Merge pull request #26590 from hashicorp/pselle/sensitivity-providers

Mark attributes providers mark as sensitive
This commit is contained in:
Pam Selle 2020-10-19 16:13:39 -04:00 committed by GitHub
commit cdebf2820d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 281 additions and 4 deletions

View File

@ -11870,7 +11870,23 @@ variable "sensitive_map" {
resource "test_resource" "foo" { resource "test_resource" "foo" {
value = var.sensitive_map.x value = var.sensitive_map.x
}`, sensitive_value = "should get marked"
}
resource "test_resource" "bar" {
value = test_resource.foo.sensitive_value
random = test_resource.foo.id # not sensitive
nesting_single {
value = "abc"
sensitive_value = "xyz"
}
}
resource "test_resource" "baz" {
value = test_resource.bar.nesting_single.sensitive_value
}
`,
}) })
p := testProvider("test") p := testProvider("test")
@ -11903,10 +11919,20 @@ resource "test_resource" "foo" {
} }
addr := mustResourceInstanceAddr("test_resource.foo") addr := mustResourceInstanceAddr("test_resource.foo")
fooChangeSrc := plan.Changes.ResourceInstance(addr) fooChangeSrc := plan.Changes.ResourceInstance(addr)
verifySensitiveValue(fooChangeSrc.AfterValMarks) verifySensitiveValue(fooChangeSrc.AfterValMarks)
// Sensitive attributes (defined by the provider) are marked
// as sensitive when referenced from another resource
// "bar" references sensitive resources in "foo"
barAddr := mustResourceInstanceAddr("test_resource.bar")
barChangeSrc := plan.Changes.ResourceInstance(barAddr)
verifySensitiveValue(barChangeSrc.AfterValMarks)
bazAddr := mustResourceInstanceAddr("test_resource.baz")
bazChangeSrc := plan.Changes.ResourceInstance(bazAddr)
verifySensitiveValue(bazChangeSrc.AfterValMarks)
state, diags := ctx.Apply() state, diags := ctx.Apply()
if diags.HasErrors() { if diags.HasErrors() {
t.Fatalf("apply errors: %s", diags.Err()) t.Fatalf("apply errors: %s", diags.Err())
@ -11914,6 +11940,12 @@ resource "test_resource" "foo" {
fooState := state.ResourceInstance(addr) fooState := state.ResourceInstance(addr)
verifySensitiveValue(fooState.Current.AttrSensitivePaths) verifySensitiveValue(fooState.Current.AttrSensitivePaths)
barState := state.ResourceInstance(barAddr)
verifySensitiveValue(barState.Current.AttrSensitivePaths)
bazState := state.ResourceInstance(bazAddr)
verifySensitiveValue(bazState.Current.AttrSensitivePaths)
} }
func TestContext2Apply_variableSensitivityChange(t *testing.T) { func TestContext2Apply_variableSensitivityChange(t *testing.T) {

View File

@ -425,6 +425,11 @@ func testProviderSchema(name string) *ProviderSchema {
Type: cty.String, Type: cty.String,
Optional: true, Optional: true,
}, },
"sensitive_value": {
Type: cty.String,
Sensitive: true,
Optional: true,
},
"random": { "random": {
Type: cty.String, Type: cty.String,
Optional: true, Optional: true,
@ -440,6 +445,15 @@ func testProviderSchema(name string) *ProviderSchema {
}, },
Nesting: configschema.NestingSet, Nesting: configschema.NestingSet,
}, },
"nesting_single": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingSingle,
},
}, },
}, },
name + "_ami_list": { name + "_ami_list": {

View File

@ -727,7 +727,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
} }
// Planned resources are temporarily stored in state with empty values, // Planned resources are temporarily stored in state with empty values,
// and need to be replaced bu the planned value here. // and need to be replaced by the planned value here.
if is.Current.Status == states.ObjectPlanned { if is.Current.Status == states.ObjectPlanned {
if change == nil { if change == nil {
// If the object is in planned status then we should not get // If the object is in planned status then we should not get
@ -752,6 +752,10 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
continue continue
} }
// If our schema contains sensitive values, mark those as sensitive
if schema.ContainsSensitive() {
val = markProviderSensitiveAttributes(schema, val)
}
instances[key] = val instances[key] = val
continue continue
} }
@ -768,7 +772,13 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
}) })
continue continue
} }
instances[key] = ios.Value
val := ios.Value
// If our schema contains sensitive values, mark those as sensitive
if schema.ContainsSensitive() {
val = markProviderSensitiveAttributes(schema, val)
}
instances[key] = val
} }
var ret cty.Value var ret cty.Value
@ -935,3 +945,51 @@ func moduleDisplayAddr(addr addrs.ModuleInstance) string {
return addr.String() return addr.String()
} }
} }
// markProviderSensitiveAttributes returns an updated value
// where attributes that are Sensitive are marked
func markProviderSensitiveAttributes(schema *configschema.Block, val cty.Value) cty.Value {
return val.MarkWithPaths(getValMarks(schema, val, nil))
}
func getValMarks(schema *configschema.Block, val cty.Value, path cty.Path) []cty.PathValueMarks {
var pvm []cty.PathValueMarks
for name, attrS := range schema.Attributes {
if attrS.Sensitive {
// Create a copy of the path, with this step added, to add to our PathValueMarks slice
attrPath := make(cty.Path, len(path), len(path)+1)
copy(attrPath, path)
attrPath = append(path, cty.GetAttrStep{Name: name})
pvm = append(pvm, cty.PathValueMarks{
Path: attrPath,
Marks: cty.NewValueMarks("sensitive"),
})
}
}
for name, blockS := range schema.BlockTypes {
// If our block doesn't contain any sensitive attributes, skip inspecting it
if !blockS.Block.ContainsSensitive() {
continue
}
// Create a copy of the path, with this step added, to add to our PathValueMarks slice
blockPath := make(cty.Path, len(path), len(path)+1)
copy(blockPath, path)
blockPath = append(path, cty.GetAttrStep{Name: name})
blockV := val.GetAttr(name)
switch blockS.Nesting {
case configschema.NestingSingle, configschema.NestingGroup:
pvm = append(pvm, getValMarks(&blockS.Block, blockV, blockPath)...)
case configschema.NestingList, configschema.NestingMap, configschema.NestingSet:
for it := blockV.ElementIterator(); it.Next(); {
idx, blockEV := it.Element()
morePaths := getValMarks(&blockS.Block, blockEV, append(blockPath, cty.IndexStep{Key: idx}))
pvm = append(pvm, morePaths...)
}
default:
panic(fmt.Sprintf("unsupported nesting mode %s", blockS.Nesting))
}
}
return pvm
}

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
@ -124,6 +125,178 @@ func TestEvaluatorGetInputVariable(t *testing.T) {
} }
} }
func TestEvaluatorGetResource(t *testing.T) {
stateSync := states.BuildState(func(ss *states.SyncState) {
ss.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo", "nesting_list": [{"sensitive_value":"abc"}], "nesting_map": {"foo":{"foo":"x"}}, "nesting_set": [{"baz":"abc"}], "nesting_single": {"boop":"abc"}, "nesting_nesting": {"nesting_list":[{"sensitive_value":"abc"}]}, "value":"hello"}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
}).SyncWrapper()
rc := &configs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
Provider: addrs.Provider{
Hostname: addrs.DefaultRegistryHost,
Namespace: "hashicorp",
Type: "test",
},
}
evaluator := &Evaluator{
Meta: &ContextMeta{
Env: "foo",
},
Changes: plans.NewChanges().SyncWrapper(),
Config: &configs.Config{
Module: &configs.Module{
ManagedResources: map[string]*configs.Resource{
"test_resource.foo": rc,
},
},
},
State: stateSync,
Schemas: &Schemas{
Providers: map[addrs.Provider]*ProviderSchema{
addrs.NewDefaultProvider("test"): {
Provider: &configschema.Block{},
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Computed: true,
},
"value": {
Type: cty.String,
Computed: true,
Sensitive: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"nesting_list": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingList,
},
"nesting_map": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingMap,
},
"nesting_set": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"baz": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingSet,
},
"nesting_single": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"boop": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingSingle,
},
"nesting_nesting": {
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"nesting_list": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingList,
},
},
},
Nesting: configschema.NestingSingle,
},
},
},
},
},
},
},
}
data := &evaluationStateData{
Evaluator: evaluator,
}
scope := evaluator.Scope(data, nil)
want := cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
"nesting_list": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"sensitive_value": cty.StringVal("abc").Mark("sensitive"),
"value": cty.NullVal(cty.String),
}),
}),
"nesting_map": cty.MapVal(map[string]cty.Value{
"foo": cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("x").Mark("sensitive")}),
}),
"nesting_nesting": cty.ObjectVal(map[string]cty.Value{
"nesting_list": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"sensitive_value": cty.StringVal("abc").Mark("sensitive"),
"value": cty.NullVal(cty.String),
}),
}),
}),
"nesting_set": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"baz": cty.StringVal("abc").Mark("sensitive"),
}),
}),
"nesting_single": cty.ObjectVal(map[string]cty.Value{
"boop": cty.StringVal("abc").Mark("sensitive"),
}),
"value": cty.StringVal("hello").Mark("sensitive"),
})
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
}
got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want)
}
}
func TestEvaluatorGetModule(t *testing.T) { func TestEvaluatorGetModule(t *testing.T) {
// Create a new evaluator with an existing state // Create a new evaluator with an existing state
stateSync := states.BuildState(func(ss *states.SyncState) { stateSync := states.BuildState(func(ss *states.SyncState) {