mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
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:
parent
8f8e0aa4aa
commit
19b5287b8f
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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...)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
//
|
||||
|
@ -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 {
|
||||
|
@ -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...)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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, ®istry.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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{},
|
||||
},
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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" {
|
||||
|
Loading…
Reference in New Issue
Block a user