stackruntime: Support sensitive component inputs

Components can emit sensitive values as outputs, which can be consumed
as inputs to other components. This commit ensures that such values are
correctly processed in order to pass their sensitivity to the modules
runtime.
alisdair/stacks-sensitive-component-outputs
Alisdair McDiarmid 2 years ago
parent fc75657113
commit 1d3f863f2b

@ -111,6 +111,8 @@ type PlannedChangeComponentInstance struct {
// with what's captured here.
PlannedInputValues map[string]plans.DynamicValue
PlannedInputValueMarks map[string][]cty.PathValueMarks
PlannedOutputValues map[string]cty.Value
// PlanTimestamp is the timestamp that would be returned from the
@ -128,20 +130,21 @@ func (pc *PlannedChangeComponentInstance) PlannedChangeProto() (*terraform1.Plan
if n := len(pc.PlannedInputValues); n != 0 {
plannedInputValues = make(map[string]*tfstackdata1.DynamicValue, n)
for k, v := range pc.PlannedInputValues {
var sensitivePaths []*planproto.Path
if pvm, ok := pc.PlannedInputValueMarks[k]; ok {
for _, p := range pvm {
path, err := planproto.NewPath(p.Path)
if err != nil {
return nil, err
}
sensitivePaths = append(sensitivePaths, path)
}
}
plannedInputValues[k] = &tfstackdata1.DynamicValue{
Value: &planproto.DynamicValue{
Msgpack: v,
},
// FIXME: We're currently losing track of sensitivity here --
// or, more accurately, in the caller that's populating
// pc.PlannedInputValues -- but that's not _super_ important
// because we don't directly use these values during the
// apply phase anyway, and instead recalculate the input
// values based on updated data from other components having
// already been applied. These values are here only to give
// us something to compare against as a safety check to catch
// if a bug somewhere causes the values to be inconsistent
// between plan and apply.
SensitivePaths: sensitivePaths,
}
}
}

@ -110,3 +110,11 @@ func mustPlanDynamicValue(v cty.Value) plans.DynamicValue {
}
return ret
}
func mustPlanDynamicValueDynamicType(v cty.Value) plans.DynamicValue {
ret, err := plans.NewDynamicValue(v, cty.DynamicPseudoType)
if err != nil {
panic(err)
}
return ret
}

@ -703,8 +703,10 @@ func (c *ComponentInstance) ApplyModuleTreePlan(ctx context.Context, plan *plans
// and let the plan file serializer worry about encoding, but we'll
// defer that API change for now to avoid disrupting other codepaths.
modifiedPlan.VariableValues = make(map[string]plans.DynamicValue, len(inputValues))
modifiedPlan.VariableMarks = make(map[string][]cty.PathValueMarks, len(inputValues))
for name, iv := range inputValues {
dv, err := plans.NewDynamicValue(iv.Value, cty.DynamicPseudoType)
val, pvm := iv.Value.UnmarkDeepWithPaths()
dv, err := plans.NewDynamicValue(val, cty.DynamicPseudoType)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -717,6 +719,7 @@ func (c *ComponentInstance) ApplyModuleTreePlan(ctx context.Context, plan *plans
continue
}
modifiedPlan.VariableValues[name] = dv
modifiedPlan.VariableMarks[name] = pvm
}
if diags.HasErrors() {
return nil, diags
@ -1015,10 +1018,11 @@ func (c *ComponentInstance) PlanChanges(ctx context.Context) ([]stackplan.Planne
changes = append(changes, &stackplan.PlannedChangeComponentInstance{
Addr: c.Addr(),
Action: action,
RequiredComponents: c.RequiredComponents(ctx),
PlannedInputValues: corePlan.VariableValues,
PlannedOutputValues: outputVals,
Action: action,
RequiredComponents: c.RequiredComponents(ctx),
PlannedInputValues: corePlan.VariableValues,
PlannedInputValueMarks: corePlan.VariableMarks,
PlannedOutputValues: outputVals,
// We must remember the plan timestamp so that the plantimestamp
// function can return a consistent result during a later apply phase.

@ -383,6 +383,95 @@ func TestPlanSensitiveOutputNested(t *testing.T) {
}
}
func TestPlanSensitiveOutputAsInput(t *testing.T) {
ctx := context.Background()
cfg := loadMainBundleConfigForTest(t, "sensitive-output-as-input")
fakePlanTimestamp, err := time.Parse(time.RFC3339, "1991-08-25T20:57:08Z")
if err != nil {
t.Fatal(err)
}
changesCh := make(chan stackplan.PlannedChange, 8)
diagsCh := make(chan tfdiags.Diagnostic, 2)
req := PlanRequest{
Config: cfg,
ForcePlanTimestamp: &fakePlanTimestamp,
}
resp := PlanResponse{
PlannedChanges: changesCh,
Diagnostics: diagsCh,
}
go Plan(ctx, &req, &resp)
gotChanges, diags := collectPlanOutput(changesCh, diagsCh)
if len(diags) != 0 {
t.Errorf("unexpected diagnostics\n%s", diags.ErrWithWarnings().Error())
}
wantChanges := []stackplan.PlannedChange{
&stackplan.PlannedChangeApplyable{
Applyable: true,
},
&stackplan.PlannedChangeComponentInstance{
Addr: stackaddrs.Absolute(
stackaddrs.RootStackInstance.Child("sensitive", addrs.NoKey),
stackaddrs.ComponentInstance{
Component: stackaddrs.Component{Name: "self"},
},
),
Action: plans.Create,
PlannedInputValues: make(map[string]plans.DynamicValue),
PlannedOutputValues: map[string]cty.Value{
"out": cty.StringVal("secret").Mark(marks.Sensitive),
},
PlanTimestamp: fakePlanTimestamp,
},
&stackplan.PlannedChangeComponentInstance{
Addr: stackaddrs.Absolute(
stackaddrs.RootStackInstance,
stackaddrs.ComponentInstance{
Component: stackaddrs.Component{Name: "self"},
},
),
Action: plans.Create,
PlannedInputValues: map[string]plans.DynamicValue{
"secret": mustPlanDynamicValueDynamicType(cty.StringVal("secret")),
},
PlannedInputValueMarks: map[string][]cty.PathValueMarks{
"secret": {
{
Marks: cty.NewValueMarks(marks.Sensitive),
},
},
},
PlannedOutputValues: map[string]cty.Value{
"result": cty.StringVal("SECRET").Mark(marks.Sensitive),
},
PlanTimestamp: fakePlanTimestamp,
},
&stackplan.PlannedChangeHeader{
TerraformVersion: version.SemVer,
},
&stackplan.PlannedChangeOutputValue{
Addr: stackaddrs.OutputValue{Name: "result"},
Action: plans.Create,
OldValue: plans.DynamicValue{0xc0}, // MessagePack nil
NewValue: mustPlanDynamicValue(cty.StringVal("SECRET")),
NewValueMarks: []cty.PathValueMarks{{Marks: cty.NewValueMarks(marks.Sensitive)}},
},
}
sort.SliceStable(gotChanges, func(i, j int) bool {
// An arbitrary sort just to make the result stable for comparison.
return fmt.Sprintf("%T", gotChanges[i]) < fmt.Sprintf("%T", gotChanges[j])
})
if diff := cmp.Diff(wantChanges, gotChanges, ctydebug.CmpOptions, cmpCollectionsSet); diff != "" {
t.Errorf("wrong changes\n%s", diff)
}
}
func TestPlanWithProviderConfig(t *testing.T) {
ctx := context.Background()
cfg := loadMainBundleConfigForTest(t, "with-provider-config")

@ -0,0 +1,8 @@
variable "secret" {
type = string
}
output "result" {
value = sensitive(upper(var.secret))
sensitive = true
}

@ -0,0 +1,19 @@
stack "sensitive" {
source = "../sensitive-output"
inputs = {
}
}
component "self" {
source = "./"
inputs = {
secret = stack.sensitive.result
}
}
output "result" {
type = string
value = component.self.result
}
Loading…
Cancel
Save