Merge pull request #26183 from hashicorp/pselle/sensitive-values
Add sensitive attribute to variables
This commit is contained in:
commit
6a126df0c6
|
@ -302,6 +302,10 @@ func compactValueStr(val cty.Value) string {
|
||||||
// helpful but concise messages in diagnostics. It is not comprehensive
|
// helpful but concise messages in diagnostics. It is not comprehensive
|
||||||
// nor intended to be used for other purposes.
|
// nor intended to be used for other purposes.
|
||||||
|
|
||||||
|
if val.ContainsMarked() {
|
||||||
|
return "(sensitive value)"
|
||||||
|
}
|
||||||
|
|
||||||
ty := val.Type()
|
ty := val.Type()
|
||||||
switch {
|
switch {
|
||||||
case val.IsNull():
|
case val.IsNull():
|
||||||
|
|
|
@ -125,10 +125,18 @@ func ResourceChange(
|
||||||
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
|
||||||
changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, 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)
|
result := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
|
||||||
if result.bodyWritten {
|
if result.bodyWritten {
|
||||||
p.buf.WriteString("\n")
|
buf.WriteString("\n")
|
||||||
p.buf.WriteString(strings.Repeat(" ", 4))
|
buf.WriteString(strings.Repeat(" ", 4))
|
||||||
}
|
}
|
||||||
buf.WriteString("}\n")
|
buf.WriteString("}\n")
|
||||||
|
|
||||||
|
@ -293,10 +301,18 @@ func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, ol
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path) bool {
|
// getPlanActionAndShow returns the action value
|
||||||
path = append(path, cty.GetAttrStep{Name: name})
|
// and a boolean for showJustNew. In this function we
|
||||||
showJustNew := false
|
// 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
|
var action plans.Action
|
||||||
|
showJustNew := false
|
||||||
|
if old.ContainsMarked() {
|
||||||
|
old, _ = old.UnmarkDeep()
|
||||||
|
}
|
||||||
|
if new.ContainsMarked() {
|
||||||
|
new, _ = new.UnmarkDeep()
|
||||||
|
}
|
||||||
switch {
|
switch {
|
||||||
case old.IsNull():
|
case old.IsNull():
|
||||||
action = plans.Create
|
action = plans.Create
|
||||||
|
@ -309,6 +325,12 @@ func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.At
|
||||||
default:
|
default:
|
||||||
action = plans.Update
|
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) {
|
if action == plans.NoOp && p.concise && !identifyingAttribute(name, attrS) {
|
||||||
return true
|
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) {
|
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() {
|
if !val.IsKnown() {
|
||||||
p.buf.WriteString("(known after apply)")
|
p.buf.WriteString("(known after apply)")
|
||||||
return
|
return
|
||||||
|
@ -739,6 +767,12 @@ func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, pa
|
||||||
ty := old.Type()
|
ty := old.Type()
|
||||||
typesEqual := ctyTypesEqual(ty, new.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
|
// We have some specialized diff implementations for certain complex
|
||||||
// values where it's useful to see a visualization of the diff of
|
// values where it's useful to see a visualization of the diff of
|
||||||
// the nested elements rather than just showing the entire old and
|
// 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
|
// This allows us to avoid spurious diffs
|
||||||
// until we introduce null to the SDK.
|
// until we introduce null to the SDK.
|
||||||
attrValue := val.GetAttr(name)
|
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)
|
return cty.NullVal(attrType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3622,10 +3622,75 @@ func TestResourceChange_nestedMap(t *testing.T) {
|
||||||
runTestCases(t, testCases)
|
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 {
|
type testCase struct {
|
||||||
Action plans.Action
|
Action plans.Action
|
||||||
Mode addrs.ResourceMode
|
Mode addrs.ResourceMode
|
||||||
Before cty.Value
|
Before cty.Value
|
||||||
|
BeforeValMarks []cty.PathValueMarks
|
||||||
|
AfterValMarks []cty.PathValueMarks
|
||||||
After cty.Value
|
After cty.Value
|
||||||
Schema *configschema.Block
|
Schema *configschema.Block
|
||||||
RequiredReplace cty.PathSet
|
RequiredReplace cty.PathSet
|
||||||
|
@ -3679,9 +3744,11 @@ func runTestCases(t *testing.T, testCases map[string]testCase) {
|
||||||
Module: addrs.RootModule,
|
Module: addrs.RootModule,
|
||||||
},
|
},
|
||||||
ChangeSrc: plans.ChangeSrc{
|
ChangeSrc: plans.ChangeSrc{
|
||||||
Action: tc.Action,
|
Action: tc.Action,
|
||||||
Before: before,
|
Before: before,
|
||||||
After: after,
|
After: after,
|
||||||
|
BeforeValMarks: tc.BeforeValMarks,
|
||||||
|
AfterValMarks: tc.AfterValMarks,
|
||||||
},
|
},
|
||||||
RequiredReplace: tc.RequiredReplace,
|
RequiredReplace: tc.RequiredReplace,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
return diags
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ type Variable struct {
|
||||||
Type cty.Type
|
Type cty.Type
|
||||||
ParsingMode VariableParsingMode
|
ParsingMode VariableParsingMode
|
||||||
Validations []*VariableValidation
|
Validations []*VariableValidation
|
||||||
|
Sensitive bool
|
||||||
|
|
||||||
DescriptionSet bool
|
DescriptionSet bool
|
||||||
|
|
||||||
|
@ -94,6 +95,11 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno
|
||||||
v.ParsingMode = parseMode
|
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 {
|
if attr, exists := content.Attributes["default"]; exists {
|
||||||
val, valDiags := attr.Expr.Value(nil)
|
val, valDiags := attr.Expr.Value(nil)
|
||||||
diags = append(diags, valDiags...)
|
diags = append(diags, valDiags...)
|
||||||
|
@ -534,6 +540,9 @@ var variableBlockSchema = &hcl.BodySchema{
|
||||||
{
|
{
|
||||||
Name: "type",
|
Name: "type",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "sensitive",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Blocks: []hcl.BlockHeaderSchema{
|
Blocks: []hcl.BlockHeaderSchema{
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
terraform {
|
||||||
|
experiments = [sensitive_variables] # WARNING: Experimental feature "sensitive_variables" is active
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "sensitive-value" {
|
||||||
|
sensitive = true
|
||||||
|
}
|
|
@ -14,12 +14,14 @@ type Experiment string
|
||||||
// identifier so that it can be specified in configuration.
|
// identifier so that it can be specified in configuration.
|
||||||
const (
|
const (
|
||||||
VariableValidation = Experiment("variable_validation")
|
VariableValidation = Experiment("variable_validation")
|
||||||
|
SensitiveVariables = Experiment("sensitive_variables")
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Each experiment constant defined above must be registered here as either
|
// Each experiment constant defined above must be registered here as either
|
||||||
// a current or a concluded experiment.
|
// a current or a concluded experiment.
|
||||||
registerConcludedExperiment(VariableValidation, "Custom variable validation can now be used by default, without enabling an 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
|
// GetCurrent takes an experiment name and returns the experiment value
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -123,7 +123,7 @@ require (
|
||||||
github.com/xanzy/ssh-agent v0.2.1
|
github.com/xanzy/ssh-agent v0.2.1
|
||||||
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18 // indirect
|
||||||
github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
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
|
github.com/zclconf/go-cty-yaml v1.0.2
|
||||||
go.uber.org/atomic v1.3.2 // indirect
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
go.uber.org/multierr v1.1.0 // indirect
|
go.uber.org/multierr v1.1.0 // indirect
|
||||||
|
|
2
go.sum
2
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.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 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.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 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0=
|
||||||
github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
|
|
|
@ -337,18 +337,33 @@ type Change struct {
|
||||||
// to call the corresponding Encode method of that struct rather than working
|
// to call the corresponding Encode method of that struct rather than working
|
||||||
// directly with its embedded Change.
|
// directly with its embedded Change.
|
||||||
func (c *Change) Encode(ty cty.Type) (*ChangeSrc, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ChangeSrc{
|
return &ChangeSrc{
|
||||||
Action: c.Action,
|
Action: c.Action,
|
||||||
Before: beforeDV,
|
Before: beforeDV,
|
||||||
After: afterDV,
|
After: afterDV,
|
||||||
|
BeforeValMarks: beforeVM,
|
||||||
|
AfterValMarks: afterVM,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -156,6 +156,13 @@ type ChangeSrc struct {
|
||||||
// but have not yet been decoded from the serialized value used for
|
// but have not yet been decoded from the serialized value used for
|
||||||
// storage.
|
// storage.
|
||||||
Before, After DynamicValue
|
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
|
// Decode unmarshals the raw representations of the before and after values
|
||||||
|
|
|
@ -51,7 +51,17 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu
|
||||||
actualV := actual.GetAttr(name)
|
actualV := actual.GetAttr(name)
|
||||||
|
|
||||||
path := append(path, cty.GetAttrStep{Name: 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 attrS.Sensitive {
|
||||||
if len(moreErrs) > 0 {
|
if len(moreErrs) > 0 {
|
||||||
// Use a vague placeholder message instead, to avoid disclosing
|
// Use a vague placeholder message instead, to avoid disclosing
|
||||||
|
|
|
@ -98,7 +98,12 @@ func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*Res
|
||||||
// and raise an error about that.
|
// and raise an error about that.
|
||||||
val := cty.UnknownAsNull(o.Value)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
func (c *Context) Plan() (*plans.Plan, tfdiags.Diagnostics) {
|
||||||
defer c.acquireRun("plan")()
|
defer c.acquireRun("plan")()
|
||||||
c.changes = plans.NewChanges()
|
c.changes = plans.NewChanges()
|
||||||
|
|
||||||
var diags tfdiags.Diagnostics
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
if len(c.targets) > 0 {
|
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(walker.NonFatalDiagnostics)
|
||||||
diags = diags.Append(walkDiags)
|
diags = diags.Append(walkDiags)
|
||||||
if walkDiags.HasErrors() {
|
if walkDiags.HasErrors() {
|
||||||
|
fmt.Println("walkerr")
|
||||||
return nil, diags
|
return nil, diags
|
||||||
}
|
}
|
||||||
p.Changes = c.changes
|
p.Changes = c.changes
|
||||||
|
|
|
@ -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) {
|
func checkVals(t *testing.T, expected, got cty.Value) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
||||||
|
|
|
@ -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)
|
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{
|
resp := provider.ApplyResourceChange(providers.ApplyResourceChangeRequest{
|
||||||
TypeName: n.Addr.Resource.Type,
|
TypeName: n.Addr.Resource.Type,
|
||||||
PriorState: change.Before,
|
PriorState: change.Before,
|
||||||
Config: configVal,
|
Config: unmarkedConfigVal,
|
||||||
PlannedState: change.After,
|
PlannedState: unmarkedAfter,
|
||||||
PlannedPrivate: change.Private,
|
PlannedPrivate: change.Private,
|
||||||
ProviderMeta: metaConfigVal,
|
ProviderMeta: metaConfigVal,
|
||||||
})
|
})
|
||||||
|
|
|
@ -141,6 +141,17 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
return nil, diags.Err()
|
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)
|
metaConfigVal := cty.NullVal(cty.DynamicPseudoType)
|
||||||
if n.ProviderMetas != nil {
|
if n.ProviderMetas != nil {
|
||||||
if m, ok := n.ProviderMetas[n.ProviderAddr.Provider]; ok && m != 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())
|
priorVal = cty.NullVal(schema.ImpliedType())
|
||||||
}
|
}
|
||||||
|
|
||||||
proposedNewVal := objchange.ProposedNewObject(schema, priorVal, configVal)
|
proposedNewVal := objchange.ProposedNewObject(schema, priorVal, unmarkedConfigVal)
|
||||||
|
|
||||||
// Call pre-diff hook
|
// Call pre-diff hook
|
||||||
if !n.Stub {
|
if !n.Stub {
|
||||||
|
@ -203,7 +214,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
validateResp := provider.ValidateResourceTypeConfig(
|
validateResp := provider.ValidateResourceTypeConfig(
|
||||||
providers.ValidateResourceTypeConfigRequest{
|
providers.ValidateResourceTypeConfigRequest{
|
||||||
TypeName: n.Addr.Resource.Type,
|
TypeName: n.Addr.Resource.Type,
|
||||||
Config: configVal,
|
Config: unmarkedConfigVal,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if validateResp.Diagnostics.HasErrors() {
|
if validateResp.Diagnostics.HasErrors() {
|
||||||
|
@ -223,7 +234,7 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
|
|
||||||
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
|
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
|
||||||
TypeName: n.Addr.Resource.Type,
|
TypeName: n.Addr.Resource.Type,
|
||||||
Config: configVal,
|
Config: unmarkedConfigVal,
|
||||||
PriorState: priorVal,
|
PriorState: priorVal,
|
||||||
ProposedNewState: proposedNewVal,
|
ProposedNewState: proposedNewVal,
|
||||||
PriorPrivate: priorPrivate,
|
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()))
|
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_
|
// We allow the planned new value to disagree with configuration _values_
|
||||||
// here, since that allows the provider to do special logic like a
|
// here, since that allows the provider to do special logic like a
|
||||||
// DiffSuppressFunc, but we still require that the provider produces
|
// DiffSuppressFunc, but we still require that the provider produces
|
||||||
|
@ -480,7 +496,10 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
|
||||||
Change: plans.Change{
|
Change: plans.Change{
|
||||||
Action: action,
|
Action: action,
|
||||||
Before: priorVal,
|
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,
|
RequiredReplace: reqRep,
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,14 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.V
|
||||||
|
|
||||||
forEachVal, forEachDiags := ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil)
|
forEachVal, forEachDiags := ctx.EvaluateExpr(expr, cty.DynamicPseudoType, nil)
|
||||||
diags = diags.Append(forEachDiags)
|
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() {
|
if diags.HasErrors() {
|
||||||
return nullMap, diags
|
return nullMap, diags
|
||||||
}
|
}
|
||||||
|
|
|
@ -292,6 +292,10 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd
|
||||||
val = cty.UnknownVal(wantType)
|
val = cty.UnknownVal(wantType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.Sensitive {
|
||||||
|
val = val.Mark("sensitive")
|
||||||
|
}
|
||||||
|
|
||||||
return val, diags
|
return val, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
variable "foo" {}
|
variable "foo" {}
|
||||||
|
|
||||||
resource "aws_instance" "foo" {
|
resource "aws_instance" "foo" {
|
||||||
ami = "${var.foo}"
|
ami = var.foo
|
||||||
|
|
||||||
lifecycle {
|
lifecycle {
|
||||||
ignore_changes = ["ami"]
|
ignore_changes = [ami]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
terraform {
|
||||||
|
experiments = [sensitive_variables]
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "sensitive_var" {
|
||||||
|
default = "foo"
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "foo" {
|
||||||
|
foo = var.sensitive_var
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error {
|
func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error {
|
||||||
if val.IsMarked() {
|
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
|
// If we're going to decode as DynamicPseudoType then we need to save
|
||||||
|
|
|
@ -67,6 +67,23 @@ func (m ValueMarks) GoString() string {
|
||||||
return s.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
|
// 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
|
// one mark. A marked value cannot be used directly with integration methods
|
||||||
// without explicitly unmarking it (and retrieving the markings) first.
|
// 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,
|
// 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.
|
// 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
|
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 {
|
func (val Value) unmarkForce() Value {
|
||||||
unw, _ := val.Unmark()
|
unw, _ := val.Unmark()
|
||||||
return unw
|
return unw
|
||||||
|
|
|
@ -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 {
|
func marshal(val cty.Value, ty cty.Type, path cty.Path, enc *msgpack.Encoder) error {
|
||||||
if val.IsMarked() {
|
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
|
// If we're going to decode as DynamicPseudoType then we need to save
|
||||||
|
|
|
@ -605,7 +605,7 @@ github.com/xanzy/ssh-agent
|
||||||
# github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
# github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557
|
||||||
## explicit
|
## explicit
|
||||||
github.com/xlab/treeprint
|
github.com/xlab/treeprint
|
||||||
# github.com/zclconf/go-cty v1.6.1
|
# github.com/zclconf/go-cty v1.6.2-0.20200908203537-4ad5e68430d3
|
||||||
## explicit
|
## explicit
|
||||||
github.com/zclconf/go-cty/cty
|
github.com/zclconf/go-cty/cty
|
||||||
github.com/zclconf/go-cty/cty/convert
|
github.com/zclconf/go-cty/cty/convert
|
||||||
|
|
Loading…
Reference in New Issue