| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package encryption | |
| import ( | |
| "context" | |
| "errors" | |
| "fmt" | |
| "strings" | |
| "github.com/hashicorp/hcl/v2" | |
| "github.com/hashicorp/hcl/v2/hclsyntax" | |
| "github.com/opentofu/opentofu/internal/addrs" | |
| "github.com/opentofu/opentofu/internal/configs" | |
| "github.com/opentofu/opentofu/internal/encryption/config" | |
| "github.com/opentofu/opentofu/internal/encryption/method" | |
| "github.com/opentofu/opentofu/internal/encryption/registry" | |
| "github.com/zclconf/go-cty/cty" | |
| ) | |
| // setupMethod sets up a single method for encryption. It returns a list of diagnostics if the method is invalid. | |
| func setupMethod(ctx context.Context, enc *config.EncryptionConfig, cfg config.MethodConfig, meta keyProviderMetadata, reg registry.Registry, staticEval *configs.StaticEvaluator) (method.Method, hcl.Diagnostics) { | |
| // Lookup the definition of the encryption method from the registry | |
| encryptionMethod, err := reg.GetMethodDescriptor(method.ID(cfg.Type)) | |
| if err != nil { | |
| // Handle if the method was not found | |
| var notFoundError *registry.MethodNotFoundError | |
| if errors.Is(err, notFoundError) { | |
| return nil, hcl.Diagnostics{{ | |
| Severity: hcl.DiagError, | |
| Summary: "Unknown encryption method type", | |
| Detail: fmt.Sprintf("Can not find %q", cfg.Type), | |
| }} | |
| } | |
| // Or, we don't know the error type, so we'll just return it as a generic error | |
| return nil, hcl.Diagnostics{{ | |
| Severity: hcl.DiagError, | |
| Summary: fmt.Sprintf("Error fetching encryption method %q", cfg.Type), | |
| Detail: err.Error(), | |
| }} | |
| } | |
| var methodCtx method.EvalContext | |
| methodCtx = method.EvalContext{ValueForExpression: func(expr hcl.Expression) (cty.Value, hcl.Diagnostics) { | |
| var diags hcl.Diagnostics | |
| deps := expr.Variables() | |
| kpConfigs, refs, kpDiags := filterKeyProviderReferences(enc, deps) | |
| diags = diags.Extend(kpDiags) | |
| if diags.HasErrors() { | |
| return cty.NilVal, diags | |
| } | |
| hclCtx, kpDiags := setupKeyProviders(ctx, enc, kpConfigs, meta, reg, staticEval) | |
| diags = diags.Extend(kpDiags) | |
| if diags.HasErrors() { | |
| return cty.NilVal, diags | |
| } | |
| hclCtx, evalDiags := staticEval.EvalContextWithParent(ctx, hclCtx, configs.StaticIdentifier{ | |
| Module: addrs.RootModule, | |
| Subject: fmt.Sprintf("encryption.method.%s.%s", cfg.Type, cfg.Name), | |
| DeclRange: enc.DeclRange, | |
| }, refs) | |
| diags = diags.Extend(evalDiags) | |
| if diags.HasErrors() { | |
| return cty.NilVal, diags | |
| } | |
| val, valDiags := expr.Value(hclCtx) | |
| diags = diags.Extend(valDiags) | |
| if diags.HasErrors() { | |
| return cty.NilVal, diags | |
| } | |
| if val.Type() == cty.String { | |
| // Try to be clever to see if it's a kp string that is actually a reference | |
| // We might want a bool to opt-in to this functionality for JSON compat on specific fields | |
| str := val.AsString() | |
| if strings.HasPrefix(str, "key_provider.") { | |
| traversal, travDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{}) | |
| if !travDiags.HasErrors() { | |
| // Call into the expr resolver again | |
| val, valDiags = methodCtx.ValueForExpression(&hclsyntax.ScopeTraversalExpr{Traversal: traversal, SrcRange: expr.Range()}) | |
| diags = diags.Extend(valDiags) | |
| } | |
| } | |
| } | |
| return val, diags | |
| }} | |
| methodConfig, diags := encryptionMethod.DecodeConfig(methodCtx, cfg.Body) | |
| if diags.HasErrors() { | |
| return nil, diags | |
| } | |
| m, err := methodConfig.Build() | |
| if err != nil { | |
| // Convert the error to a diagnostic | |
| diags = diags.Append(errorToDiagnostic(err)) | |
| } | |
| return m, diags | |
| } | |
| func errorToDiagnostic(err error) *hcl.Diagnostic { | |
| originalErr := err | |
| for e := err; e != nil; e = errors.Unwrap(e) { | |
| switch typedErr := e.(type) { | |
| case *method.ErrEncryptionFailed: | |
| return &hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Encryption operation failed during method setup", | |
| Detail: typedErr.Error(), | |
| } | |
| case *method.ErrDecryptionFailed: | |
| return &hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Decryption operation failed during method setup", | |
| Detail: typedErr.Error(), | |
| } | |
| case *method.ErrDecryptionKeyUnavailable: | |
| return &hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Required key unavailable for method setup", | |
| Detail: typedErr.Error(), | |
| } | |
| case *method.ErrCryptoFailure: | |
| return &hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Cryptographic operation failed during method setup", | |
| Detail: typedErr.Error(), | |
| } | |
| case *method.ErrInvalidConfiguration: | |
| return &hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Invalid method configuration", | |
| Detail: typedErr.Error(), | |
| } | |
| } | |
| } | |
| // Generic fallback if no specific type was matched in the error chain | |
| return &hcl.Diagnostic{ | |
| Severity: hcl.DiagError, | |
| Summary: "Encryption method configuration error", | |
| Detail: originalErr.Error(), | |
| } | |
| } |