Merge pull request #26611 from hashicorp/alisdair/sensitive-values-provisioners

Fixes for sensitive values used as input to provisioners
This commit is contained in:
Alisdair McDiarmid 2020-10-19 13:39:18 -04:00 committed by GitHub
commit 5e047b0a0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 2 deletions

View File

@ -4098,11 +4098,14 @@ func TestContext2Apply_Provisioner_compute(t *testing.T) {
if val != "computed_value" {
t.Fatalf("bad value for foo: %q", val)
}
req.UIOutput.Output(fmt.Sprintf("Executing: %q", val))
return
}
h := new(MockHook)
ctx := testContext2(t, &ContextOpts{
Config: m,
Hooks: []Hook{h},
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
@ -4136,6 +4139,14 @@ func TestContext2Apply_Provisioner_compute(t *testing.T) {
if !pr.ProvisionResourceCalled {
t.Fatalf("provisioner not invoked")
}
// Verify output was rendered
if !h.ProvisionOutputCalled {
t.Fatalf("ProvisionOutput hook not called")
}
if got, want := h.ProvisionOutputMessage, `Executing: "computed_value"`; got != want {
t.Errorf("expected output to be %q, but was %q", want, got)
}
}
func TestContext2Apply_provisionerCreateFail(t *testing.T) {
@ -12052,3 +12063,73 @@ output "out" {
t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, want)
}
}
func TestContext2Apply_provisionerSensitive(t *testing.T) {
m := testModule(t, "apply-provisioner-sensitive")
p := testProvider("aws")
pr := testProvisioner()
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
if req.Config.ContainsMarked() {
t.Fatalf("unexpectedly marked config value: %#v", req.Config)
}
command := req.Config.GetAttr("command")
if command.IsMarked() {
t.Fatalf("unexpectedly marked command argument: %#v", command.Marks())
}
req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString()))
return
}
p.ApplyResourceChangeFn = testApplyFn
p.PlanResourceChangeFn = testDiffFn
h := new(MockHook)
ctx := testContext2(t, &ContextOpts{
Config: m,
Hooks: []Hook{h},
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
Provisioners: map[string]ProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
Variables: InputValues{
"password": &InputValue{
Value: cty.StringVal("secret"),
SourceType: ValueFromCaller,
},
},
})
if _, diags := ctx.Plan(); diags.HasErrors() {
logDiagnostics(t, diags)
t.Fatal("plan failed")
}
state, diags := ctx.Apply()
if diags.HasErrors() {
logDiagnostics(t, diags)
t.Fatal("apply failed")
}
actual := strings.TrimSpace(state.String())
expected := strings.TrimSpace(testTerraformApplyProvisionerSensitiveStr)
if actual != expected {
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
}
// Verify apply was invoked
if !pr.ProvisionResourceCalled {
t.Fatalf("provisioner was not called on apply")
}
// Verify output was suppressed
if !h.ProvisionOutputCalled {
t.Fatalf("ProvisionOutput hook not called")
}
if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) {
t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got)
}
if got, want := h.ProvisionOutputMessage, "output suppressed"; !strings.Contains(got, want) {
t.Errorf("expected hook to be called with %q, but was:\n%s", want, got)
}
}

View File

@ -683,10 +683,30 @@ func (n *EvalApplyProvisioners) apply(ctx EvalContext, provs []*configs.Provisio
})
}
// If our config or connection info contains any marked values, ensure
// those are stripped out before sending to the provisioner. Unlike
// resources, we have no need to capture the marked paths and reapply
// later.
unmarkedConfig, configMarks := config.UnmarkDeep()
unmarkedConnInfo, _ := connInfo.UnmarkDeep()
// Marks on the config might result in leaking sensitive values through
// provisioner logging, so we conservatively suppress all output in
// this case. This should not apply to connection info values, which
// provisioners ought not to be logging anyway.
if len(configMarks) > 0 {
outputFn = func(msg string) {
ctx.Hook(func(h Hook) (HookAction, error) {
h.ProvisionOutput(absAddr, prov.Type, "(output suppressed due to sensitive value in config)")
return HookActionContinue, nil
})
}
}
output := CallbackUIOutput{OutputFn: outputFn}
resp := provisioner.ProvisionResource(provisioners.ProvisionResourceRequest{
Config: config,
Connection: connInfo,
Config: unmarkedConfig,
Connection: unmarkedConnInfo,
UIOutput: &output,
})
applyDiags := resp.Diagnostics.InConfigBody(prov.Config)

View File

@ -866,6 +866,13 @@ aws_instance.bar:
type = aws_instance
`
const testTerraformApplyProvisionerSensitiveStr = `
aws_instance.foo:
ID = foo
provider = provider["registry.terraform.io/hashicorp/aws"]
type = aws_instance
`
const testTerraformApplyDestroyStr = `
<no state>
`

View File

@ -0,0 +1,18 @@
variable "password" {
type = string
sensitive = true
}
resource "aws_instance" "foo" {
connection {
host = "localhost"
type = "telnet"
user = "superuser"
port = 2222
password = var.password
}
provisioner "shell" {
command = "echo ${var.password} > secrets"
}
}

View File

@ -186,6 +186,10 @@ that resource's attributes. For example, use `self.public_ip` to reference an
references create dependencies. Referring to a resource by name within its own
block would create a dependency cycle.
## Suppressing Provisioner Logs in CLI Output
The configuration for a `provisioner` block may use sensitive values, such as [`sensitive` variables](../configuration/variables.html#suppressing-values-in-cli-output) or [`sensitive` output values](../outputs.html#sensitive-suppressing-values-in-cli-output). In this case, all log output from the provider is automatically suppressed to prevent the sensitive values from being displayed.
## Creation-Time Provisioners
By default, provisioners run when the resource they are defined within is