// 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" "encoding/json" "errors" "fmt" "strings" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" "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/keyprovider" "github.com/opentofu/opentofu/internal/encryption/registry" "github.com/opentofu/opentofu/internal/lang" "github.com/opentofu/opentofu/internal/lang/marks" "github.com/zclconf/go-cty/cty" ) // valueMap is a helper type for building hcl.EvalContexts for key_providers. type valueMap map[string]map[string]cty.Value func (v valueMap) set(first string, second string, value cty.Value) { if _, ok := v[first]; !ok { v[first] = make(map[string]cty.Value) } v[first][second] = value } func (v valueMap) has(first string, second string) bool { s, ok := v[first] if !ok { return false } _, ok = s[second] return ok } func (v valueMap) hclEvalContext(root string) *hcl.EvalContext { mMap := make(map[string]cty.Value) for name, ms := range v { mMap[name] = cty.ObjectVal(ms) } return &hcl.EvalContext{ Variables: map[string]cty.Value{root: cty.ObjectVal(mMap)}, } } // Given a set of hcl.Traversals, determine the required key provider configs and non-key_provider references func filterKeyProviderReferences(cfg *config.EncryptionConfig, deps []hcl.Traversal) ([]config.KeyProviderConfig, []*addrs.Reference, hcl.Diagnostics) { var diags hcl.Diagnostics var keyProviderDeps []config.KeyProviderConfig // lang.References is going to fail parsing key_provider deps // so we filter them out in nonKeyProviderDeps. var nonKeyProviderDeps []hcl.Traversal // Setting up key providers from deps. for _, dep := range deps { // Key Provider references should be in the form key_provider.type.name if len(dep) != 3 { nonKeyProviderDeps = append(nonKeyProviderDeps, dep) continue } depRoot := dep.RootName() if depRoot != "key_provider" { nonKeyProviderDeps = append(nonKeyProviderDeps, dep) continue } depTypeAttr, typeOk := dep[1].(hcl.TraverseAttr) depNameAttr, nameOk := dep[2].(hcl.TraverseAttr) if !typeOk || !nameOk { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Invalid Key Provider expression format", Detail: "The key_provider symbol must be followed by two more attribute names specifying the type and name of the selected key provider.", Subject: dep.SourceRange().Ptr(), }) continue } depType := depTypeAttr.Name depName := depNameAttr.Name kpc, ok := cfg.GetKeyProvider(depType, depName) if !ok { diags = diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Reference to undeclared key provider", Detail: fmt.Sprintf("There is no key_provider %q %q block declared in the encryption block.", depType, depName), Subject: dep.SourceRange().Ptr(), }) continue } keyProviderDeps = append(keyProviderDeps, kpc) } refs, refDiags := lang.References(addrs.ParseRef, nonKeyProviderDeps) diags = diags.Extend(refDiags.ToHCL()) return keyProviderDeps, refs, diags } // setupKeyProviders sets up the key providers for encryption. It returns a list of diagnostics if any of the key providers // are invalid. func setupKeyProviders(ctx context.Context, enc *config.EncryptionConfig, cfgs []config.KeyProviderConfig, meta keyProviderMetadata, reg registry.Registry, staticEval *configs.StaticEvaluator) (*hcl.EvalContext, hcl.Diagnostics) { var diags hcl.Diagnostics kpData := make(valueMap) for _, keyProviderConfig := range cfgs { diags = diags.Extend(setupKeyProvider(ctx, enc, keyProviderConfig, kpData, nil, meta, reg, staticEval)) } return kpData.hclEvalContext("key_provider"), diags } func setupKeyProvider(ctx context.Context, enc *config.EncryptionConfig, cfg config.KeyProviderConfig, kpData valueMap, stack []config.KeyProviderConfig, meta keyProviderMetadata, reg registry.Registry, staticEval *configs.StaticEvaluator) hcl.Diagnostics { // Check if we have already setup this Descriptor (due to dependency loading) // if we've already setup this key provider, then we don't need to do it again // and we can return early if kpData.has(cfg.Type, cfg.Name) { return nil } // Mark this key provider as partially handled. This value will be replaced below once it is actually known. // The goal is to allow an early return via the above if statement to prevent duplicate errors if errors are encountered in the key loading stack. kpData.set(cfg.Type, cfg.Name, cty.UnknownVal(cty.DynamicPseudoType)) // Check for circular references, this is done by inspecting the stack of key providers // that are currently being setup. If we find a key provider in the stack that matches // the current key provider, then we have a circular reference and we should return an error // to the user. for _, s := range stack { if s == cfg { addr, diags := keyprovider.NewAddr(cfg.Type, cfg.Name) stackAddrs, diag := keyProvidersStack(append(stack, cfg)) diags = diags.Extend(diag) return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Circular reference detected", Detail: fmt.Sprintf("Cannot load %s due to circular reference between key providers. Stack trace %s", addr, strings.Join(stackAddrs, " -> ")), }) } } stack = append(stack, cfg) // Pull the meta key out for error messages and meta storage tmpMetaKey, diags := cfg.Addr() if diags.HasErrors() { return diags } metaKey := keyprovider.MetaStorageKey(tmpMetaKey) if cfg.EncryptedMetadataAlias != "" { metaKey = keyprovider.MetaStorageKey(cfg.EncryptedMetadataAlias) } // Lookup the KeyProviderDescriptor from the registry id := keyprovider.ID(cfg.Type) keyProviderDescriptor, err := reg.GetKeyProviderDescriptor(id) if err != nil { if errors.Is(err, ®istry.KeyProviderNotFoundError{}) { return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unknown key_provider type", Detail: fmt.Sprintf("Can not find %q", cfg.Type), }) } return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: fmt.Sprintf("Error fetching key_provider %q", cfg.Type), Detail: err.Error(), }) } // Now that we know we have the correct Descriptor, we can decode the configuration // and build the KeyProvider keyProviderConfig := keyProviderDescriptor.ConfigStruct() // Locate all the dependencies deps, varDiags := gohcl.VariablesInBody(cfg.Body, keyProviderConfig) diags = diags.Extend(varDiags) if diags.HasErrors() { return diags } // Filter between dependent key providers and static references kpConfigs, refs, filterDiags := filterKeyProviderReferences(enc, deps) diags = diags.Extend(filterDiags) if diags.HasErrors() { return diags } // Ensure all key provider dependencies have been initialized for _, kp := range kpConfigs { diags = diags.Extend(setupKeyProvider(ctx, enc, kp, kpData, stack, meta, reg, staticEval)) } if diags.HasErrors() { return diags } evalCtx, evalDiags := staticEval.EvalContextWithParent(ctx, kpData.hclEvalContext("key_provider"), configs.StaticIdentifier{ Module: addrs.RootModule, Subject: fmt.Sprintf("encryption.key_provider.%s.%s", cfg.Type, cfg.Name), DeclRange: enc.DeclRange, }, refs) diags = diags.Extend(evalDiags) if diags.HasErrors() { return diags } // gohcl does not handle marks, we need to remove the sensitive and ephemeral marks from any input variables // We assume that the entire configuration in the encryption block should be treated as sensitive for key, sv := range evalCtx.Variables { if marks.ContainsAnyMark(sv, marks.Sensitive, marks.Ephemeral) { evalCtx.Variables[key], _ = sv.UnmarkDeep() } } // Initialize the Key Provider decodeDiags := gohcl.DecodeBody(cfg.Body, evalCtx, keyProviderConfig) diags = diags.Extend(decodeDiags) if diags.HasErrors() { return diags } // Build the Key Provider from the configuration keyProvider, keyMetaIn, err := keyProviderConfig.Build() if err != nil { return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unable to build encryption key data", Detail: fmt.Sprintf("%s failed with error: %s", metaKey, err.Error()), }) } // Add the metadata if meta, ok := meta.input[metaKey]; ok { err := json.Unmarshal(meta, keyMetaIn) if err != nil { return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unable to decode encrypted metadata (did you change your encryption config?)", Detail: fmt.Sprintf("metadata decoder for %s failed with error: %s", metaKey, err.Error()), }) } } output, keyMetaOut, err := keyProvider.Provide(keyMetaIn) if err != nil { return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unable to fetch encryption key data", Detail: fmt.Sprintf("%s failed with error: %s", metaKey, err.Error()), }) } if keyMetaOut != nil { if _, ok := meta.output[metaKey]; ok { return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Duplicate metadata key", Detail: fmt.Sprintf("The metadata key %s is duplicated across multiple key providers for the same method; use the encrypted_metadata_alias option to specify unique metadata keys for each key provider in an encryption method", metaKey), }) } meta.output[metaKey], err = json.Marshal(keyMetaOut) if err != nil { return diags.Append(&hcl.Diagnostic{ Severity: hcl.DiagError, Summary: "Unable to encode encrypted metadata", Detail: fmt.Sprintf("The metadata encoder for %s failed with error: %s", metaKey, err.Error()), }) } } kpData.set(cfg.Type, cfg.Name, output.Cty()) return nil } func keyProvidersStack(stack []config.KeyProviderConfig) ([]string, hcl.Diagnostics) { res := make([]string, len(stack)) var diags hcl.Diagnostics for i, cfg := range stack { addr, diag := cfg.Addr() diags = diags.Extend(diag) if diag.HasErrors() { res[i] = "" continue } res[i] = string(addr) } return res, diags }