diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index e36f48e65..e266540fd 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -12052,3 +12052,58 @@ 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()) + } + return + } + p.ApplyResourceChangeFn = testApplyFn + p.PlanResourceChangeFn = testDiffFn + ctx := testContext2(t, &ContextOpts{ + Config: m, + 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") + } +} diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index 5832fcd63..0b5e3d5cb 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -683,10 +683,17 @@ 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, _ := config.UnmarkDeep() + unmarkedConnInfo, _ := connInfo.UnmarkDeep() + 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) diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 00788f546..94e5a5113 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -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 = ` ` diff --git a/terraform/testdata/apply-provisioner-sensitive/main.tf b/terraform/testdata/apply-provisioner-sensitive/main.tf new file mode 100644 index 000000000..99ec4a290 --- /dev/null +++ b/terraform/testdata/apply-provisioner-sensitive/main.tf @@ -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" + } +}