Merge pull request #28472 from hashicorp/alisdair/submodule-outputs-inferred-sensitive
core: Loosen output value sensitivity requirement
This commit is contained in:
commit
389415ab18
|
@ -2,6 +2,7 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
|
@ -485,3 +486,32 @@ provider "test" {
|
||||||
t.Fatal(diags.Err())
|
t.Fatal(diags.Err())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext2Plan_invalidSensitiveModuleOutput(t *testing.T) {
|
||||||
|
m := testModuleInline(t, map[string]string{
|
||||||
|
"child/main.tf": `
|
||||||
|
output "out" {
|
||||||
|
value = sensitive("xyz")
|
||||||
|
}`,
|
||||||
|
"main.tf": `
|
||||||
|
module "child" {
|
||||||
|
source = "./child"
|
||||||
|
}
|
||||||
|
|
||||||
|
output "root" {
|
||||||
|
value = module.child.out
|
||||||
|
}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx := testContext2(t, &ContextOpts{
|
||||||
|
Config: m,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, diags := ctx.Plan()
|
||||||
|
if !diags.HasErrors() {
|
||||||
|
t.Fatal("succeeded; want errors")
|
||||||
|
}
|
||||||
|
if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
|
||||||
|
t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1379,7 +1379,7 @@ resource "aws_instance" "foo" {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContext2Validate_invalidSensitiveModuleOutput(t *testing.T) {
|
func TestContext2Validate_sensitiveRootModuleOutput(t *testing.T) {
|
||||||
m := testModuleInline(t, map[string]string{
|
m := testModuleInline(t, map[string]string{
|
||||||
"child/main.tf": `
|
"child/main.tf": `
|
||||||
variable "foo" {
|
variable "foo" {
|
||||||
|
@ -1395,27 +1395,19 @@ module "child" {
|
||||||
source = "./child"
|
source = "./child"
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
output "root" {
|
||||||
foo = module.child.out
|
value = module.child.out
|
||||||
|
sensitive = true
|
||||||
}`,
|
}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
p := testProvider("aws")
|
|
||||||
ctx := testContext2(t, &ContextOpts{
|
ctx := testContext2(t, &ContextOpts{
|
||||||
Config: m,
|
Config: m,
|
||||||
Providers: map[addrs.Provider]providers.Factory{
|
|
||||||
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
diags := ctx.Validate()
|
diags := ctx.Validate()
|
||||||
if !diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
t.Fatal("succeeded; want errors")
|
t.Fatal(diags.Err())
|
||||||
}
|
|
||||||
// Should get this error:
|
|
||||||
// Output refers to sensitive values: Expressions used in outputs can only refer to sensitive values if the sensitive attribute is true.
|
|
||||||
if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
|
|
||||||
t.Fatalf("wrong error:\ngot: %s\nwant: message containing %q", got, want)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -459,7 +459,7 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
instance[cfg.Name] = change.After
|
instance[cfg.Name] = change.After.MarkWithPaths(changeSrc.AfterValMarks)
|
||||||
|
|
||||||
if change.Sensitive && !change.After.HasMark("sensitive") {
|
if change.Sensitive && !change.After.HasMark("sensitive") {
|
||||||
instance[cfg.Name] = change.After.Mark("sensitive")
|
instance[cfg.Name] = change.After.Mark("sensitive")
|
||||||
|
|
|
@ -275,16 +275,24 @@ func (n *NodeApplyableOutput) Execute(ctx EvalContext, op walkOperation) (diags
|
||||||
// depends_on expressions here too
|
// depends_on expressions here too
|
||||||
diags = diags.Append(validateDependsOn(ctx, n.Config.DependsOn))
|
diags = diags.Append(validateDependsOn(ctx, n.Config.DependsOn))
|
||||||
|
|
||||||
// Ensure that non-sensitive outputs don't include sensitive values
|
// For root module outputs in particular, an output value must be
|
||||||
|
// statically declared as sensitive in order to dynamically return
|
||||||
|
// a sensitive result, to help avoid accidental exposure in the state
|
||||||
|
// of a sensitive value that the user doesn't want to include there.
|
||||||
_, marks := val.UnmarkDeep()
|
_, marks := val.UnmarkDeep()
|
||||||
_, hasSensitive := marks["sensitive"]
|
_, hasSensitive := marks["sensitive"]
|
||||||
if !n.Config.Sensitive && hasSensitive {
|
if n.Addr.Module.IsRoot() {
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
if !n.Config.Sensitive && hasSensitive {
|
||||||
Severity: hcl.DiagError,
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Summary: "Output refers to sensitive values",
|
Severity: hcl.DiagError,
|
||||||
Detail: "Expressions used in outputs can only refer to sensitive values if the sensitive attribute is true.",
|
Summary: "Output refers to sensitive values",
|
||||||
Subject: n.Config.DeclRange.Ptr(),
|
Detail: `To reduce the risk of accidentally exporting sensitive data that was intended to be only internal, Terraform requires that any root module output containing sensitive data be explicitly marked as sensitive, to confirm your intent.
|
||||||
})
|
|
||||||
|
If you do intend to export this data, annotate the output value as sensitive by adding the following argument:
|
||||||
|
sensitive = true`,
|
||||||
|
Subject: n.Config.DeclRange.Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,7 +462,7 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
|
||||||
sensitiveChange := sensitiveBefore || n.Config.Sensitive
|
sensitiveChange := sensitiveBefore || n.Config.Sensitive
|
||||||
|
|
||||||
// strip any marks here just to be sure we don't panic on the True comparison
|
// strip any marks here just to be sure we don't panic on the True comparison
|
||||||
val, _ = val.UnmarkDeep()
|
unmarkedVal, _ := val.UnmarkDeep()
|
||||||
|
|
||||||
action := plans.Update
|
action := plans.Update
|
||||||
switch {
|
switch {
|
||||||
|
@ -468,7 +476,7 @@ func (n *NodeApplyableOutput) setValue(state *states.SyncState, changes *plans.C
|
||||||
action = plans.Create
|
action = plans.Create
|
||||||
|
|
||||||
case val.IsWhollyKnown() &&
|
case val.IsWhollyKnown() &&
|
||||||
val.Equals(before).True() &&
|
unmarkedVal.Equals(before).True() &&
|
||||||
n.Config.Sensitive == sensitiveBefore:
|
n.Config.Sensitive == sensitiveBefore:
|
||||||
// Sensitivity must also match to be a NoOp.
|
// Sensitivity must also match to be a NoOp.
|
||||||
// Theoretically marks may not match here, but sensitivity is the
|
// Theoretically marks may not match here, but sensitivity is the
|
||||||
|
|
Loading…
Reference in New Issue