diff --git a/command/format/diagnostic.go b/command/format/diagnostic.go index 6ceb30894..38626e30b 100644 --- a/command/format/diagnostic.go +++ b/command/format/diagnostic.go @@ -302,6 +302,10 @@ func compactValueStr(val cty.Value) string { // helpful but concise messages in diagnostics. It is not comprehensive // nor intended to be used for other purposes. + if val.ContainsMarked() { + return "(sensitive value)" + } + ty := val.Type() switch { case val.IsNull(): diff --git a/command/format/diff.go b/command/format/diff.go index 901c9a081..ede5620e6 100644 --- a/command/format/diff.go +++ b/command/format/diff.go @@ -125,10 +125,18 @@ func ResourceChange( changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema) changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema) + // Now that the change is decoded, add back the marks at the defined paths + if len(change.BeforeValMarks) > 0 { + changeV.Change.Before = changeV.Change.Before.MarkWithPaths(change.BeforeValMarks) + } + if len(change.AfterValMarks) > 0 { + changeV.Change.After = changeV.Change.After.MarkWithPaths(change.AfterValMarks) + } + result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path) if result.bodyWritten { - p.buf.WriteString("\n") - p.buf.WriteString(strings.Repeat(" ", 4)) + buf.WriteString("\n") + buf.WriteString(strings.Repeat(" ", 4)) } buf.WriteString("}\n") @@ -293,10 +301,18 @@ func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, ol return result } -func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path) bool { - path = append(path, cty.GetAttrStep{Name: name}) - showJustNew := false +// getPlanActionAndShow returns the action value +// and a boolean for showJustNew. In this function we +// modify the old and new values to remove any possible marks +func getPlanActionAndShow(old cty.Value, new cty.Value) (plans.Action, bool) { var action plans.Action + showJustNew := false + if old.ContainsMarked() { + old, _ = old.UnmarkDeep() + } + if new.ContainsMarked() { + new, _ = new.UnmarkDeep() + } switch { case old.IsNull(): action = plans.Create @@ -309,6 +325,12 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At default: action = plans.Update } + return action, showJustNew +} + +func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path) bool { + path = append(path, cty.GetAttrStep{Name: name}) + action, showJustNew := getPlanActionAndShow(old, new) if action == plans.NoOp && p.concise && !identifyingAttribute(name, attrS) { return true @@ -586,6 +608,12 @@ func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, } func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, indent int) { + // Could check specifically for the sensitivity marker + if val.IsMarked() { + p.buf.WriteString("(sensitive)") + return + } + if !val.IsKnown() { p.buf.WriteString("(known after apply)") return @@ -739,6 +767,12 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa ty := old.Type() typesEqual := ctyTypesEqual(ty, new.Type()) + // If either the old or new value is marked, don't display the value + if old.ContainsMarked() || new.ContainsMarked() { + p.buf.WriteString("(sensitive)") + return + } + // We have some specialized diff implementations for certain complex // values where it's useful to see a visualization of the diff of // the nested elements rather than just showing the entire old and @@ -1284,7 +1318,8 @@ func ctyGetAttrMaybeNull(val cty.Value, name string) cty.Value { // This allows us to avoid spurious diffs // until we introduce null to the SDK. attrValue := val.GetAttr(name) - if ctyEmptyString(attrValue) { + // If the value is marked, the ctyEmptyString function will fail + if !val.ContainsMarked() && ctyEmptyString(attrValue) { return cty.NullVal(attrType) } diff --git a/command/format/diff_test.go b/command/format/diff_test.go index 00d0c82c8..b885791a3 100644 --- a/command/format/diff_test.go +++ b/command/format/diff_test.go @@ -3622,10 +3622,75 @@ func TestResourceChange_nestedMap(t *testing.T) { runTestCases(t, testCases) } +func TestResourceChange_sensitiveVariable(t *testing.T) { + testCases := map[string]testCase{ + "in-place update - creation": { + Action: plans.Update, + Mode: addrs.ManagedResourceMode, + Before: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-BEFORE"), + "root_block_device": cty.MapValEmpty(cty.Object(map[string]cty.Type{ + "volume_type": cty.String, + })), + }), + After: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("i-02ae66f368e8518a9"), + "ami": cty.StringVal("ami-AFTER"), + "root_block_device": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "volume_type": cty.StringVal("gp2"), + }), + }), + }), + AfterValMarks: []cty.PathValueMarks{ + { + Path: cty.Path{cty.GetAttrStep{Name: "ami"}}, + Marks: cty.NewValueMarks("sensitive"), + }}, + RequiredReplace: cty.NewPathSet(), + Tainted: false, + Schema: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "id": {Type: cty.String, Optional: true, Computed: true}, + "ami": {Type: cty.String, Optional: true}, + }, + BlockTypes: map[string]*configschema.NestedBlock{ + "root_block_device": { + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "volume_type": { + Type: cty.String, + Optional: true, + Computed: true, + }, + }, + }, + Nesting: configschema.NestingMap, + }, + }, + }, + ExpectedOutput: ` # test_instance.example will be updated in-place + ~ resource "test_instance" "example" { + ~ ami = (sensitive) + id = "i-02ae66f368e8518a9" + + + root_block_device "a" { + + volume_type = "gp2" + } + } +`, + }, + } + runTestCases(t, testCases) +} + type testCase struct { Action plans.Action Mode addrs.ResourceMode Before cty.Value + BeforeValMarks []cty.PathValueMarks + AfterValMarks []cty.PathValueMarks After cty.Value Schema *configschema.Block RequiredReplace cty.PathSet @@ -3679,9 +3744,11 @@ func runTestCases(t *testing.T, testCases map[string]testCase) { Module: addrs.RootModule, }, ChangeSrc: plans.ChangeSrc{ - Action: tc.Action, - Before: before, - After: after, + Action: tc.Action, + Before: before, + After: after, + BeforeValMarks: tc.BeforeValMarks, + AfterValMarks: tc.AfterValMarks, }, RequiredReplace: tc.RequiredReplace, } diff --git a/configs/experiments.go b/configs/experiments.go index 8af1e951f..aacca8e61 100644 --- a/configs/experiments.go +++ b/configs/experiments.go @@ -138,6 +138,17 @@ func checkModuleExperiments(m *Module) hcl.Diagnostics { } } */ - + if !m.ActiveExperiments.Has(experiments.SensitiveVariables) { + for _, v := range m.Variables { + if v.Sensitive { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Variable sensitivity is experimental", + Detail: "This feature is currently an opt-in experiment, subject to change in future releases based on feedback.\n\nActivate the feature for this module by adding sensitive_variables to the list of active experiments.", + Subject: v.DeclRange.Ptr(), + }) + } + } + } return diags } diff --git a/configs/named_values.go b/configs/named_values.go index e0b708a1e..6f867d6c9 100644 --- a/configs/named_values.go +++ b/configs/named_values.go @@ -25,6 +25,7 @@ type Variable struct { Type cty.Type ParsingMode VariableParsingMode Validations []*VariableValidation + Sensitive bool DescriptionSet bool @@ -94,6 +95,11 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno v.ParsingMode = parseMode } + if attr, exists := content.Attributes["sensitive"]; exists { + valDiags := gohcl.DecodeExpression(attr.Expr, nil, &v.Sensitive) + diags = append(diags, valDiags...) + } + if attr, exists := content.Attributes["default"]; exists { val, valDiags := attr.Expr.Value(nil) diags = append(diags, valDiags...) @@ -534,6 +540,9 @@ var variableBlockSchema = &hcl.BodySchema{ { Name: "type", }, + { + Name: "sensitive", + }, }, Blocks: []hcl.BlockHeaderSchema{ { diff --git a/configs/testdata/warning-files/variables-sensitive.tf b/configs/testdata/warning-files/variables-sensitive.tf new file mode 100644 index 000000000..5fedc3986 --- /dev/null +++ b/configs/testdata/warning-files/variables-sensitive.tf @@ -0,0 +1,7 @@ +terraform { + experiments = [sensitive_variables] # WARNING: Experimental feature "sensitive_variables" is active +} + +variable "sensitive-value" { + sensitive = true +} \ No newline at end of file diff --git a/experiments/experiment.go b/experiments/experiment.go index cac7d54fc..f4ca707df 100644 --- a/experiments/experiment.go +++ b/experiments/experiment.go @@ -14,12 +14,14 @@ type Experiment string // identifier so that it can be specified in configuration. const ( VariableValidation = Experiment("variable_validation") + SensitiveVariables = Experiment("sensitive_variables") ) func init() { // Each experiment constant defined above must be registered here as either // a current or a concluded experiment. registerConcludedExperiment(VariableValidation, "Custom variable validation can now be used by default, without enabling an experiment.") + registerCurrentExperiment(SensitiveVariables) } // GetCurrent takes an experiment name and returns the experiment value diff --git a/go.mod b/go.mod index 5e5aa2ab7..4f79a3f20 100644 --- a/go.mod +++ b/go.mod @@ -123,7 +123,7 @@ require ( github.com/xanzy/ssh-agent v0.2.1 github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 - github.com/zclconf/go-cty v1.6.1 + github.com/zclconf/go-cty v1.6.2-0.20200908203537-4ad5e68430d3 github.com/zclconf/go-cty-yaml v1.0.2 go.uber.org/atomic v1.3.2 // indirect go.uber.org/multierr v1.1.0 // indirect diff --git a/go.sum b/go.sum index 3b4b9e3da..2adc08d99 100644 --- a/go.sum +++ b/go.sum @@ -503,6 +503,8 @@ github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLE github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.6.1 h1:wHtZ+LSSQVwUSb+XIJ5E9hgAQxyWATZsAWT+ESJ9dQ0= github.com/zclconf/go-cty v1.6.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= +github.com/zclconf/go-cty v1.6.2-0.20200908203537-4ad5e68430d3 h1:iGouBJrrvGf/H4L6a2n7YBCO0FDhq81FEHI4ILDphkw= +github.com/zclconf/go-cty v1.6.2-0.20200908203537-4ad5e68430d3/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= github.com/zclconf/go-cty-yaml v1.0.2 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0= github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= diff --git a/plans/changes.go b/plans/changes.go index 9e8f25bae..239ceb714 100644 --- a/plans/changes.go +++ b/plans/changes.go @@ -337,18 +337,33 @@ type Change struct { // to call the corresponding Encode method of that struct rather than working // directly with its embedded Change. func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) { - beforeDV, err := NewDynamicValue(c.Before, ty) + // Storing unmarked values so that we can encode unmarked values + // and save the PathValueMarks for re-marking the values later + var beforeVM, afterVM []cty.PathValueMarks + unmarkedBefore := c.Before + unmarkedAfter := c.After + + if c.Before.ContainsMarked() { + unmarkedBefore, beforeVM = c.Before.UnmarkDeepWithPaths() + } + beforeDV, err := NewDynamicValue(unmarkedBefore, ty) if err != nil { return nil, err } - afterDV, err := NewDynamicValue(c.After, ty) + + if c.After.ContainsMarked() { + unmarkedAfter, afterVM = c.After.UnmarkDeepWithPaths() + } + afterDV, err := NewDynamicValue(unmarkedAfter, ty) if err != nil { return nil, err } return &ChangeSrc{ - Action: c.Action, - Before: beforeDV, - After: afterDV, + Action: c.Action, + Before: beforeDV, + After: afterDV, + BeforeValMarks: beforeVM, + AfterValMarks: afterVM, }, nil } diff --git a/plans/changes_src.go b/plans/changes_src.go index 553a84082..683c9f174 100644 --- a/plans/changes_src.go +++ b/plans/changes_src.go @@ -156,6 +156,13 @@ type ChangeSrc struct { // but have not yet been decoded from the serialized value used for // storage. Before, After DynamicValue + + // BeforeValMarks and AfterValMarks are stored path+mark combinations + // that might be discovered when encoding a change. Marks are removed + // to enable encoding (marked values cannot be marshalled), and so storing + // the path+mark combinations allow us to re-mark the value later + // when, for example, displaying the diff to the UI. + BeforeValMarks, AfterValMarks []cty.PathValueMarks } // Decode unmarshals the raw representations of the before and after values diff --git a/plans/objchange/compatible.go b/plans/objchange/compatible.go index 4f944775a..181a044f6 100644 --- a/plans/objchange/compatible.go +++ b/plans/objchange/compatible.go @@ -51,7 +51,17 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu actualV := actual.GetAttr(name) path := append(path, cty.GetAttrStep{Name: name}) - moreErrs := assertValueCompatible(plannedV, actualV, path) + // If our value is marked, unmark it here before + // checking value assertions + unmarkedActualV := actualV + if actualV.ContainsMarked() { + unmarkedActualV, _ = actualV.UnmarkDeep() + } + unmarkedPlannedV := plannedV + if plannedV.ContainsMarked() { + unmarkedPlannedV, _ = actualV.UnmarkDeep() + } + moreErrs := assertValueCompatible(unmarkedPlannedV, unmarkedActualV, path) if attrS.Sensitive { if len(moreErrs) > 0 { // Use a vague placeholder message instead, to avoid disclosing diff --git a/states/instance_object.go b/states/instance_object.go index d1b53e292..2a9e58327 100644 --- a/states/instance_object.go +++ b/states/instance_object.go @@ -98,7 +98,12 @@ func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*Res // and raise an error about that. val := cty.UnknownAsNull(o.Value) - src, err := ctyjson.Marshal(val, ty) + // If it contains marks, dump those now + unmarked := val + if val.ContainsMarked() { + unmarked, _ = val.UnmarkDeep() + } + src, err := ctyjson.Marshal(unmarked, ty) if err != nil { return nil, err } diff --git a/terraform/context.go b/terraform/context.go index cc2e5c7b7..07235776e 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -503,7 +503,6 @@ Note that the -target option is not suitable for routine use, and is provided on func (c *Context) Plan() (*plans.Plan, tfdiags.Diagnostics) { defer c.acquireRun("plan")() c.changes = plans.NewChanges() - var diags tfdiags.Diagnostics if len(c.targets) > 0 { @@ -575,6 +574,7 @@ The -target option is not for routine use, and is provided only for exceptional diags = diags.Append(walker.NonFatalDiagnostics) diags = diags.Append(walkDiags) if walkDiags.HasErrors() { + fmt.Println("walkerr") return nil, diags } p.Changes = c.changes diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index b81f730af..c981834bc 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -5626,6 +5626,61 @@ resource "aws_instance" "foo" { } } +func TestContext2Plan_variableSensitivity(t *testing.T) { + m := testModule(t, "plan-variable-sensitivity") + + p := testProvider("aws") + p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) { + foo := req.Config.GetAttr("foo").AsString() + if foo == "bar" { + resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar")) + } + return + } + + p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { + resp.PlannedState = req.ProposedNewState + return + } + + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p), + }, + }) + + plan, diags := ctx.Plan() + if diags.HasErrors() { + t.Fatalf("unexpected errors: %s", diags.Err()) + } + schema := p.GetSchemaReturn.ResourceTypes["aws_instance"] + ty := schema.ImpliedType() + + if len(plan.Changes.Resources) != 1 { + t.Fatal("expected 1 changes, got", len(plan.Changes.Resources)) + } + + for _, res := range plan.Changes.Resources { + if res.Action != plans.Create { + t.Fatalf("expected resource creation, got %s", res.Action) + } + ric, err := res.Decode(ty) + if err != nil { + t.Fatal(err) + } + + switch i := ric.Addr.String(); i { + case "aws_instance.foo": + checkVals(t, objectVal(t, schema, map[string]cty.Value{ + "foo": cty.StringVal("foo"), + }), ric.After) + default: + t.Fatal("unknown instance:", i) + } + } +} + func checkVals(t *testing.T, expected, got cty.Value) { t.Helper() if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) { diff --git a/terraform/eval_apply.go b/terraform/eval_apply.go index 00af310bb..1a2eef0bd 100644 --- a/terraform/eval_apply.go +++ b/terraform/eval_apply.go @@ -104,11 +104,24 @@ func (n *EvalApply) Eval(ctx EvalContext) (interface{}, error) { } log.Printf("[DEBUG] %s: applying the planned %s change", n.Addr.Absolute(ctx.Path()), change.Action) + + // If our config or After value contain any marked values, + // ensure those are stripped out before sending + // this to the provider + unmarkedConfigVal := configVal + if configVal.ContainsMarked() { + unmarkedConfigVal, _ = configVal.UnmarkDeep() + } + unmarkedAfter := change.After + if change.After.ContainsMarked() { + unmarkedAfter, _ = change.After.UnmarkDeep() + } + resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{ TypeName: n.Addr.Resource.Type, PriorState: change.Before, - Config: configVal, - PlannedState: change.After, + Config: unmarkedConfigVal, + PlannedState: unmarkedAfter, PlannedPrivate: change.Private, ProviderMeta: metaConfigVal, }) diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index 509bd5bbc..e263a7a09 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -141,6 +141,17 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { return nil, diags.Err() } + // Create an unmarked version of our config val, defaulting + // to the configVal so we don't do the work of unmarking unless + // necessary + unmarkedConfigVal := configVal + var unmarkedPaths []cty.PathValueMarks + if configVal.ContainsMarked() { + // store the marked values so we can re-mark them later after + // we've sent things over the wire. + unmarkedConfigVal, unmarkedPaths = configVal.UnmarkDeepWithPaths() + } + metaConfigVal := cty.NullVal(cty.DynamicPseudoType) if n.ProviderMetas != nil { if m, ok := n.ProviderMetas[n.ProviderAddr.Provider]; ok && m != nil { @@ -184,7 +195,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { priorVal = cty.NullVal(schema.ImpliedType()) } - proposedNewVal := objchange.ProposedNewObject(schema, priorVal, configVal) + proposedNewVal := objchange.ProposedNewObject(schema, priorVal, unmarkedConfigVal) // Call pre-diff hook if !n.Stub { @@ -203,7 +214,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { validateResp := provider.ValidateResourceTypeConfig( providers.ValidateResourceTypeConfigRequest{ TypeName: n.Addr.Resource.Type, - Config: configVal, + Config: unmarkedConfigVal, }, ) if validateResp.Diagnostics.HasErrors() { @@ -223,7 +234,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{ TypeName: n.Addr.Resource.Type, - Config: configVal, + Config: unmarkedConfigVal, PriorState: priorVal, ProposedNewState: proposedNewVal, PriorPrivate: priorPrivate, @@ -244,6 +255,11 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { panic(fmt.Sprintf("PlanResourceChange of %s produced nil value", absAddr.String())) } + // Add the marks back to the planned new value + if configVal.ContainsMarked() { + plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths) + } + // We allow the planned new value to disagree with configuration _values_ // here, since that allows the provider to do special logic like a // DiffSuppressFunc, but we still require that the provider produces @@ -480,7 +496,10 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) { Change: plans.Change{ Action: action, Before: priorVal, - After: plannedNewVal, + // Pass the marked planned value through in our change + // to propogate through evaluation. + // Marks will be removed when encoding. + After: plannedNewVal, }, RequiredReplace: reqRep, } diff --git a/terraform/eval_for_each.go b/terraform/eval_for_each.go index f2518bcbc..75e6eabf1 100644 --- a/terraform/eval_for_each.go +++ b/terraform/eval_for_each.go @@ -48,6 +48,14 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.V forEachVal, forEachDiags := ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil) diags = diags.Append(forEachDiags) + if forEachVal.ContainsMarked() { + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid for_each argument", + Detail: "Sensitive variable, or values derived from sensitive variables, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.", + Subject: expr.Range().Ptr(), + }) + } if diags.HasErrors() { return nullMap, diags } diff --git a/terraform/evaluate.go b/terraform/evaluate.go index 3e77ca9b8..3c11b66ca 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -292,6 +292,10 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd val = cty.UnknownVal(wantType) } + if config.Sensitive { + val = val.Mark("sensitive") + } + return val, diags } diff --git a/terraform/testdata/plan-ignore-changes/main.tf b/terraform/testdata/plan-ignore-changes/main.tf index 056256a1d..ed17c6344 100644 --- a/terraform/testdata/plan-ignore-changes/main.tf +++ b/terraform/testdata/plan-ignore-changes/main.tf @@ -1,9 +1,9 @@ variable "foo" {} resource "aws_instance" "foo" { - ami = "${var.foo}" + ami = var.foo lifecycle { - ignore_changes = ["ami"] + ignore_changes = [ami] } } diff --git a/terraform/testdata/plan-variable-sensitivity/main.tf b/terraform/testdata/plan-variable-sensitivity/main.tf new file mode 100644 index 000000000..e8b02a77c --- /dev/null +++ b/terraform/testdata/plan-variable-sensitivity/main.tf @@ -0,0 +1,12 @@ +terraform { + experiments = [sensitive_variables] +} + +variable "sensitive_var" { + default = "foo" + sensitive = true +} + +resource "aws_instance" "foo" { + foo = var.sensitive_var +} \ No newline at end of file diff --git a/vendor/github.com/zclconf/go-cty/cty/json/marshal.go b/vendor/github.com/zclconf/go-cty/cty/json/marshal.go index 75e02577b..7a14ce81a 100644 --- a/vendor/github.com/zclconf/go-cty/cty/json/marshal.go +++ b/vendor/github.com/zclconf/go-cty/cty/json/marshal.go @@ -10,7 +10,7 @@ import ( func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error { if val.IsMarked() { - return path.NewErrorf("value has marks, so it cannot be seralized") + return path.NewErrorf("value has marks, so it cannot be serialized as JSON") } // If we're going to decode as DynamicPseudoType then we need to save diff --git a/vendor/github.com/zclconf/go-cty/cty/marks.go b/vendor/github.com/zclconf/go-cty/cty/marks.go index 3898e4553..e3e5ca74f 100644 --- a/vendor/github.com/zclconf/go-cty/cty/marks.go +++ b/vendor/github.com/zclconf/go-cty/cty/marks.go @@ -67,6 +67,23 @@ func (m ValueMarks) GoString() string { return s.String() } +// PathValueMarks is a structure that enables tracking marks +// and the paths where they are located in one type +type PathValueMarks struct { + Path Path + Marks ValueMarks +} + +func (p PathValueMarks) Equal(o PathValueMarks) bool { + if !p.Path.Equals(o.Path) { + return false + } + if !p.Marks.Equal(o.Marks) { + return false + } + return true +} + // IsMarked returns true if and only if the receiving value carries at least // one mark. A marked value cannot be used directly with integration methods // without explicitly unmarking it (and retrieving the markings) first. @@ -174,6 +191,21 @@ func (val Value) Mark(mark interface{}) Value { } } +// MarkWithPaths accepts a slice of PathValueMarks to apply +// markers to particular paths and returns the marked +// Value. +func (val Value) MarkWithPaths(pvm []PathValueMarks) Value { + ret, _ := Transform(val, func(p Path, v Value) (Value, error) { + for _, path := range pvm { + if p.Equals(path.Path) { + return v.WithMarks(path.Marks), nil + } + } + return v, nil + }) + return ret +} + // Unmark separates the marks of the receiving value from the value itself, // removing a new unmarked value and a map (representing a set) of the marks. // @@ -209,6 +241,22 @@ func (val Value) UnmarkDeep() (Value, ValueMarks) { return ret, marks } +// UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice +// of PathValueMarks rather than a superset of all marks. This allows +// a caller to know which marks are associated with which paths +// in the Value. +func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) { + var marks []PathValueMarks + ret, _ := Transform(val, func(p Path, v Value) (Value, error) { + unmarkedV, valueMarks := v.Unmark() + if v.IsMarked() { + marks = append(marks, PathValueMarks{p, valueMarks}) + } + return unmarkedV, nil + }) + return ret, marks +} + func (val Value) unmarkForce() Value { unw, _ := val.Unmark() return unw diff --git a/vendor/github.com/zclconf/go-cty/cty/msgpack/marshal.go b/vendor/github.com/zclconf/go-cty/cty/msgpack/marshal.go index cc351f0f1..2c4da8b50 100644 --- a/vendor/github.com/zclconf/go-cty/cty/msgpack/marshal.go +++ b/vendor/github.com/zclconf/go-cty/cty/msgpack/marshal.go @@ -43,7 +43,7 @@ func Marshal(val cty.Value, ty cty.Type) ([]byte, error) { func marshal(val cty.Value, ty cty.Type, path cty.Path, enc *msgpack.Encoder) error { if val.IsMarked() { - return path.NewErrorf("value has marks, so it cannot be seralized") + return path.NewErrorf("value has marks, so it cannot be serialized") } // If we're going to decode as DynamicPseudoType then we need to save diff --git a/vendor/modules.txt b/vendor/modules.txt index 6363afc66..4f40990da 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -605,7 +605,7 @@ github.com/xanzy/ssh-agent # github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 ## explicit github.com/xlab/treeprint -# github.com/zclconf/go-cty v1.6.1 +# github.com/zclconf/go-cty v1.6.2-0.20200908203537-4ad5e68430d3 ## explicit github.com/zclconf/go-cty/cty github.com/zclconf/go-cty/cty/convert