configs: Add sensitive marks for nested attributes
Object values returned from providers have their attributes marked as sensitive based on the provider schema. This was not fully implemented for nested attribute types, which is corrected in this commit.
This commit is contained in:
parent
a6b56ad76f
commit
fbed52a353
|
@ -44,6 +44,9 @@ func (b *Block) ContainsSensitive() bool {
|
||||||
if attrS.Sensitive {
|
if attrS.Sensitive {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if attrS.NestedType != nil && attrS.NestedType.ContainsSensitive() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, blockS := range b.BlockTypes {
|
for _, blockS := range b.BlockTypes {
|
||||||
if blockS.ContainsSensitive() {
|
if blockS.ContainsSensitive() {
|
||||||
|
@ -108,8 +111,8 @@ func (o *Object) ContainsSensitive() bool {
|
||||||
if attrS.Sensitive {
|
if attrS.Sensitive {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if attrS.NestedType != nil {
|
if attrS.NestedType != nil && attrS.NestedType.ContainsSensitive() {
|
||||||
return attrS.NestedType.ContainsSensitive()
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -154,6 +154,70 @@ func TestBlockImpliedType(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBlockContainsSensitive(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
Schema *Block
|
||||||
|
Want bool
|
||||||
|
}{
|
||||||
|
"object contains sensitive": {
|
||||||
|
&Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"sensitive": {Sensitive: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"no sensitive attrs": {
|
||||||
|
&Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"insensitive": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"nested object contains sensitive": {
|
||||||
|
&Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nested": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"sensitive": {Sensitive: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"nested obj, no sensitive attrs": {
|
||||||
|
&Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nested": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"public": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := test.Schema.ContainsSensitive()
|
||||||
|
if got != test.Want {
|
||||||
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestObjectImpliedType(t *testing.T) {
|
func TestObjectImpliedType(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
Schema *Object
|
Schema *Object
|
||||||
|
@ -353,6 +417,37 @@ func TestObjectContainsSensitive(t *testing.T) {
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
"several nested objects, one contains sensitive": {
|
||||||
|
&Object{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"alpha": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nonsensitive": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"beta": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"sensitive": {Sensitive: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"gamma": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nonsensitive": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
|
|
|
@ -12,6 +12,8 @@ import (
|
||||||
// blocks are descended (if present in the given value).
|
// blocks are descended (if present in the given value).
|
||||||
func (b *Block) ValueMarks(val cty.Value, path cty.Path) []cty.PathValueMarks {
|
func (b *Block) ValueMarks(val cty.Value, path cty.Path) []cty.PathValueMarks {
|
||||||
var pvm []cty.PathValueMarks
|
var pvm []cty.PathValueMarks
|
||||||
|
|
||||||
|
// We can mark attributes as sensitive even if the value is null
|
||||||
for name, attrS := range b.Attributes {
|
for name, attrS := range b.Attributes {
|
||||||
if attrS.Sensitive {
|
if attrS.Sensitive {
|
||||||
// Create a copy of the path, with this step added, to add to our PathValueMarks slice
|
// Create a copy of the path, with this step added, to add to our PathValueMarks slice
|
||||||
|
@ -25,9 +27,28 @@ func (b *Block) ValueMarks(val cty.Value, path cty.Path) []cty.PathValueMarks {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the value is null, no other marks are possible
|
||||||
if val.IsNull() {
|
if val.IsNull() {
|
||||||
return pvm
|
return pvm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Extract marks for nested attribute type values
|
||||||
|
for name, attrS := range b.Attributes {
|
||||||
|
// If the attribute has no nested type, or the nested type doesn't
|
||||||
|
// contain any sensitive attributes, skip inspecting it
|
||||||
|
if attrS.NestedType == nil || !attrS.NestedType.ContainsSensitive() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, attrS.NestedType.ValueMarks(val.GetAttr(name), attrPath)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract marks for nested blocks
|
||||||
for name, blockS := range b.BlockTypes {
|
for name, blockS := range b.BlockTypes {
|
||||||
// If our block doesn't contain any sensitive attributes, skip inspecting it
|
// If our block doesn't contain any sensitive attributes, skip inspecting it
|
||||||
if !blockS.Block.ContainsSensitive() {
|
if !blockS.Block.ContainsSensitive() {
|
||||||
|
@ -59,3 +80,72 @@ func (b *Block) ValueMarks(val cty.Value, path cty.Path) []cty.PathValueMarks {
|
||||||
}
|
}
|
||||||
return pvm
|
return pvm
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValueMarks returns a set of path value marks for a given value and path,
|
||||||
|
// based on the sensitive flag for each attribute within the nested attribute.
|
||||||
|
// Attributes with nested types are descended (if present in the given value).
|
||||||
|
func (o *Object) ValueMarks(val cty.Value, path cty.Path) []cty.PathValueMarks {
|
||||||
|
var pvm []cty.PathValueMarks
|
||||||
|
|
||||||
|
if val.IsNull() || !val.IsKnown() {
|
||||||
|
return pvm
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, attrS := range o.Attributes {
|
||||||
|
// Skip attributes which can never produce sensitive path value marks
|
||||||
|
if !attrS.Sensitive && (attrS.NestedType == nil || !attrS.NestedType.ContainsSensitive()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch o.Nesting {
|
||||||
|
case NestingSingle, NestingGroup:
|
||||||
|
// Create a path to this attribute
|
||||||
|
attrPath := make(cty.Path, len(path), len(path)+1)
|
||||||
|
copy(attrPath, path)
|
||||||
|
attrPath = append(path, cty.GetAttrStep{Name: name})
|
||||||
|
|
||||||
|
if attrS.Sensitive {
|
||||||
|
// If the entire attribute is sensitive, mark it so
|
||||||
|
pvm = append(pvm, cty.PathValueMarks{
|
||||||
|
Path: attrPath,
|
||||||
|
Marks: cty.NewValueMarks(marks.Sensitive),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// The attribute has a nested type which contains sensitive
|
||||||
|
// attributes, so recurse
|
||||||
|
pvm = append(pvm, attrS.NestedType.ValueMarks(val.GetAttr(name), attrPath)...)
|
||||||
|
}
|
||||||
|
case NestingList, NestingMap, NestingSet:
|
||||||
|
// For nested attribute types which have a non-single nesting mode,
|
||||||
|
// we add path value marks for each element of the collection
|
||||||
|
for it := val.ElementIterator(); it.Next(); {
|
||||||
|
idx, attrEV := it.Element()
|
||||||
|
attrV := attrEV.GetAttr(name)
|
||||||
|
|
||||||
|
// Create a path to this element of the attribute's collection. Note
|
||||||
|
// that the path is extended in opposite order to the iteration order
|
||||||
|
// of the loops: index into the collection, then the contained
|
||||||
|
// attribute name. This is because we have one type
|
||||||
|
// representing multiple collection elements.
|
||||||
|
attrPath := make(cty.Path, len(path), len(path)+2)
|
||||||
|
copy(attrPath, path)
|
||||||
|
attrPath = append(path, cty.IndexStep{Key: idx}, cty.GetAttrStep{Name: name})
|
||||||
|
|
||||||
|
if attrS.Sensitive {
|
||||||
|
// If the entire attribute is sensitive, mark it so
|
||||||
|
pvm = append(pvm, cty.PathValueMarks{
|
||||||
|
Path: attrPath,
|
||||||
|
Marks: cty.NewValueMarks(marks.Sensitive),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// The attribute has a nested type which contains sensitive
|
||||||
|
// attributes, so recurse
|
||||||
|
pvm = append(pvm, attrS.NestedType.ValueMarks(attrV, attrPath)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unsupported nesting mode %s", attrS.NestedType.Nesting))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pvm
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package configschema
|
package configschema
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/lang/marks"
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||||
|
@ -19,6 +18,20 @@ func TestBlockValueMarks(t *testing.T) {
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
},
|
},
|
||||||
|
"nested": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"boop": {
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
"honk": {
|
||||||
|
Type: cty.String,
|
||||||
|
Sensitive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Nesting: NestingList,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
BlockTypes: map[string]*NestedBlock{
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
@ -40,34 +53,46 @@ func TestBlockValueMarks(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range []struct {
|
testCases := map[string]struct {
|
||||||
given cty.Value
|
given cty.Value
|
||||||
expect cty.Value
|
expect cty.Value
|
||||||
}{
|
}{
|
||||||
{
|
"unknown object": {
|
||||||
cty.UnknownVal(schema.ImpliedType()),
|
cty.UnknownVal(schema.ImpliedType()),
|
||||||
cty.UnknownVal(schema.ImpliedType()),
|
cty.UnknownVal(schema.ImpliedType()),
|
||||||
},
|
},
|
||||||
{
|
"null object": {
|
||||||
cty.NullVal(schema.ImpliedType()),
|
cty.NullVal(schema.ImpliedType()),
|
||||||
cty.NullVal(schema.ImpliedType()),
|
cty.NullVal(schema.ImpliedType()),
|
||||||
},
|
},
|
||||||
{
|
"object with unknown attributes and blocks": {
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"sensitive": cty.UnknownVal(cty.String),
|
"sensitive": cty.UnknownVal(cty.String),
|
||||||
"unsensitive": cty.UnknownVal(cty.String),
|
"unsensitive": cty.UnknownVal(cty.String),
|
||||||
"list": cty.UnknownVal(schema.BlockTypes["list"].ImpliedType()),
|
"nested": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"boop": cty.String,
|
||||||
|
"honk": cty.String,
|
||||||
|
}))),
|
||||||
|
"list": cty.UnknownVal(schema.BlockTypes["list"].ImpliedType()),
|
||||||
}),
|
}),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"sensitive": cty.UnknownVal(cty.String).Mark(marks.Sensitive),
|
"sensitive": cty.UnknownVal(cty.String).Mark(marks.Sensitive),
|
||||||
"unsensitive": cty.UnknownVal(cty.String),
|
"unsensitive": cty.UnknownVal(cty.String),
|
||||||
"list": cty.UnknownVal(schema.BlockTypes["list"].ImpliedType()),
|
"nested": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"boop": cty.String,
|
||||||
|
"honk": cty.String,
|
||||||
|
}))),
|
||||||
|
"list": cty.UnknownVal(schema.BlockTypes["list"].ImpliedType()),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
"object with block value": {
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"sensitive": cty.NullVal(cty.String),
|
"sensitive": cty.NullVal(cty.String),
|
||||||
"unsensitive": cty.UnknownVal(cty.String),
|
"unsensitive": cty.UnknownVal(cty.String),
|
||||||
|
"nested": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"boop": cty.String,
|
||||||
|
"honk": cty.String,
|
||||||
|
}))),
|
||||||
"list": cty.ListVal([]cty.Value{
|
"list": cty.ListVal([]cty.Value{
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"sensitive": cty.UnknownVal(cty.String),
|
"sensitive": cty.UnknownVal(cty.String),
|
||||||
|
@ -82,6 +107,10 @@ func TestBlockValueMarks(t *testing.T) {
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"sensitive": cty.NullVal(cty.String).Mark(marks.Sensitive),
|
"sensitive": cty.NullVal(cty.String).Mark(marks.Sensitive),
|
||||||
"unsensitive": cty.UnknownVal(cty.String),
|
"unsensitive": cty.UnknownVal(cty.String),
|
||||||
|
"nested": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"boop": cty.String,
|
||||||
|
"honk": cty.String,
|
||||||
|
}))),
|
||||||
"list": cty.ListVal([]cty.Value{
|
"list": cty.ListVal([]cty.Value{
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"sensitive": cty.UnknownVal(cty.String).Mark(marks.Sensitive),
|
"sensitive": cty.UnknownVal(cty.String).Mark(marks.Sensitive),
|
||||||
|
@ -94,8 +123,56 @@ func TestBlockValueMarks(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
} {
|
"object with known values and nested attribute": {
|
||||||
t.Run(fmt.Sprintf("%#v", tc.given), func(t *testing.T) {
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"sensitive": cty.StringVal("foo"),
|
||||||
|
"unsensitive": cty.StringVal("bar"),
|
||||||
|
"nested": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("foo"),
|
||||||
|
"honk": cty.StringVal("bar"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"boop": cty.NullVal(cty.String),
|
||||||
|
"honk": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"boop": cty.UnknownVal(cty.String),
|
||||||
|
"honk": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"sensitive": cty.String,
|
||||||
|
"unsensitive": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"sensitive": cty.StringVal("foo").Mark(marks.Sensitive),
|
||||||
|
"unsensitive": cty.StringVal("bar"),
|
||||||
|
"nested": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"boop": cty.StringVal("foo"),
|
||||||
|
"honk": cty.StringVal("bar").Mark(marks.Sensitive),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"boop": cty.NullVal(cty.String),
|
||||||
|
"honk": cty.NullVal(cty.String).Mark(marks.Sensitive),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"boop": cty.UnknownVal(cty.String),
|
||||||
|
"honk": cty.UnknownVal(cty.String).Mark(marks.Sensitive),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{
|
||||||
|
"sensitive": cty.String,
|
||||||
|
"unsensitive": cty.String,
|
||||||
|
}))),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
got := tc.given.MarkWithPaths(schema.ValueMarks(tc.given, nil))
|
got := tc.given.MarkWithPaths(schema.ValueMarks(tc.given, nil))
|
||||||
if !got.RawEquals(tc.expect) {
|
if !got.RawEquals(tc.expect) {
|
||||||
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expect, got)
|
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.expect, got)
|
||||||
|
|
Loading…
Reference in New Issue