Skip to content

Commit 990dae7

Browse files
authored
Merge pull request #34 from hashicorp/sensitive-values
sanitize: allow for the sanitization of sensitive values
2 parents dd1a819 + 70b0331 commit 990dae7

14 files changed

+1757
-0
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ go 1.13
55
require (
66
github.com/davecgh/go-spew v1.1.1
77
github.com/google/go-cmp v0.3.1
8+
github.com/mitchellh/copystructure v1.2.0
9+
github.com/sebdah/goldie v1.0.0
810
github.com/zclconf/go-cty v1.2.1
911
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
2+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
23
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
34
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -8,6 +9,17 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
89
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
910
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1011
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
12+
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
13+
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
14+
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
15+
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
16+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
17+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
18+
github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc=
19+
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
20+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
21+
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
22+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
1123
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
1224
github.com/zclconf/go-cty v1.2.1 h1:vGMsygfmeCl4Xb6OA5U5XVAaQZ69FvoG7X2jUtQujb8=
1325
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=

sanitize/copy.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package sanitize
2+
3+
import (
4+
"reflect"
5+
6+
tfjson "github.com/hashicorp/terraform-json"
7+
"github.com/mitchellh/copystructure"
8+
)
9+
10+
// copyStructureCopy is an internal function that wraps copystructure.Copy with
11+
// a shallow copier for unknown values.
12+
//
13+
// Performing the shallow copy of the unknown values is important
14+
// here, as unknown values are parsed in with the main terraform-json
15+
// package as singletons, and must continue to be comparable.
16+
func copyStructureCopy(v interface{}) (interface{}, error) {
17+
c := &copystructure.Config{
18+
ShallowCopiers: map[reflect.Type]struct{}{
19+
reflect.TypeOf(tfjson.UnknownConstantValue): struct{}{},
20+
},
21+
}
22+
23+
return c.Copy(v)
24+
}
25+
26+
// copyChange copies a Change value and returns the copy.
27+
func copyChange(old *tfjson.Change) (*tfjson.Change, error) {
28+
c, err := copyStructureCopy(old)
29+
if err != nil {
30+
return nil, err
31+
}
32+
33+
return c.(*tfjson.Change), nil
34+
}
35+
36+
// copyPlan copies a Plan value and returns the copy.
37+
func copyPlan(old *tfjson.Plan) (*tfjson.Plan, error) {
38+
c, err := copyStructureCopy(old)
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
return c.(*tfjson.Plan), nil
44+
}
45+
46+
// copyPlanVariable copies a PlanVariable value and returns the copy.
47+
func copyPlanVariable(old *tfjson.PlanVariable) (*tfjson.PlanVariable, error) {
48+
c, err := copyStructureCopy(old)
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
return c.(*tfjson.PlanVariable), nil
54+
}
55+
56+
// copyStateResource copies a StateResource value and returns the copy.
57+
func copyStateResource(old *tfjson.StateResource) (*tfjson.StateResource, error) {
58+
c, err := copyStructureCopy(old)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
return c.(*tfjson.StateResource), nil
64+
}
65+
66+
// copyStateOutput copies a StateOutput value and returns the copy.
67+
func copyStateOutputs(old map[string]*tfjson.StateOutput) (map[string]*tfjson.StateOutput, error) {
68+
c, err := copystructure.Copy(old)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
return c.(map[string]*tfjson.StateOutput), nil
74+
}

sanitize/copy_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package sanitize
2+
3+
import (
4+
"testing"
5+
6+
tfjson "github.com/hashicorp/terraform-json"
7+
)
8+
9+
func TestCopyStructureCopy(t *testing.T) {
10+
in := tfjson.UnknownConstantValue
11+
out, err := copyStructureCopy(in)
12+
if err != nil {
13+
t.Fatal(err)
14+
}
15+
16+
if in != out {
17+
t.Fatal("did not shallow copy")
18+
}
19+
}

sanitize/sanitize_change.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package sanitize
2+
3+
import (
4+
tfjson "github.com/hashicorp/terraform-json"
5+
)
6+
7+
// SanitizeChange traverses a Change and replaces all values at
8+
// the particular locations marked by BeforeSensitive AfterSensitive
9+
// with the value supplied as replaceWith.
10+
//
11+
// A new change is issued.
12+
func SanitizeChange(old *tfjson.Change, replaceWith interface{}) (*tfjson.Change, error) {
13+
result, err := copyChange(old)
14+
if err != nil {
15+
return nil, err
16+
}
17+
18+
result.Before = sanitizeChangeValue(result.Before, result.BeforeSensitive, replaceWith)
19+
result.After = sanitizeChangeValue(result.After, result.AfterSensitive, replaceWith)
20+
21+
return result, nil
22+
}
23+
24+
func sanitizeChangeValue(old, sensitive, replaceWith interface{}) interface{} {
25+
// Only expect deep types that we would normally see in JSON, so
26+
// arrays and objects.
27+
switch x := old.(type) {
28+
case []interface{}:
29+
if filterSlice, ok := sensitive.([]interface{}); ok {
30+
for i := range filterSlice {
31+
if i >= len(x) {
32+
break
33+
}
34+
35+
x[i] = sanitizeChangeValue(x[i], filterSlice[i], replaceWith)
36+
}
37+
}
38+
case map[string]interface{}:
39+
if filterMap, ok := sensitive.(map[string]interface{}); ok {
40+
for filterKey := range filterMap {
41+
if value, ok := x[filterKey]; ok {
42+
x[filterKey] = sanitizeChangeValue(value, filterMap[filterKey], replaceWith)
43+
}
44+
}
45+
}
46+
}
47+
48+
if shouldFilter, ok := sensitive.(bool); ok && shouldFilter {
49+
return replaceWith
50+
}
51+
52+
return old
53+
}

sanitize/sanitize_change_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package sanitize
2+
3+
import (
4+
"testing"
5+
6+
"github.com/google/go-cmp/cmp"
7+
tfjson "github.com/hashicorp/terraform-json"
8+
)
9+
10+
type testChangeCase struct {
11+
name string
12+
old *tfjson.Change
13+
expected *tfjson.Change
14+
}
15+
16+
func changeCases() []testChangeCase {
17+
return []testChangeCase{
18+
{
19+
name: "basic",
20+
old: &tfjson.Change{
21+
Before: map[string]interface{}{
22+
"foo": map[string]interface{}{"a": "foo"},
23+
"bar": map[string]interface{}{"a": "foo"},
24+
"baz": map[string]interface{}{"a": "foo"},
25+
"qux": map[string]interface{}{
26+
"a": map[string]interface{}{
27+
"b": "foo",
28+
},
29+
"c": "bar",
30+
},
31+
"quxx": map[string]interface{}{
32+
"a": map[string]interface{}{
33+
"b": "foo",
34+
},
35+
"c": "bar",
36+
},
37+
},
38+
After: map[string]interface{}{
39+
"one": map[string]interface{}{"x": "one"},
40+
"two": map[string]interface{}{"x": "one"},
41+
"three": map[string]interface{}{"x": "one"},
42+
"four": map[string]interface{}{
43+
"x": map[string]interface{}{
44+
"y": "one",
45+
},
46+
"z": "two",
47+
},
48+
"five": map[string]interface{}{
49+
"x": map[string]interface{}{
50+
"y": "one",
51+
},
52+
"z": "two",
53+
},
54+
},
55+
BeforeSensitive: map[string]interface{}{
56+
"foo": map[string]interface{}{},
57+
"bar": true,
58+
"baz": map[string]interface{}{"a": true},
59+
"qux": map[string]interface{}{},
60+
"quxx": map[string]interface{}{"c": true},
61+
},
62+
AfterSensitive: map[string]interface{}{
63+
"one": map[string]interface{}{},
64+
"two": true,
65+
"three": map[string]interface{}{"x": true},
66+
"four": map[string]interface{}{},
67+
"five": map[string]interface{}{"z": true},
68+
},
69+
},
70+
expected: &tfjson.Change{
71+
Before: map[string]interface{}{
72+
"foo": map[string]interface{}{"a": "foo"},
73+
"bar": DefaultSensitiveValue,
74+
"baz": map[string]interface{}{"a": DefaultSensitiveValue},
75+
"qux": map[string]interface{}{
76+
"a": map[string]interface{}{
77+
"b": "foo",
78+
},
79+
"c": "bar",
80+
},
81+
"quxx": map[string]interface{}{
82+
"a": map[string]interface{}{
83+
"b": "foo",
84+
},
85+
"c": DefaultSensitiveValue,
86+
},
87+
},
88+
After: map[string]interface{}{
89+
"one": map[string]interface{}{"x": "one"},
90+
"two": DefaultSensitiveValue,
91+
"three": map[string]interface{}{"x": DefaultSensitiveValue},
92+
"four": map[string]interface{}{
93+
"x": map[string]interface{}{
94+
"y": "one",
95+
},
96+
"z": "two",
97+
},
98+
"five": map[string]interface{}{
99+
"x": map[string]interface{}{
100+
"y": "one",
101+
},
102+
"z": DefaultSensitiveValue,
103+
},
104+
},
105+
BeforeSensitive: map[string]interface{}{
106+
"foo": map[string]interface{}{},
107+
"bar": true,
108+
"baz": map[string]interface{}{"a": true},
109+
"qux": map[string]interface{}{},
110+
"quxx": map[string]interface{}{"c": true},
111+
},
112+
AfterSensitive: map[string]interface{}{
113+
"one": map[string]interface{}{},
114+
"two": true,
115+
"three": map[string]interface{}{"x": true},
116+
"four": map[string]interface{}{},
117+
"five": map[string]interface{}{"z": true},
118+
},
119+
},
120+
},
121+
}
122+
}
123+
124+
func TestSanitizeChange(t *testing.T) {
125+
for i, tc := range changeCases() {
126+
tc := tc
127+
t.Run(tc.name, func(t *testing.T) {
128+
actual, err := SanitizeChange(tc.old, DefaultSensitiveValue)
129+
if err != nil {
130+
t.Fatal(err)
131+
}
132+
133+
if diff := cmp.Diff(tc.expected, actual); diff != "" {
134+
t.Errorf("SanitizeChange() mismatch (-expected +actual):\n%s", diff)
135+
}
136+
137+
if diff := cmp.Diff(changeCases()[i].old, tc.old); diff != "" {
138+
t.Errorf("SanitizeChange() altered original (-expected +actual):\n%s", diff)
139+
}
140+
})
141+
}
142+
}

0 commit comments

Comments
 (0)