Unverified Commit 8ec9ad04 authored by Alisdair McDiarmid's avatar Alisdair McDiarmid Committed by GitHub
Browse files

Merge pull request #30029 from hashicorp/alisdair/add-sensitive-marks-for-nested-attributes

configs: Add sensitive marks for nested attributes
parents d2440379 fbed52a3
Showing with 277 additions and 12 deletions
+277 -12
...@@ -44,6 +44,9 @@ func (b *Block) ContainsSensitive() bool { ...@@ -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 { ...@@ -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) { ...@@ -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) { ...@@ -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 ( ...@@ -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 { ...@@ -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 { ...@@ -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
}
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) { ...@@ -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) { ...@@ -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) { ...@@ -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) { ...@@ -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)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment