Skip to content

Commit 7726342

Browse files
author
Liam Cervante
authored
Hide ephemeral values from provisioner output (#36427)
1 parent 16a34fe commit 7726342

File tree

5 files changed

+157
-63
lines changed

5 files changed

+157
-63
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'ephemeral values: correct error message when ephemeral values are included in provisioner output'
3+
time: 2025-02-05T09:52:31.116553+01:00
4+
custom:
5+
Issue: "36427"

internal/terraform/context_apply_test.go

Lines changed: 96 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -12464,76 +12464,110 @@ output "out" {
1246412464
}
1246512465
}
1246612466

12467-
func TestContext2Apply_provisionerSensitive(t *testing.T) {
12468-
m := testModule(t, "apply-provisioner-sensitive")
12469-
p := testProvider("aws")
12470-
12471-
pr := testProvisioner()
12472-
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
12473-
if req.Config.ContainsMarked() {
12474-
t.Fatalf("unexpectedly marked config value: %#v", req.Config)
12475-
}
12476-
command := req.Config.GetAttr("command")
12477-
if command.IsMarked() {
12478-
t.Fatalf("unexpectedly marked command argument: %#v", command.Marks())
12479-
}
12480-
req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString()))
12481-
return
12482-
}
12483-
p.PlanResourceChangeFn = testDiffFn
12484-
p.ApplyResourceChangeFn = testApplyFn
12485-
12486-
h := new(MockHook)
12487-
ctx := testContext2(t, &ContextOpts{
12488-
Hooks: []Hook{h},
12489-
Providers: map[addrs.Provider]providers.Factory{
12490-
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
12491-
},
12492-
Provisioners: map[string]provisioners.Factory{
12493-
"shell": testProvisionerFuncFixed(pr),
12467+
func TestContext2Apply_provisionerMarks(t *testing.T) {
12468+
tcs := map[string]struct {
12469+
opts *ApplyOpts
12470+
want string
12471+
}{
12472+
"apply-provisioner-sensitive": {
12473+
want: "output suppressed due to sensitive value",
12474+
},
12475+
"apply-provisioner-sensitive-ephemeral": {
12476+
want: "output suppressed due to sensitive, ephemeral value",
12477+
opts: &ApplyOpts{
12478+
SetVariables: InputValues{
12479+
"password": &InputValue{
12480+
Value: cty.StringVal("secret"),
12481+
SourceType: ValueFromCaller,
12482+
},
12483+
},
12484+
},
1249412485
},
12495-
})
12496-
12497-
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
12498-
Mode: plans.NormalMode,
12499-
SetVariables: InputValues{
12500-
"password": &InputValue{
12501-
Value: cty.StringVal("secret"),
12502-
SourceType: ValueFromCaller,
12486+
"apply-provisioner-ephemeral": {
12487+
want: "output suppressed due to ephemeral value",
12488+
opts: &ApplyOpts{
12489+
SetVariables: InputValues{
12490+
"password": &InputValue{
12491+
Value: cty.StringVal("secret"),
12492+
SourceType: ValueFromCaller,
12493+
},
12494+
},
1250312495
},
1250412496
},
12505-
})
12506-
assertNoErrors(t, diags)
12497+
}
12498+
for name, tc := range tcs {
12499+
t.Run(name, func(t *testing.T) {
12500+
m := testModule(t, name)
12501+
p := testProvider("aws")
12502+
12503+
pr := testProvisioner()
12504+
pr.ProvisionResourceFn = func(req provisioners.ProvisionResourceRequest) (resp provisioners.ProvisionResourceResponse) {
12505+
if req.Config.ContainsMarked() {
12506+
t.Fatalf("unexpectedly marked config value: %#v", req.Config)
12507+
}
12508+
command := req.Config.GetAttr("command")
12509+
if command.IsMarked() {
12510+
t.Fatalf("unexpectedly marked command argument: %#v", command.Marks())
12511+
}
12512+
req.UIOutput.Output(fmt.Sprintf("Executing: %q", command.AsString()))
12513+
return
12514+
}
12515+
p.PlanResourceChangeFn = testDiffFn
12516+
p.ApplyResourceChangeFn = testApplyFn
1250712517

12508-
// "restart" provisioner
12509-
pr.CloseCalled = false
12518+
h := new(MockHook)
12519+
ctx := testContext2(t, &ContextOpts{
12520+
Hooks: []Hook{h},
12521+
Providers: map[addrs.Provider]providers.Factory{
12522+
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
12523+
},
12524+
Provisioners: map[string]provisioners.Factory{
12525+
"shell": testProvisionerFuncFixed(pr),
12526+
},
12527+
})
1251012528

12511-
state, diags := ctx.Apply(plan, m, nil)
12512-
if diags.HasErrors() {
12513-
logDiagnostics(t, diags)
12514-
t.Fatal("apply failed")
12515-
}
12529+
plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
12530+
Mode: plans.NormalMode,
12531+
SetVariables: InputValues{
12532+
"password": &InputValue{
12533+
Value: cty.StringVal("secret"),
12534+
SourceType: ValueFromCaller,
12535+
},
12536+
},
12537+
})
12538+
assertNoErrors(t, diags)
1251612539

12517-
actual := strings.TrimSpace(state.String())
12518-
expected := strings.TrimSpace(testTerraformApplyProvisionerSensitiveStr)
12519-
if actual != expected {
12520-
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
12521-
}
12540+
// "restart" provisioner
12541+
pr.CloseCalled = false
1252212542

12523-
// Verify apply was invoked
12524-
if !pr.ProvisionResourceCalled {
12525-
t.Fatalf("provisioner was not called on apply")
12526-
}
12543+
state, diags := ctx.Apply(plan, m, tc.opts)
12544+
if diags.HasErrors() {
12545+
logDiagnostics(t, diags)
12546+
t.Fatal("apply failed")
12547+
}
1252712548

12528-
// Verify output was suppressed
12529-
if !h.ProvisionOutputCalled {
12530-
t.Fatalf("ProvisionOutput hook not called")
12531-
}
12532-
if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) {
12533-
t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got)
12534-
}
12535-
if got, want := h.ProvisionOutputMessage, "output suppressed"; !strings.Contains(got, want) {
12536-
t.Errorf("expected hook to be called with %q, but was:\n%s", want, got)
12549+
actual := strings.TrimSpace(state.String())
12550+
expected := strings.TrimSpace(testTerraformApplyProvisionerSensitiveStr)
12551+
if actual != expected {
12552+
t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
12553+
}
12554+
12555+
// Verify apply was invoked
12556+
if !pr.ProvisionResourceCalled {
12557+
t.Fatalf("provisioner was not called on apply")
12558+
}
12559+
12560+
// Verify output was suppressed
12561+
if !h.ProvisionOutputCalled {
12562+
t.Fatalf("ProvisionOutput hook not called")
12563+
}
12564+
if got, doNotWant := h.ProvisionOutputMessage, "secret"; strings.Contains(got, doNotWant) {
12565+
t.Errorf("sensitive value %q included in output:\n%s", doNotWant, got)
12566+
}
12567+
if got, want := h.ProvisionOutputMessage, tc.want; !strings.Contains(got, want) {
12568+
t.Errorf("expected hook to be called with %q, but was:\n%s", want, got)
12569+
}
12570+
})
1253712571
}
1253812572
}
1253912573

internal/terraform/node_resource_abstract_instance.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2351,13 +2351,31 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
23512351
// provisioner logging, so we conservatively suppress all output in
23522352
// this case. This should not apply to connection info values, which
23532353
// provisioners ought not to be logging anyway.
2354-
if len(configMarks) > 0 {
2354+
_, sensitive := configMarks[marks.Sensitive]
2355+
_, ephemeral := configMarks[marks.Ephemeral]
2356+
2357+
switch {
2358+
case sensitive && ephemeral:
2359+
outputFn = func(msg string) {
2360+
ctx.Hook(func(h Hook) (HookAction, error) {
2361+
h.ProvisionOutput(n.HookResourceIdentity(), prov.Type, "(output suppressed due to sensitive, ephemeral value in config)")
2362+
return HookActionContinue, nil
2363+
})
2364+
}
2365+
case sensitive:
23552366
outputFn = func(msg string) {
23562367
ctx.Hook(func(h Hook) (HookAction, error) {
23572368
h.ProvisionOutput(n.HookResourceIdentity(), prov.Type, "(output suppressed due to sensitive value in config)")
23582369
return HookActionContinue, nil
23592370
})
23602371
}
2372+
case ephemeral:
2373+
outputFn = func(msg string) {
2374+
ctx.Hook(func(h Hook) (HookAction, error) {
2375+
h.ProvisionOutput(n.HookResourceIdentity(), prov.Type, "(output suppressed due to ephemeral value in config)")
2376+
return HookActionContinue, nil
2377+
})
2378+
}
23612379
}
23622380

23632381
output := CallbackUIOutput{OutputFn: outputFn}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
variable "password" {
2+
type = string
3+
ephemeral = true
4+
}
5+
6+
resource "aws_instance" "foo" {
7+
connection {
8+
host = "localhost"
9+
type = "telnet"
10+
user = "superuser"
11+
port = 2222
12+
password = "password"
13+
}
14+
15+
provisioner "shell" {
16+
command = "echo ${var.password} > secrets"
17+
}
18+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
variable "password" {
2+
type = string
3+
ephemeral = true
4+
sensitive = true
5+
}
6+
7+
resource "aws_instance" "foo" {
8+
connection {
9+
host = "localhost"
10+
type = "telnet"
11+
user = "superuser"
12+
port = 2222
13+
password = "password"
14+
}
15+
16+
provisioner "shell" {
17+
command = "echo ${var.password} > secrets"
18+
}
19+
}

0 commit comments

Comments
 (0)