Merge pull request #26491 from hashicorp/pselle/sensitive-vals-out-modules
Consider sensitivity when evaluating module outputs
This commit is contained in:
commit
7a7ad23113
|
@ -426,6 +426,10 @@ func (d *evaluationStateData) GetModule(addr addrs.ModuleCall, rng tfdiags.Sourc
|
||||||
}
|
}
|
||||||
|
|
||||||
instance[cfg.Name] = outputState
|
instance[cfg.Name] = outputState
|
||||||
|
|
||||||
|
if cfg.Sensitive {
|
||||||
|
instance[cfg.Name] = outputState.Mark("sensitive")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// any pending changes override the state state values
|
// 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
|
instance[cfg.Name] = change.After
|
||||||
|
|
||||||
|
if change.Sensitive {
|
||||||
|
instance[cfg.Name] = change.After.Mark("sensitive")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/addrs"
|
"github.com/hashicorp/terraform/addrs"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
|
"github.com/hashicorp/terraform/plans"
|
||||||
|
"github.com/hashicorp/terraform/states"
|
||||||
"github.com/hashicorp/terraform/tfdiags"
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -121,3 +123,108 @@ func TestEvaluatorGetInputVariable(t *testing.T) {
|
||||||
t.Errorf("wrong result %#v; want %#v", got, want)
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -98,10 +98,53 @@ output "db_password" {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Setting an output value in the root module as sensitive prevents Terraform
|
Setting an output value as sensitive prevents Terraform from showing its value
|
||||||
from showing its value in the list of outputs at the end of `terraform apply`.
|
in `plan` and `apply`. In the following scenario, our root module has an output declared as sensitive
|
||||||
It might still be shown in the CLI output for other reasons, like if the
|
and a module call with a sensitive output, which we then use in a resource attribute.
|
||||||
value is referenced in an expression for a resource argument.
|
|
||||||
|
```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
|
Sensitive output values are still recorded in the
|
||||||
[state](/docs/state/index.html), and so will be visible to anyone who is able
|
[state](/docs/state/index.html), and so will be visible to anyone who is able
|
||||||
|
|
Loading…
Reference in New Issue