allow static evaluations in encryption configuration (#1728)

Signed-off-by: ollevche <ollevche@gmail.com>
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
Signed-off-by: Oleksandr Levchenkov <ollevche@gmail.com>
Co-authored-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Oleksandr Levchenkov 2024-06-24 17:18:16 +03:00 committed by GitHub
parent 8f8e0aa4aa
commit 19b5287b8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 281 additions and 66 deletions

View File

@ -13,6 +13,7 @@ ENHANCEMENTS:
* Make state persistence interval configurable via `TF_STATE_PERSIST_INTERVAL` environment variable ([#1591](https://github.com/opentofu/opentofu/pull/1591)) * Make state persistence interval configurable via `TF_STATE_PERSIST_INTERVAL` environment variable ([#1591](https://github.com/opentofu/opentofu/pull/1591))
* Improved performance of writing state files and reduced their size using compact json encoding. ([#1647](https://github.com/opentofu/opentofu/pull/1647)) * Improved performance of writing state files and reduced their size using compact json encoding. ([#1647](https://github.com/opentofu/opentofu/pull/1647))
* Allow to reference variable inside the `variables` block of a test file. ([1488](https://github.com/opentofu/opentofu/pull/1488)) * Allow to reference variable inside the `variables` block of a test file. ([1488](https://github.com/opentofu/opentofu/pull/1488))
* Allow variables and other static values to be used in encryption configuration. ([1728](https://github.com/opentofu/opentofu/pull/1728))
BUG FIXES: BUG FIXES:
* Fixed validation for `enforced` flag in encryption configuration. ([#1711](https://github.com/opentofu/opentofu/pull/1711)) * Fixed validation for `enforced` flag in encryption configuration. ([#1711](https://github.com/opentofu/opentofu/pull/1711))

View File

@ -1,8 +1,17 @@
variable "passphrase" {
type = string
default = "aaaaaaaa-83f1-47ec-9b2d-2aebf6417167"
}
locals {
key_length = 32
}
terraform { terraform {
encryption { encryption {
key_provider "pbkdf2" "basic" { key_provider "pbkdf2" "basic" {
passphrase = "aaaaaaaa-83f1-47ec-9b2d-2aebf6417167" passphrase = var.passphrase
key_length = 32 key_length = local.key_length
iterations = 200000 iterations = 200000
hash_function = "sha512" hash_function = "sha512"
salt_length = 12 salt_length = 12

View File

@ -52,7 +52,7 @@ func (m *Meta) EncryptionFromModule(module *configs.Module) (encryption.Encrypti
cfg = cfg.Merge(envCfg) cfg = cfg.Merge(envCfg)
} }
enc, encDiags := encryption.New(encryption.DefaultRegistry, cfg) enc, encDiags := encryption.New(encryption.DefaultRegistry, cfg, module.StaticEvaluator)
diags = diags.Append(encDiags) diags = diags.Append(encDiags)
return enc, diags return enc, diags

View File

@ -64,6 +64,9 @@ type Module struct {
// IsOverridden indicates if the module is being overridden. It's used in // IsOverridden indicates if the module is being overridden. It's used in
// testing framework to not call the underlying module. // testing framework to not call the underlying module.
IsOverridden bool IsOverridden bool
// StaticEvaluator is used to evaluate static expressions in the scope of the Module.
StaticEvaluator *StaticEvaluator
} }
// File describes the contents of a single configuration file. // File describes the contents of a single configuration file.
@ -186,20 +189,20 @@ func NewModule(primaryFiles, overrideFiles []*File, call StaticModuleCall, sourc
} }
// Static evaluation to build a StaticContext now that module has all relevant Locals / Variables // Static evaluation to build a StaticContext now that module has all relevant Locals / Variables
eval := NewStaticEvaluator(mod, call) mod.StaticEvaluator = NewStaticEvaluator(mod, call)
// If we have a backend, it may have fields that require locals/vars // If we have a backend, it may have fields that require locals/vars
if mod.Backend != nil { if mod.Backend != nil {
// We don't know the backend type / loader at this point so we save the context for later use // We don't know the backend type / loader at this point so we save the context for later use
mod.Backend.Eval = eval mod.Backend.Eval = mod.StaticEvaluator
} }
if mod.CloudConfig != nil { if mod.CloudConfig != nil {
mod.CloudConfig.eval = eval mod.CloudConfig.eval = mod.StaticEvaluator
} }
// Process all module calls now that we have the static context // Process all module calls now that we have the static context
for _, mc := range mod.ModuleCalls { for _, mc := range mod.ModuleCalls {
mDiags := mc.decodeStaticFields(eval) mDiags := mc.decodeStaticFields(mod.StaticEvaluator)
diags = append(diags, mDiags...) diags = append(diags, mDiags...)
} }

View File

@ -272,7 +272,7 @@ func TestParserLoadConfigDirWithTests_TofuFiles(t *testing.T) {
parser := NewParser(nil) parser := NewParser(nil)
path := tt.path path := tt.path
mod, diags := parser.LoadConfigDirWithTests(path, "test") mod, diags := parser.LoadConfigDirWithTests(path, "test", RootModuleCallForTesting())
if len(diags) != 0 { if len(diags) != 0 {
t.Errorf("unexpected diagnostics") t.Errorf("unexpected diagnostics")
for _, diag := range diags { for _, diag := range diags {

View File

@ -117,3 +117,12 @@ func (s StaticEvaluator) DecodeBlock(body hcl.Body, spec hcldec.Spec, ident Stat
diags = append(diags, valDiags...) diags = append(diags, valDiags...)
return val, diags return val, diags
} }
func (s StaticEvaluator) EvalContext(ident StaticIdentifier, refs []*addrs.Reference) (*hcl.EvalContext, hcl.Diagnostics) {
return s.EvalContextWithParent(nil, ident, refs)
}
func (s StaticEvaluator) EvalContextWithParent(parent *hcl.EvalContext, ident StaticIdentifier, refs []*addrs.Reference) (*hcl.EvalContext, hcl.Diagnostics) {
evalCtx, diags := s.scope(ident).EvalContextWithParent(parent, refs)
return evalCtx, diags.ToHCL()
}

View File

@ -9,6 +9,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider" "github.com/opentofu/opentofu/internal/encryption/keyprovider"
"github.com/opentofu/opentofu/internal/encryption/method" "github.com/opentofu/opentofu/internal/encryption/method"
@ -28,15 +29,17 @@ type baseEncryption struct {
name string name string
encMethods []method.Method encMethods []method.Method
encMeta map[keyprovider.Addr][]byte encMeta map[keyprovider.Addr][]byte
staticEval *configs.StaticEvaluator
} }
func newBaseEncryption(enc *encryption, target *config.TargetConfig, enforced bool, name string) (*baseEncryption, hcl.Diagnostics) { func newBaseEncryption(enc *encryption, target *config.TargetConfig, enforced bool, name string, staticEval *configs.StaticEvaluator) (*baseEncryption, hcl.Diagnostics) {
base := &baseEncryption{ base := &baseEncryption{
enc: enc, enc: enc,
target: target, target: target,
enforced: enforced, enforced: enforced,
name: name, name: name,
encMeta: make(map[keyprovider.Addr][]byte), encMeta: make(map[keyprovider.Addr][]byte),
staticEval: staticEval,
} }
// Setup the encryptor // Setup the encryptor
// //

View File

@ -31,6 +31,16 @@ func (c *EncryptionConfig) Merge(override *EncryptionConfig) *EncryptionConfig {
return MergeConfigs(c, override) return MergeConfigs(c, override)
} }
// GetKeyProvider takes type and name arguments to find a respective KeyProviderConfig in the list.
func (c *EncryptionConfig) GetKeyProvider(kpType, kpName string) (KeyProviderConfig, bool) {
for _, kp := range c.KeyProviderConfigs {
if kp.Type == kpType && kp.Name == kpName {
return kp, true
}
}
return KeyProviderConfig{}, false
}
// KeyProviderConfig describes the terraform.encryption.key_provider.* block you can use to declare a key provider for // KeyProviderConfig describes the terraform.encryption.key_provider.* block you can use to declare a key provider for
// encryption. The Body field will contain the remaining undeclared fields the key provider can consume. // encryption. The Body field will contain the remaining undeclared fields the key provider can consume.
type KeyProviderConfig struct { type KeyProviderConfig struct {

View File

@ -7,6 +7,7 @@ package encryption
import ( import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/registry" "github.com/opentofu/opentofu/internal/encryption/registry"
) )
@ -37,7 +38,7 @@ type encryption struct {
} }
// New creates a new Encryption provider from the given configuration and registry. // New creates a new Encryption provider from the given configuration and registry.
func New(reg registry.Registry, cfg *config.EncryptionConfig) (Encryption, hcl.Diagnostics) { func New(reg registry.Registry, cfg *config.EncryptionConfig, staticEval *configs.StaticEvaluator) (Encryption, hcl.Diagnostics) {
if cfg == nil { if cfg == nil {
return Disabled(), nil return Disabled(), nil
} }
@ -52,21 +53,21 @@ func New(reg registry.Registry, cfg *config.EncryptionConfig) (Encryption, hcl.D
var encDiags hcl.Diagnostics var encDiags hcl.Diagnostics
if cfg.State != nil { if cfg.State != nil {
enc.state, encDiags = newStateEncryption(enc, cfg.State.AsTargetConfig(), cfg.State.Enforced, "state") enc.state, encDiags = newStateEncryption(enc, cfg.State.AsTargetConfig(), cfg.State.Enforced, "state", staticEval)
diags = append(diags, encDiags...) diags = append(diags, encDiags...)
} else { } else {
enc.state = StateEncryptionDisabled() enc.state = StateEncryptionDisabled()
} }
if cfg.Plan != nil { if cfg.Plan != nil {
enc.plan, encDiags = newPlanEncryption(enc, cfg.Plan.AsTargetConfig(), cfg.Plan.Enforced, "plan") enc.plan, encDiags = newPlanEncryption(enc, cfg.Plan.AsTargetConfig(), cfg.Plan.Enforced, "plan", staticEval)
diags = append(diags, encDiags...) diags = append(diags, encDiags...)
} else { } else {
enc.plan = PlanEncryptionDisabled() enc.plan = PlanEncryptionDisabled()
} }
if cfg.Remote != nil && cfg.Remote.Default != nil { if cfg.Remote != nil && cfg.Remote.Default != nil {
enc.remoteDefault, encDiags = newStateEncryption(enc, cfg.Remote.Default, false, "remote.default") enc.remoteDefault, encDiags = newStateEncryption(enc, cfg.Remote.Default, false, "remote.default", staticEval)
diags = append(diags, encDiags...) diags = append(diags, encDiags...)
} else { } else {
enc.remoteDefault = StateEncryptionDisabled() enc.remoteDefault = StateEncryptionDisabled()
@ -76,7 +77,7 @@ func New(reg registry.Registry, cfg *config.EncryptionConfig) (Encryption, hcl.D
for _, remoteTarget := range cfg.Remote.Targets { for _, remoteTarget := range cfg.Remote.Targets {
// TODO the addr here should be generated in one place. // TODO the addr here should be generated in one place.
addr := "remote.remote_state_datasource." + remoteTarget.Name addr := "remote.remote_state_datasource." + remoteTarget.Name
enc.remotes[remoteTarget.Name], encDiags = newStateEncryption(enc, remoteTarget.AsTargetConfig(), false, addr) enc.remotes[remoteTarget.Name], encDiags = newStateEncryption(enc, remoteTarget.AsTargetConfig(), false, addr, staticEval)
diags = append(diags, encDiags...) diags = append(diags, encDiags...)
} }
} }

View File

@ -9,6 +9,7 @@ package enctest
import ( import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption" "github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static" "github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
@ -35,7 +36,9 @@ func EncryptionDirect(configData string) encryption.Encryption {
handleDiags(diags) handleDiags(diags)
enc, diags := encryption.New(reg, cfg) staticEval := configs.NewStaticEvaluator(nil, configs.RootModuleCallForTesting())
enc, diags := encryption.New(reg, cfg, staticEval)
handleDiags(diags) handleDiags(diags)
return enc return enc

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption" "github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static" "github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
@ -57,8 +58,11 @@ func Example() {
// Merge the configurations // Merge the configurations
cfg := config.MergeConfigs(cfgA, cfgB) cfg := config.MergeConfigs(cfgA, cfgB)
// Construct static evaluator to pass additional values into encryption configuration.
staticEval := configs.NewStaticEvaluator(nil, configs.RootModuleCallForTesting())
// Construct the encryption object // Construct the encryption object
enc, diags := encryption.New(reg, cfg) enc, diags := encryption.New(reg, cfg, staticEval)
handleDiags(diags) handleDiags(diags)
sfe := enc.State() sfe := enc.State()

View File

@ -10,7 +10,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"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/config"
"github.com/opentofu/opentofu/internal/lang"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/encryption/keyprovider" "github.com/opentofu/opentofu/internal/encryption/keyprovider"
@ -88,17 +91,17 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
keyProviderDescriptor, err := e.reg.GetKeyProviderDescriptor(id) keyProviderDescriptor, err := e.reg.GetKeyProviderDescriptor(id)
if err != nil { if err != nil {
if errors.Is(err, &registry.KeyProviderNotFoundError{}) { if errors.Is(err, &registry.KeyProviderNotFoundError{}) {
return hcl.Diagnostics{&hcl.Diagnostic{ return diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Unknown key_provider type", Summary: "Unknown key_provider type",
Detail: fmt.Sprintf("Can not find %q", cfg.Type), Detail: fmt.Sprintf("Can not find %q", cfg.Type),
}} })
} }
return hcl.Diagnostics{&hcl.Diagnostic{ return diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: fmt.Sprintf("Error fetching key_provider %q", cfg.Type), Summary: fmt.Sprintf("Error fetching key_provider %q", cfg.Type),
Detail: err.Error(), Detail: err.Error(),
}} })
} }
// Now that we know we have the correct Descriptor, we can decode the configuration // Now that we know we have the correct Descriptor, we can decode the configuration
@ -106,21 +109,21 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
keyProviderConfig := keyProviderDescriptor.ConfigStruct() keyProviderConfig := keyProviderDescriptor.ConfigStruct()
// Locate all the dependencies // Locate all the dependencies
deps, diags := gohcl.VariablesInBody(cfg.Body, keyProviderConfig) deps, varDiags := gohcl.VariablesInBody(cfg.Body, keyProviderConfig)
diags = append(diags, varDiags...)
if diags.HasErrors() { if diags.HasErrors() {
return diags return diags
} }
// Required Dependencies // 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 { for _, dep := range deps {
// Key Provider references should be in the form key_provider.type.name // Key Provider references should be in the form key_provider.type.name
if len(dep) != 3 { if len(dep) != 3 {
diags = append(diags, &hcl.Diagnostic{ nonKeyProviderDeps = append(nonKeyProviderDeps, dep)
Severity: hcl.DiagError,
Summary: "Invalid key_provider reference",
Detail: "Expected reference in form key_provider.type.name",
Subject: dep.SourceRange().Ptr(),
})
continue continue
} }
@ -130,23 +133,23 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
depName := (dep[2].(hcl.TraverseAttr)).Name depName := (dep[2].(hcl.TraverseAttr)).Name
if depRoot != "key_provider" { if depRoot != "key_provider" {
nonKeyProviderDeps = append(nonKeyProviderDeps, dep)
continue
}
kpc, ok := e.cfg.GetKeyProvider(depType, depName)
if !ok {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Invalid key_provider reference", Summary: "Undefined Key Provider",
Detail: "Expected reference in form key_provider.type.name", Detail: fmt.Sprintf("Key provider %s.%s is missing from the encryption configuration.", depType, depName),
Subject: dep.SourceRange().Ptr(), Subject: dep.SourceRange().Ptr(),
}) })
continue continue
} }
for _, kpc := range e.cfg.KeyProviderConfigs {
// Find the key provider in the config
if kpc.Type == depType && kpc.Name == depName {
depDiags := e.setupKeyProvider(kpc, stack) depDiags := e.setupKeyProvider(kpc, stack)
diags = append(diags, depDiags...) diags = append(diags, depDiags...)
break
}
}
} }
if diags.HasErrors() { if diags.HasErrors() {
// We should not continue now if we have any diagnostics that are errors // We should not continue now if we have any diagnostics that are errors
@ -156,8 +159,24 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
return diags return diags
} }
refs, refDiags := lang.References(addrs.ParseRef, nonKeyProviderDeps)
diags = append(diags, refDiags.ToHCL()...)
if diags.HasErrors() {
return diags
}
evalCtx, evalDiags := e.staticEval.EvalContextWithParent(e.ctx, configs.StaticIdentifier{
Module: addrs.RootModule,
Subject: fmt.Sprintf("encryption.key_provider.%s.%s", cfg.Type, cfg.Name),
DeclRange: e.cfg.DeclRange,
}, refs)
diags = append(diags, evalDiags...)
if diags.HasErrors() {
return diags
}
// Initialize the Key Provider // Initialize the Key Provider
decodeDiags := gohcl.DecodeBody(cfg.Body, e.ctx, keyProviderConfig) decodeDiags := gohcl.DecodeBody(cfg.Body, evalCtx, keyProviderConfig)
diags = append(diags, decodeDiags...) diags = append(diags, decodeDiags...)
if diags.HasErrors() { if diags.HasErrors() {
return diags return diags

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption" "github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static" "github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
@ -44,7 +45,9 @@ func Example() {
panic(diags) panic(diags)
} }
enc, diags := encryption.New(registry, cfg) staticEvaluator := configs.NewStaticEvaluator(nil, configs.RootModuleCallForTesting())
enc, diags := encryption.New(registry, cfg, staticEvaluator)
if diags.HasErrors() { if diags.HasErrors() {
panic(diags) panic(diags)
} }

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
) )
@ -49,8 +50,8 @@ type planEncryption struct {
base *baseEncryption base *baseEncryption
} }
func newPlanEncryption(enc *encryption, target *config.TargetConfig, enforced bool, name string) (PlanEncryption, hcl.Diagnostics) { func newPlanEncryption(enc *encryption, target *config.TargetConfig, enforced bool, name string, staticEval *configs.StaticEvaluator) (PlanEncryption, hcl.Diagnostics) {
base, diags := newBaseEncryption(enc, target, enforced, name) base, diags := newBaseEncryption(enc, target, enforced, name, staticEval)
return &planEncryption{base}, diags return &planEncryption{base}, diags
} }

View File

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
) )
@ -57,8 +58,8 @@ type stateEncryption struct {
base *baseEncryption base *baseEncryption
} }
func newStateEncryption(enc *encryption, target *config.TargetConfig, enforced bool, name string) (StateEncryption, hcl.Diagnostics) { func newStateEncryption(enc *encryption, target *config.TargetConfig, enforced bool, name string, staticEval *configs.StaticEvaluator) (StateEncryption, hcl.Diagnostics) {
base, diags := newBaseEncryption(enc, target, enforced, name) base, diags := newBaseEncryption(enc, target, enforced, name, staticEval)
return &stateEncryption{base}, diags return &stateEncryption{base}, diags
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/gohcl"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config" "github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider" "github.com/opentofu/opentofu/internal/encryption/keyprovider"
"github.com/opentofu/opentofu/internal/encryption/method" "github.com/opentofu/opentofu/internal/encryption/method"
@ -31,6 +32,7 @@ type targetBuilder struct {
keyValues map[string]map[string]cty.Value keyValues map[string]map[string]cty.Value
methodValues map[string]map[string]cty.Value methodValues map[string]map[string]cty.Value
methods map[method.Addr]method.Method methods map[method.Addr]method.Method
staticEval *configs.StaticEvaluator
} }
func (base *baseEncryption) buildTargetMethods(meta map[keyprovider.Addr][]byte) ([]method.Method, hcl.Diagnostics) { func (base *baseEncryption) buildTargetMethods(meta map[keyprovider.Addr][]byte) ([]method.Method, hcl.Diagnostics) {
@ -40,6 +42,7 @@ func (base *baseEncryption) buildTargetMethods(meta map[keyprovider.Addr][]byte)
cfg: base.enc.cfg, cfg: base.enc.cfg,
reg: base.enc.reg, reg: base.enc.reg,
staticEval: base.staticEval,
ctx: &hcl.EvalContext{ ctx: &hcl.EvalContext{
Variables: map[string]cty.Value{}, Variables: map[string]cty.Value{},
}, },

View File

@ -10,6 +10,8 @@ import (
"testing" "testing"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"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/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider" "github.com/opentofu/opentofu/internal/encryption/keyprovider"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static" "github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
@ -18,6 +20,7 @@ import (
"github.com/opentofu/opentofu/internal/encryption/method/unencrypted" "github.com/opentofu/opentofu/internal/encryption/method/unencrypted"
"github.com/opentofu/opentofu/internal/encryption/registry" "github.com/opentofu/opentofu/internal/encryption/registry"
"github.com/opentofu/opentofu/internal/encryption/registry/lockingencryptionregistry" "github.com/opentofu/opentofu/internal/encryption/registry/lockingencryptionregistry"
"github.com/zclconf/go-cty/cty"
) )
func TestBaseEncryption_buildTargetMethods(t *testing.T) { func TestBaseEncryption_buildTargetMethods(t *testing.T) {
@ -112,6 +115,36 @@ func TestBaseEncryption_buildTargetMethods(t *testing.T) {
`, `,
wantErr: "<nil>: Unencrypted method is forbidden; Unable to use `unencrypted` method since the `enforced` flag is used.", wantErr: "<nil>: Unencrypted method is forbidden; Unable to use `unencrypted` method since the `enforced` flag is used.",
}, },
"key-from-vars": {
rawConfig: `
key_provider "static" "basic" {
key = var.key
}
method "aes_gcm" "example" {
keys = key_provider.static.basic
}
state {
method = method.aes_gcm.example
}
`,
wantMethods: []func(method.Method) bool{
aesgcm.Is,
},
},
"undefined-key-from-vars": {
rawConfig: `
key_provider "static" "basic" {
key = var.undefinedkey
}
method "aes_gcm" "example" {
keys = key_provider.static.basic
}
state {
method = method.aes_gcm.example
}
`,
wantErr: "Test Config Source:3,12-28: Undefined variable; Undefined variable var.undefinedkey",
},
} }
reg := lockingencryptionregistry.New() reg := lockingencryptionregistry.New()
@ -125,8 +158,26 @@ func TestBaseEncryption_buildTargetMethods(t *testing.T) {
panic(err) panic(err)
} }
mod := &configs.Module{
Variables: map[string]*configs.Variable{
"key": {
Name: "key",
Default: cty.StringVal("6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"),
Type: cty.String,
},
},
}
getVars := func(v *configs.Variable) (cty.Value, hcl.Diagnostics) {
return v.Default, nil
}
modCall := configs.NewStaticModuleCall(addrs.RootModule, getVars, "<testing>", "")
staticEval := configs.NewStaticEvaluator(mod, modCall)
for name, test := range tests { for name, test := range tests {
t.Run(name, test.newTestRun(reg)) t.Run(name, test.newTestRun(reg, staticEval))
} }
} }
@ -136,7 +187,7 @@ type btmTestCase struct {
wantErr string wantErr string
} }
func (testCase btmTestCase) newTestRun(reg registry.Registry) func(t *testing.T) { func (testCase btmTestCase) newTestRun(reg registry.Registry, staticEval *configs.StaticEvaluator) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
t.Parallel() t.Parallel()
@ -154,6 +205,7 @@ func (testCase btmTestCase) newTestRun(reg registry.Registry) func(t *testing.T)
enforced: cfg.State.Enforced, enforced: cfg.State.Enforced,
name: "test", name: "test",
encMeta: make(map[keyprovider.Addr][]byte), encMeta: make(map[keyprovider.Addr][]byte),
staticEval: staticEval,
} }
methods, diags := base.buildTargetMethods(base.encMeta) methods, diags := base.buildTargetMethods(base.encMeta)

View File

@ -260,7 +260,7 @@ func (s *Scope) EvalReference(ref *addrs.Reference, wantType cty.Type) (cty.Valu
// We cheat a bit here and just build an EvalContext for our requested // We cheat a bit here and just build an EvalContext for our requested
// reference with the "self" address overridden, and then pull the "self" // reference with the "self" address overridden, and then pull the "self"
// result out of it to return. // result out of it to return.
ctx, ctxDiags := s.evalContext([]*addrs.Reference{ref}, ref.Subject) ctx, ctxDiags := s.evalContext(nil, []*addrs.Reference{ref}, ref.Subject)
diags = diags.Append(ctxDiags) diags = diags.Append(ctxDiags)
val := ctx.Variables["self"] val := ctx.Variables["self"]
if val == cty.NilVal { if val == cty.NilVal {
@ -289,21 +289,34 @@ func (s *Scope) EvalReference(ref *addrs.Reference, wantType cty.Type) (cty.Valu
// this type offers, but this is here for less common situations where the // this type offers, but this is here for less common situations where the
// caller will handle the evaluation calls itself. // caller will handle the evaluation calls itself.
func (s *Scope) EvalContext(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) { func (s *Scope) EvalContext(refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) {
return s.evalContext(refs, s.SelfAddr) return s.evalContext(nil, refs, s.SelfAddr)
} }
func (s *Scope) evalContext(refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) { // EvalContextWithParent is exactly the same as EvalContext except the resulting hcl.EvalContext
// will be derived from the given parental hcl.EvalContext. It will enable different hcl mechanisms
// to iteratively lookup target functions and variables in EvalContext's parent.
// See Traversal.TraverseAbs (hcl) or FunctionCallExpr.Value (hcl/hclsyntax) for more details.
func (s *Scope) EvalContextWithParent(p *hcl.EvalContext, refs []*addrs.Reference) (*hcl.EvalContext, tfdiags.Diagnostics) {
return s.evalContext(p, refs, s.SelfAddr)
}
//nolint:funlen,gocyclo,cyclop // TODO: refactor this function to match linting requirements
func (s *Scope) evalContext(parent *hcl.EvalContext, refs []*addrs.Reference, selfAddr addrs.Referenceable) (*hcl.EvalContext, tfdiags.Diagnostics) {
if s == nil { if s == nil {
panic("attempt to construct EvalContext for nil Scope") panic("attempt to construct EvalContext for nil Scope")
} }
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
vals := make(map[string]cty.Value) vals := make(map[string]cty.Value)
funcs := make(map[string]function.Function) funcs := make(map[string]function.Function)
ctx := &hcl.EvalContext{
Variables: vals, // Calling NewChild() on a nil parent will
Functions: funcs, // produce an EvalContext with no parent.
} ctx := parent.NewChild()
ctx.Variables = vals
ctx.Functions = funcs
for name, fn := range s.Functions() { for name, fn := range s.Functions() {
funcs[name] = fn funcs[name] = fn
} }

View File

@ -8,6 +8,7 @@ package lang
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"reflect"
"testing" "testing"
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
@ -19,6 +20,7 @@ import (
"github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
ctyjson "github.com/zclconf/go-cty/cty/json" ctyjson "github.com/zclconf/go-cty/cty/json"
) )
@ -421,6 +423,76 @@ func TestScopeEvalContext(t *testing.T) {
} }
} }
// TestScopeEvalContextWithParent tests if the resulting EvalCtx has correct parent.
func TestScopeEvalContextWithParent(t *testing.T) {
t.Run("with-parent", func(t *testing.T) {
barStr, barFunc := cty.StringVal("bar"), function.New(&function.Spec{
Impl: func(_ []cty.Value, _ cty.Type) (cty.Value, error) {
return cty.NilVal, nil
},
})
scope, parent := &Scope{}, &hcl.EvalContext{
Variables: map[string]cty.Value{
"foo": barStr,
},
Functions: map[string]function.Function{
"foo": barFunc,
},
}
child, diags := scope.EvalContextWithParent(parent, nil)
if len(diags) != 0 {
t.Errorf("Unexpected diagnostics:")
for _, diag := range diags {
t.Errorf("- %s", diag)
}
return
}
if child.Parent() == nil {
t.Fatalf("Child EvalCtx has no parent")
}
if child.Parent() != parent {
t.Fatalf("Child EvalCtx has different parent:\n GOT:%v\nWANT:%v", child.Parent(), parent)
}
if ln := len(child.Parent().Variables); ln != 1 {
t.Fatalf("EvalContextWithParent modified parent's variables: incorrent length: %d", ln)
}
if v := child.Parent().Variables["foo"]; !v.RawEquals(barStr) {
t.Fatalf("EvalContextWithParent modified parent's variables:\n GOT:%v\nWANT:%v", v, barStr)
}
if ln := len(child.Parent().Functions); ln != 1 {
t.Fatalf("EvalContextWithParent modified parent's functions: incorrent length: %d", ln)
}
if v := child.Parent().Functions["foo"]; !reflect.DeepEqual(v, barFunc) {
t.Fatalf("EvalContextWithParent modified parent's functions:\n GOT:%v\nWANT:%v", v, barFunc)
}
})
t.Run("zero-parent", func(t *testing.T) {
scope := &Scope{}
root, diags := scope.EvalContextWithParent(nil, nil)
if len(diags) != 0 {
t.Errorf("Unexpected diagnostics:")
for _, diag := range diags {
t.Errorf("- %s", diag)
}
return
}
if root.Parent() != nil {
t.Fatalf("Resulting EvalCtx has unexpected parent: %v", root.Parent())
}
})
}
func TestScopeExpandEvalBlock(t *testing.T) { func TestScopeExpandEvalBlock(t *testing.T) {
nestedObjTy := cty.Object(map[string]cty.Type{ nestedObjTy := cty.Object(map[string]cty.Type{
"boop": cty.String, "boop": cty.String,

View File

@ -1,3 +1,8 @@
variable "passphrase" {
# Change passphrase to be at least 16 characters long:
default = "changeme!"
}
terraform { terraform {
encryption { encryption {
## Step 1: Add the unencrypted method: ## Step 1: Add the unencrypted method:
@ -5,8 +10,7 @@ terraform {
## Step 2: Add the desired key provider: ## Step 2: Add the desired key provider:
key_provider "pbkdf2" "mykey" { key_provider "pbkdf2" "mykey" {
# Change this to be at least 16 characters long: passphrase = var.passphrase
passphrase = "changeme!"
} }
## Step 3: Add the desired encryption method: ## Step 3: Add the desired encryption method:

View File

@ -1,9 +1,13 @@
variable "passphrase" {
# Change passphrase to be at least 16 characters long:
default = "changeme!"
}
terraform { terraform {
encryption { encryption {
## Step 1: Add the desired key provider: ## Step 1: Add the desired key provider:
key_provider "pbkdf2" "mykey" { key_provider "pbkdf2" "mykey" {
# Change this to be at least 16 characters long: passphrase = var.passphrase
passphrase = "changeme!"
} }
## Step 2: Set up your encryption method: ## Step 2: Set up your encryption method:
method "aes_gcm" "new_method" { method "aes_gcm" "new_method" {