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))
* 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 variables and other static values to be used in encryption configuration. ([1728](https://github.com/opentofu/opentofu/pull/1728))
BUG FIXES:
* 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 {
encryption {
key_provider "pbkdf2" "basic" {
passphrase = "aaaaaaaa-83f1-47ec-9b2d-2aebf6417167"
key_length = 32
passphrase = var.passphrase
key_length = local.key_length
iterations = 200000
hash_function = "sha512"
salt_length = 12

View File

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

View File

@ -64,6 +64,9 @@ type Module struct {
// IsOverridden indicates if the module is being overridden. It's used in
// testing framework to not call the underlying module.
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.
@ -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
eval := NewStaticEvaluator(mod, call)
mod.StaticEvaluator = NewStaticEvaluator(mod, call)
// If we have a backend, it may have fields that require locals/vars
if mod.Backend != nil {
// 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 {
mod.CloudConfig.eval = eval
mod.CloudConfig.eval = mod.StaticEvaluator
}
// Process all module calls now that we have the static context
for _, mc := range mod.ModuleCalls {
mDiags := mc.decodeStaticFields(eval)
mDiags := mc.decodeStaticFields(mod.StaticEvaluator)
diags = append(diags, mDiags...)
}

View File

@ -272,7 +272,7 @@ func TestParserLoadConfigDirWithTests_TofuFiles(t *testing.T) {
parser := NewParser(nil)
path := tt.path
mod, diags := parser.LoadConfigDirWithTests(path, "test")
mod, diags := parser.LoadConfigDirWithTests(path, "test", RootModuleCallForTesting())
if len(diags) != 0 {
t.Errorf("unexpected diagnostics")
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...)
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"
"fmt"
"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/method"
@ -28,15 +29,17 @@ type baseEncryption struct {
name string
encMethods []method.Method
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{
enc: enc,
target: target,
enforced: enforced,
name: name,
encMeta: make(map[keyprovider.Addr][]byte),
staticEval: staticEval,
}
// Setup the encryptor
//

View File

@ -31,6 +31,16 @@ func (c *EncryptionConfig) Merge(override *EncryptionConfig) *EncryptionConfig {
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
// encryption. The Body field will contain the remaining undeclared fields the key provider can consume.
type KeyProviderConfig struct {

View File

@ -7,6 +7,7 @@ package encryption
import (
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption/config"
"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.
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 {
return Disabled(), nil
}
@ -52,21 +53,21 @@ func New(reg registry.Registry, cfg *config.EncryptionConfig) (Encryption, hcl.D
var encDiags hcl.Diagnostics
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...)
} else {
enc.state = StateEncryptionDisabled()
}
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...)
} else {
enc.plan = PlanEncryptionDisabled()
}
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...)
} else {
enc.remoteDefault = StateEncryptionDisabled()
@ -76,7 +77,7 @@ func New(reg registry.Registry, cfg *config.EncryptionConfig) (Encryption, hcl.D
for _, remoteTarget := range cfg.Remote.Targets {
// TODO the addr here should be generated in one place.
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...)
}
}

View File

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

View File

@ -9,6 +9,7 @@ import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/encryption"
"github.com/opentofu/opentofu/internal/encryption/config"
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
@ -57,8 +58,11 @@ func Example() {
// Merge the configurations
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
enc, diags := encryption.New(reg, cfg)
enc, diags := encryption.New(reg, cfg, staticEval)
handleDiags(diags)
sfe := enc.State()

View File

@ -10,7 +10,10 @@ import (
"errors"
"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/lang"
"github.com/hashicorp/hcl/v2"
"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)
if err != nil {
if errors.Is(err, &registry.KeyProviderNotFoundError{}) {
return hcl.Diagnostics{&hcl.Diagnostic{
return diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Unknown key_provider type",
Detail: fmt.Sprintf("Can not find %q", cfg.Type),
}}
})
}
return hcl.Diagnostics{&hcl.Diagnostic{
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
@ -106,21 +109,21 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
keyProviderConfig := keyProviderDescriptor.ConfigStruct()
// 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() {
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 {
// Key Provider references should be in the form key_provider.type.name
if len(dep) != 3 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid key_provider reference",
Detail: "Expected reference in form key_provider.type.name",
Subject: dep.SourceRange().Ptr(),
})
nonKeyProviderDeps = append(nonKeyProviderDeps, dep)
continue
}
@ -130,23 +133,23 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
depName := (dep[2].(hcl.TraverseAttr)).Name
if depRoot != "key_provider" {
nonKeyProviderDeps = append(nonKeyProviderDeps, dep)
continue
}
kpc, ok := e.cfg.GetKeyProvider(depType, depName)
if !ok {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid key_provider reference",
Detail: "Expected reference in form key_provider.type.name",
Summary: "Undefined Key Provider",
Detail: fmt.Sprintf("Key provider %s.%s is missing from the encryption configuration.", depType, depName),
Subject: dep.SourceRange().Ptr(),
})
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)
diags = append(diags, depDiags...)
break
}
}
}
if diags.HasErrors() {
// 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
}
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
decodeDiags := gohcl.DecodeBody(cfg.Body, e.ctx, keyProviderConfig)
decodeDiags := gohcl.DecodeBody(cfg.Body, evalCtx, keyProviderConfig)
diags = append(diags, decodeDiags...)
if diags.HasErrors() {
return diags

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ import (
"github.com/hashicorp/hcl/v2"
"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/keyprovider"
"github.com/opentofu/opentofu/internal/encryption/method"
@ -31,6 +32,7 @@ type targetBuilder struct {
keyValues map[string]map[string]cty.Value
methodValues map[string]map[string]cty.Value
methods map[method.Addr]method.Method
staticEval *configs.StaticEvaluator
}
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,
reg: base.enc.reg,
staticEval: base.staticEval,
ctx: &hcl.EvalContext{
Variables: map[string]cty.Value{},
},

View File

@ -10,6 +10,8 @@ import (
"testing"
"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/keyprovider"
"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/registry"
"github.com/opentofu/opentofu/internal/encryption/registry/lockingencryptionregistry"
"github.com/zclconf/go-cty/cty"
)
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.",
},
"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()
@ -125,8 +158,26 @@ func TestBaseEncryption_buildTargetMethods(t *testing.T) {
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 {
t.Run(name, test.newTestRun(reg))
t.Run(name, test.newTestRun(reg, staticEval))
}
}
@ -136,7 +187,7 @@ type btmTestCase struct {
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) {
t.Parallel()
@ -154,6 +205,7 @@ func (testCase btmTestCase) newTestRun(reg registry.Registry) func(t *testing.T)
enforced: cfg.State.Enforced,
name: "test",
encMeta: make(map[keyprovider.Addr][]byte),
staticEval: staticEval,
}
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
// reference with the "self" address overridden, and then pull the "self"
// 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)
val := ctx.Variables["self"]
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
// caller will handle the evaluation calls itself.
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 {
panic("attempt to construct EvalContext for nil Scope")
}
var diags tfdiags.Diagnostics
vals := make(map[string]cty.Value)
funcs := make(map[string]function.Function)
ctx := &hcl.EvalContext{
Variables: vals,
Functions: funcs,
}
// Calling NewChild() on a nil parent will
// produce an EvalContext with no parent.
ctx := parent.NewChild()
ctx.Variables = vals
ctx.Functions = funcs
for name, fn := range s.Functions() {
funcs[name] = fn
}

View File

@ -8,6 +8,7 @@ package lang
import (
"bytes"
"encoding/json"
"reflect"
"testing"
"github.com/opentofu/opentofu/internal/addrs"
@ -19,6 +20,7 @@ import (
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
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) {
nestedObjTy := cty.Object(map[string]cty.Type{
"boop": cty.String,

View File

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