mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
add early validation for enforced encryption methods (#1711)
Signed-off-by: ollevche <ollevche@gmail.com>
This commit is contained in:
parent
ed0c761b0e
commit
568ff66bef
@ -13,6 +13,7 @@ ENHANCEMENTS:
|
||||
* Allow to reference variable inside the `variables` block of a test file. ([1488](https://github.com/opentofu/opentofu/pull/1488))
|
||||
|
||||
BUG FIXES:
|
||||
* Fixed validation for `enforced` flag in encryption configuration. ([#1711](https://github.com/opentofu/opentofu/pull/1711))
|
||||
* Fixed crash in gcs backend when using certain commands. ([#1618](https://github.com/opentofu/opentofu/pull/1618))
|
||||
* Fixed inmem backend crash due to missing struct field. ([#1619](https://github.com/opentofu/opentofu/pull/1619))
|
||||
* Added a check in the `tofu test` to validate that the names of test run blocks do not contain spaces. ([#1489](https://github.com/opentofu/opentofu/pull/1489))
|
||||
|
@ -95,10 +95,6 @@ func (s *baseEncryption) encrypt(data []byte, enhance func(basedata) interface{}
|
||||
encryptor := s.encMethods[0]
|
||||
|
||||
if unencrypted.Is(encryptor) {
|
||||
// ensure that the method is defined when Enforced is true
|
||||
if s.enforced {
|
||||
return nil, fmt.Errorf("unable to use unencrypted method for %q when enforced = true", s.name)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
@ -143,9 +139,6 @@ func (s *baseEncryption) decrypt(data []byte, validator func([]byte) error) ([]b
|
||||
// Yep, it's already decrypted
|
||||
for _, method := range s.encMethods {
|
||||
if unencrypted.Is(method) {
|
||||
if s.enforced {
|
||||
return nil, fmt.Errorf("unable to use unencrypted method when enforced = true")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
}
|
||||
|
@ -123,3 +123,8 @@ func (a aesgcm) getGCM(key []byte) (cipher.AEAD, error) {
|
||||
}
|
||||
return gcm, nil
|
||||
}
|
||||
|
||||
func Is(m method.Method) bool {
|
||||
_, ok := m.(*aesgcm)
|
||||
return ok
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/opentofu/opentofu/internal/encryption/config"
|
||||
"github.com/opentofu/opentofu/internal/encryption/keyprovider"
|
||||
"github.com/opentofu/opentofu/internal/encryption/method"
|
||||
"github.com/opentofu/opentofu/internal/encryption/method/unencrypted"
|
||||
"github.com/opentofu/opentofu/internal/encryption/registry"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
@ -58,7 +59,21 @@ func (base *baseEncryption) buildTargetMethods(meta map[keyprovider.Addr][]byte)
|
||||
}
|
||||
|
||||
methods, targetDiags := builder.build(base.target, base.name)
|
||||
return methods, append(diags, targetDiags...)
|
||||
diags = append(diags, targetDiags...)
|
||||
|
||||
if base.enforced {
|
||||
for _, m := range methods {
|
||||
if unencrypted.Is(m) {
|
||||
return nil, append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unencrypted method is forbidden",
|
||||
Detail: "Unable to use `unencrypted` method since the `enforced` flag is used.",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return methods, diags
|
||||
}
|
||||
|
||||
// build sets up a single target for encryption. It returns the primary and fallback methods for the target, as well
|
||||
|
190
internal/encryption/targets_test.go
Normal file
190
internal/encryption/targets_test.go
Normal file
@ -0,0 +1,190 @@
|
||||
// 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 (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/opentofu/opentofu/internal/encryption/config"
|
||||
"github.com/opentofu/opentofu/internal/encryption/keyprovider"
|
||||
"github.com/opentofu/opentofu/internal/encryption/keyprovider/static"
|
||||
"github.com/opentofu/opentofu/internal/encryption/method"
|
||||
"github.com/opentofu/opentofu/internal/encryption/method/aesgcm"
|
||||
"github.com/opentofu/opentofu/internal/encryption/method/unencrypted"
|
||||
"github.com/opentofu/opentofu/internal/encryption/registry"
|
||||
"github.com/opentofu/opentofu/internal/encryption/registry/lockingencryptionregistry"
|
||||
)
|
||||
|
||||
func TestBaseEncryption_buildTargetMethods(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := map[string]btmTestCase{
|
||||
"simple": {
|
||||
rawConfig: `
|
||||
key_provider "static" "basic" {
|
||||
key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
|
||||
}
|
||||
method "aes_gcm" "example" {
|
||||
keys = key_provider.static.basic
|
||||
}
|
||||
state {
|
||||
method = method.aes_gcm.example
|
||||
}
|
||||
`,
|
||||
wantMethods: []func(method.Method) bool{
|
||||
aesgcm.Is,
|
||||
},
|
||||
},
|
||||
"no-key-provider": {
|
||||
rawConfig: `
|
||||
method "aes_gcm" "example" {
|
||||
keys = key_provider.static.basic
|
||||
}
|
||||
state {
|
||||
method = method.aes_gcm.example
|
||||
}
|
||||
`,
|
||||
wantErr: `Test Config Source:3,25-32: Unsupported attribute; This object does not have an attribute named "static".`,
|
||||
},
|
||||
"fallback": {
|
||||
rawConfig: `
|
||||
key_provider "static" "basic" {
|
||||
key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
|
||||
}
|
||||
method "aes_gcm" "example" {
|
||||
keys = key_provider.static.basic
|
||||
}
|
||||
method "unencrypted" "example" {
|
||||
}
|
||||
state {
|
||||
method = method.aes_gcm.example
|
||||
fallback {
|
||||
method = method.unencrypted.example
|
||||
}
|
||||
}
|
||||
`,
|
||||
wantMethods: []func(method.Method) bool{
|
||||
aesgcm.Is,
|
||||
unencrypted.Is,
|
||||
},
|
||||
},
|
||||
"enforced": {
|
||||
rawConfig: `
|
||||
key_provider "static" "basic" {
|
||||
key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
|
||||
}
|
||||
method "aes_gcm" "example" {
|
||||
keys = key_provider.static.basic
|
||||
}
|
||||
method "unencrypted" "example" {
|
||||
}
|
||||
state {
|
||||
enforced = true
|
||||
method = method.aes_gcm.example
|
||||
}
|
||||
`,
|
||||
wantMethods: []func(method.Method) bool{
|
||||
aesgcm.Is,
|
||||
},
|
||||
},
|
||||
"enforced-with-unencrypted": {
|
||||
rawConfig: `
|
||||
key_provider "static" "basic" {
|
||||
key = "6f6f706830656f67686f6834616872756f3751756165686565796f6f72653169"
|
||||
}
|
||||
method "aes_gcm" "example" {
|
||||
keys = key_provider.static.basic
|
||||
}
|
||||
method "unencrypted" "example" {
|
||||
}
|
||||
state {
|
||||
enforced = true
|
||||
method = method.aes_gcm.example
|
||||
fallback {
|
||||
method = method.unencrypted.example
|
||||
}
|
||||
}
|
||||
`,
|
||||
wantErr: "<nil>: Unencrypted method is forbidden; Unable to use `unencrypted` method since the `enforced` flag is used.",
|
||||
},
|
||||
}
|
||||
|
||||
reg := lockingencryptionregistry.New()
|
||||
if err := reg.RegisterKeyProvider(static.New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := reg.RegisterMethod(aesgcm.New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := reg.RegisterMethod(unencrypted.New()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
t.Run(name, test.newTestRun(reg))
|
||||
}
|
||||
}
|
||||
|
||||
type btmTestCase struct {
|
||||
rawConfig string // must contain state target
|
||||
wantMethods []func(method.Method) bool
|
||||
wantErr string
|
||||
}
|
||||
|
||||
func (testCase btmTestCase) newTestRun(reg registry.Registry) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg, diags := config.LoadConfigFromString("Test Config Source", testCase.rawConfig)
|
||||
if diags.HasErrors() {
|
||||
panic(diags.Error())
|
||||
}
|
||||
|
||||
base := &baseEncryption{
|
||||
enc: &encryption{
|
||||
cfg: cfg,
|
||||
reg: reg,
|
||||
},
|
||||
target: cfg.State.AsTargetConfig(),
|
||||
enforced: cfg.State.Enforced,
|
||||
name: "test",
|
||||
encMeta: make(map[keyprovider.Addr][]byte),
|
||||
}
|
||||
|
||||
methods, diags := base.buildTargetMethods(base.encMeta)
|
||||
|
||||
if diags.HasErrors() {
|
||||
if !hasDiagWithMsg(diags, testCase.wantErr) {
|
||||
t.Fatalf("Got unexpected error: %v", diags.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if !diags.HasErrors() && testCase.wantErr != "" {
|
||||
t.Fatalf("Expected error (got none): %v", testCase.wantErr)
|
||||
}
|
||||
|
||||
if len(methods) != len(testCase.wantMethods) {
|
||||
t.Fatalf("Expected %d method(s), got %d", len(testCase.wantMethods), len(methods))
|
||||
}
|
||||
|
||||
for i, m := range methods {
|
||||
if !testCase.wantMethods[i](m) {
|
||||
t.Fatalf("Got unexpected method: %v", reflect.TypeOf(m).String())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasDiagWithMsg(diags hcl.Diagnostics, msg string) bool {
|
||||
for _, d := range diags {
|
||||
if d.Error() == msg {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue
Block a user