Handle static variable secret flag (#2045)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-10-03 10:46:58 -04:00 committed by GitHub
parent 8638b815dc
commit 0d1e6cd5f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 95 additions and 20 deletions

View File

@ -1,8 +1,18 @@
variable "passphrase" {
type = string
default = "26281afb-83f1-47ec-9b2d-2aebf6417167"
sensitive = true
}
locals {
key_length = sensitive(32)
}
terraform { terraform {
encryption { encryption {
key_provider "pbkdf2" "basic" { key_provider "pbkdf2" "basic" {
passphrase = "26281afb-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

@ -13,6 +13,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs/configschema" "github.com/opentofu/opentofu/internal/configs/configschema"
"github.com/opentofu/opentofu/internal/lang" "github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
) )
@ -62,6 +63,15 @@ func (b *Backend) Hash(schema *configschema.Block) (int, hcl.Diagnostics) {
val = cty.UnknownVal(schema.ImpliedType()) val = cty.UnknownVal(schema.ImpliedType())
} }
if marks.Contains(val, marks.Sensitive) {
return -1, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Backend config contains sensitive values",
Detail: "The backend configuration is stored in .terraform/terraform.tfstate as well as plan files. It is recommended to instead supply sensitive credentials via backend specific environment variables",
Subject: b.DeclRange.Ptr(),
})
}
toHash := cty.TupleVal([]cty.Value{ toHash := cty.TupleVal([]cty.Value{
cty.StringVal(b.Type), cty.StringVal(b.Type),
val, val,

View File

@ -6,11 +6,14 @@
package configs package configs
import ( import (
"fmt"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/hcl/v2/hcldec"
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/lang" "github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
) )
@ -90,21 +93,21 @@ func (s StaticEvaluator) Evaluate(expr hcl.Expression, ident StaticIdentifier) (
} }
func (s StaticEvaluator) DecodeExpression(expr hcl.Expression, ident StaticIdentifier, val any) hcl.Diagnostics { func (s StaticEvaluator) DecodeExpression(expr hcl.Expression, ident StaticIdentifier, val any) hcl.Diagnostics {
var diags hcl.Diagnostics srcVal, diags := s.Evaluate(expr, ident)
refs, refsDiags := lang.ReferencesInExpr(addrs.ParseRef, expr)
diags = append(diags, refsDiags.ToHCL()...)
if diags.HasErrors() { if diags.HasErrors() {
return diags return diags
} }
ctx, ctxDiags := s.scope(ident).EvalContext(refs) if marks.Contains(srcVal, marks.Sensitive) {
diags = append(diags, ctxDiags.ToHCL()...) return diags.Append(&hcl.Diagnostic{
if diags.HasErrors() { Severity: hcl.DiagError,
return diags Summary: "Sensitive value not allowed",
Detail: fmt.Sprintf("Sensitive values, or values derived from sensitive values, cannot be used as %s.", ident.String()),
Subject: expr.Range().Ptr(),
})
} }
return gohcl.DecodeExpression(expr, ctx, val) return diags.Extend(gohcl.DecodeValue(srcVal, expr.StartRange(), expr.Range(), val))
} }
func (s StaticEvaluator) DecodeBlock(body hcl.Body, spec hcldec.Spec, ident StaticIdentifier) (cty.Value, hcl.Diagnostics) { func (s StaticEvaluator) DecodeBlock(body hcl.Body, spec hcldec.Spec, ident StaticIdentifier) (cty.Value, hcl.Diagnostics) {

View File

@ -321,8 +321,11 @@ func TestStaticEvaluator_DecodeExpression(t *testing.T) {
}{{ }{{
expr: `"static"`, expr: `"static"`,
}, { }, {
expr: `count`, expr: `count`,
diags: []string{`eval.tf:1,1-6: Invalid reference; The "count" object cannot be accessed directly. Instead, access one of its attributes.`}, diags: []string{
`eval.tf:1,1-6: Invalid reference; The "count" object cannot be accessed directly. Instead, access one of its attributes.`,
`:0,0-0: Dynamic value in static context; Unable to use count. in static context, which is required by local.test`,
},
}, { }, {
expr: `module.foo.bar`, expr: `module.foo.bar`,
diags: []string{`eval.tf:1,1-15: Module output not supported in static context; Unable to use module.foo.bar in static context, which is required by local.test`}, diags: []string{`eval.tf:1,1-15: Module output not supported in static context; Unable to use module.foo.bar in static context, which is required by local.test`},
@ -372,8 +375,28 @@ terraform {
} }
}`, }`,
diags: []string{`eval.tf:4,11-25: Module output not supported in static context; Unable to use module.foo.bar in static context, which is required by backend.badeval`}, diags: []string{`eval.tf:4,11-25: Module output not supported in static context; Unable to use module.foo.bar in static context, which is required by backend.badeval`},
}, {
ident: "sensitive",
body: `
locals {
sens = sensitive("magic")
}
terraform {
backend "sensitive" {
thing = local.sens
}
}`,
diags: []string{`eval.tf:6,2-21: Backend config contains sensitive values; The backend configuration is stored in .terraform/terraform.tfstate as well as plan files. It is recommended to instead supply sensitive credentials via backend specific environment variables`},
}} }}
schema := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"thing": &configschema.Attribute{
Type: cty.String,
},
},
}
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.ident, func(t *testing.T) { t.Run(tc.ident, func(t *testing.T) {
parser := testParser(map[string]string{"eval.tf": tc.body}) parser := testParser(map[string]string{"eval.tf": tc.body})
@ -383,13 +406,14 @@ terraform {
} }
mod, _ := NewModule([]*File{file}, nil, RootModuleCallForTesting(), "dir", SelectiveLoadAll) mod, _ := NewModule([]*File{file}, nil, RootModuleCallForTesting(), "dir", SelectiveLoadAll)
_, diags := mod.Backend.Decode(&configschema.Block{
Attributes: map[string]*configschema.Attribute{ _, diags := mod.Backend.Hash(schema)
"thing": &configschema.Attribute{ if diags.HasErrors() {
Type: cty.String, assertExactDiagnostics(t, diags, tc.diags)
}, return
}, }
})
_, diags = mod.Backend.Decode(schema)
assertExactDiagnostics(t, diags, tc.diags) assertExactDiagnostics(t, diags, tc.diags)
}) })

