From 9c580335e303610f96e01cfa8859ea6ee78ea87e Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 16 Oct 2020 14:52:31 -0400 Subject: [PATCH] terraform: Unmark provisioner arguments If provisioner configuration or connection info includes sensitive values, we need to unmark them before calling the provisioner. Failing to do so causes serialization to error. Unlike resources, we do not need to capture marked paths here, so we just discard the marks. --- terraform/context_apply_test.go | 55 +++++++++++++++++++ terraform/eval_apply.go | 11 +++- terraform/terraform_test.go | 7 +++ .../apply-provisioner-sensitive/main.tf | 18 ++++++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 terraform/testdata/apply-provisioner-sensitive/main.tf 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" + } +}