From b9eeba0da2a2b5f388c28b2ee3d4a81770c16ef2 Mon Sep 17 00:00:00 2001 From: Pam Selle <204372+pselle@users.noreply.github.com> Date: Tue, 6 Oct 2020 12:53:49 -0400 Subject: [PATCH 1/2] Consider sensitivity when evaluating module outputs This change "marks" values related to outputs that have Sensitive set to true. --- terraform/evaluate.go | 8 +++ terraform/evaluate_test.go | 107 +++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/terraform/evaluate.go b/terraform/evaluate.go index 0647bb68e..9d3e565b4 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -426,6 +426,10 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc } instance[cfg.Name] = outputState + + if cfg.Sensitive { + instance[cfg.Name] = outputState.Mark("sensitive") + } } // any pending changes override the state state values @@ -451,6 +455,10 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc } instance[cfg.Name] = change.After + + if change.Sensitive { + instance[cfg.Name] = change.After.Mark("sensitive") + } } } diff --git a/terraform/evaluate_test.go b/terraform/evaluate_test.go index d5b13893e..3957d283e 100644 --- a/terraform/evaluate_test.go +++ b/terraform/evaluate_test.go @@ -9,6 +9,8 @@ import ( "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/tfdiags" ) @@ -121,3 +123,108 @@ func TestEvaluatorGetInputVariable(t *testing.T) { t.Errorf("wrong result %#v; want %#v", got, want) } } + +func TestEvaluatorGetModule(t *testing.T) { + // Create a new evaluator with an existing state + stateSync := states.BuildState(func(ss *states.SyncState) { + ss.SetOutputValue( + addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}), + cty.StringVal("bar"), + true, + ) + }).SyncWrapper() + evaluator := evaluatorForModule(stateSync, plans.NewChanges().SyncWrapper()) + data := &evaluationStateData{ + Evaluator: evaluator, + } + scope := evaluator.Scope(data, nil) + want := cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("bar").Mark("sensitive")}) + got, diags := scope.Data.GetModule(addrs.ModuleCall{ + Name: "mod", + }, tfdiags.SourceRange{}) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) + } + if !got.RawEquals(want) { + t.Errorf("wrong result %#v; want %#v", got, want) + } + + // Changes should override the state value + changesSync := plans.NewChanges().SyncWrapper() + change := &plans.OutputChange{ + Addr: addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}), + Sensitive: true, + Change: plans.Change{ + After: cty.StringVal("baz"), + }, + } + cs, _ := change.Encode() + changesSync.AppendOutputChange(cs) + evaluator = evaluatorForModule(stateSync, changesSync) + data = &evaluationStateData{ + Evaluator: evaluator, + } + scope = evaluator.Scope(data, nil) + want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark("sensitive")}) + got, diags = scope.Data.GetModule(addrs.ModuleCall{ + Name: "mod", + }, tfdiags.SourceRange{}) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) + } + if !got.RawEquals(want) { + t.Errorf("wrong result %#v; want %#v", got, want) + } + + // Test changes with empty state + evaluator = evaluatorForModule(states.NewState().SyncWrapper(), changesSync) + data = &evaluationStateData{ + Evaluator: evaluator, + } + scope = evaluator.Scope(data, nil) + want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark("sensitive")}) + got, diags = scope.Data.GetModule(addrs.ModuleCall{ + Name: "mod", + }, tfdiags.SourceRange{}) + + if len(diags) != 0 { + t.Errorf("unexpected diagnostics %s", spew.Sdump(diags)) + } + if !got.RawEquals(want) { + t.Errorf("wrong result %#v; want %#v", got, want) + } +} + +func evaluatorForModule(stateSync *states.SyncState, changesSync *plans.ChangesSync) *Evaluator { + return &Evaluator{ + Meta: &ContextMeta{ + Env: "foo", + }, + Config: &configs.Config{ + Module: &configs.Module{ + ModuleCalls: map[string]*configs.ModuleCall{ + "mod": { + Name: "mod", + }, + }, + }, + Children: map[string]*configs.Config{ + "mod": { + Path: addrs.Module{"module.mod"}, + Module: &configs.Module{ + Outputs: map[string]*configs.Output{ + "out": { + Name: "out", + Sensitive: true, + }, + }, + }, + }, + }, + }, + State: stateSync, + Changes: changesSync, + } +} From 9a9e61ef06ff020b89b2b8714700c7d761fe8160 Mon Sep 17 00:00:00 2001 From: Pam Selle <204372+pselle@users.noreply.github.com> Date: Tue, 6 Oct 2020 14:14:29 -0400 Subject: [PATCH 2/2] Update docs for output sensitivity change --- website/docs/configuration/outputs.html.md | 51 ++++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/website/docs/configuration/outputs.html.md b/website/docs/configuration/outputs.html.md index 2ab93e4a1..01c89acfd 100644 --- a/website/docs/configuration/outputs.html.md +++ b/website/docs/configuration/outputs.html.md @@ -98,10 +98,53 @@ output "db_password" { } ``` -Setting an output value in the root module as sensitive prevents Terraform -from showing its value in the list of outputs at the end of `terraform apply`. -It might still be shown in the CLI output for other reasons, like if the -value is referenced in an expression for a resource argument. +Setting an output value as sensitive prevents Terraform from showing its value +in `plan` and `apply`. In the following scenario, our root module has an output declared as sensitive +and a module call with a sensitive output, which we then use in a resource attribute. + +```hcl +# main.tf + +module "foo" { + source = "./mod" +} + +resource "test_instance" "x" { + some_attribute = module.mod.a # resource attribute references a sensitive output +} + +output "out" { + value = "xyz" + sensitive = true +} + +# mod/main.tf, our module containing a sensitive output + +output "a" { + value = "secret" + sensitive = true" +} +``` + +When we run a `plan` or `apply`, the sensitive value is redacted from output: + +``` +# CLI output + +Terraform will perform the following actions: + + # test_instance.x will be created + + resource "test_instance" "x" { + + some_attribute = (sensitive) + } + +Plan: 1 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + out = (sensitive value) +``` + +-> **Note:** In Terraform versions prior to Terraform 0.14, setting an output value in the root module as sensitive would prevent Terraform from showing its value in the list of outputs at the end of `terraform apply`. However, the value could still display in the CLI output for other reasons, like if the value is referenced in an expression for a resource argument. Sensitive output values are still recorded in the [state](/docs/state/index.html), and so will be visible to anyone who is able