View File

@ -16,6 +16,7 @@ import (
"github.com/opentofu/opentofu/internal/addrs" "github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/didyoumean" "github.com/opentofu/opentofu/internal/didyoumean"
"github.com/opentofu/opentofu/internal/lang" "github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/tfdiags" "github.com/opentofu/opentofu/internal/tfdiags"
) )
@ -256,6 +257,9 @@ func (s staticScopeData) GetInputVariable(ident addrs.InputVariable, rng tfdiags
} }
val, valDiags := s.eval.call.vars(variable) val, valDiags := s.eval.call.vars(variable)
if variable.Sensitive {
val = val.Mark(marks.Sensitive)
}
return val, s.enhanceDiagnostics(id, diags.Append(valDiags)) return val, s.enhanceDiagnostics(id, diags.Append(valDiags))
} }

View File

@ -0,0 +1,5 @@
module "test" {
source = sensitive("hostname/namespace/name/system") # ERROR: Sensitive value not allowed
version = sensitive("1.0.0") # ERROR: Invalid version constraint
}

View File

@ -10,6 +10,7 @@ import (
version "github.com/hashicorp/go-version" version "github.com/hashicorp/go-version"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/convert"
) )
@ -37,6 +38,15 @@ func decodeVersionConstraintValue(attr *hcl.Attribute, val cty.Value) (VersionCo
DeclRange: attr.Range, DeclRange: attr.Range,
} }
if val.HasMark(marks.Sensitive) {
return ret, diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid version constraint",
Detail: fmt.Sprintf("Sensitive values, or values derived from sensitive values, cannot be used as %s arguments.", attr.Name),
Subject: attr.Expr.Range().Ptr(),
})
}
var err error var err error
val, err = convert.Convert(val, cty.String) val, err = convert.Convert(val, cty.String)
if err != nil { if err != nil {

View File

@ -14,6 +14,7 @@ import (
"github.com/opentofu/opentofu/internal/configs" "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/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl" "github.com/hashicorp/hcl/v2/gohcl"
@ -187,6 +188,14 @@ func (e *targetBuilder) setupKeyProvider(cfg config.KeyProviderConfig, stack []c
return diags return diags
} }
// gohcl does not handle marks, we need to remove the sensitive marks from any input variables
// We assume that the entire configuration in the encryption block should be treated as sensitive
for key, sv := range evalCtx.Variables {
if marks.Contains(sv, marks.Sensitive) {
evalCtx.Variables[key], _ = sv.UnmarkDeep()
}
}
// Initialize the Key Provider // Initialize the Key Provider
decodeDiags := gohcl.DecodeBody(cfg.Body, evalCtx, keyProviderConfig) decodeDiags := gohcl.DecodeBody(cfg.Body, evalCtx, keyProviderConfig)
diags = append(diags, decodeDiags...) diags = append(diags, decodeDiags...)