-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from hashicorp/sensitive-values
sanitize: allow for the sanitization of sensitive values
- Loading branch information
Showing
14 changed files
with
1,757 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package sanitize | ||
|
||
import ( | ||
"reflect" | ||
|
||
tfjson "github.com/hashicorp/terraform-json" | ||
"github.com/mitchellh/copystructure" | ||
) | ||
|
||
// copyStructureCopy is an internal function that wraps copystructure.Copy with | ||
// a shallow copier for unknown values. | ||
// | ||
// Performing the shallow copy of the unknown values is important | ||
// here, as unknown values are parsed in with the main terraform-json | ||
// package as singletons, and must continue to be comparable. | ||
func copyStructureCopy(v interface{}) (interface{}, error) { | ||
c := ©structure.Config{ | ||
ShallowCopiers: map[reflect.Type]struct{}{ | ||
reflect.TypeOf(tfjson.UnknownConstantValue): struct{}{}, | ||
}, | ||
} | ||
|
||
return c.Copy(v) | ||
} | ||
|
||
// copyChange copies a Change value and returns the copy. | ||
func copyChange(old *tfjson.Change) (*tfjson.Change, error) { | ||
c, err := copyStructureCopy(old) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c.(*tfjson.Change), nil | ||
} | ||
|
||
// copyPlan copies a Plan value and returns the copy. | ||
func copyPlan(old *tfjson.Plan) (*tfjson.Plan, error) { | ||
c, err := copyStructureCopy(old) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c.(*tfjson.Plan), nil | ||
} | ||
|
||
// copyPlanVariable copies a PlanVariable value and returns the copy. | ||
func copyPlanVariable(old *tfjson.PlanVariable) (*tfjson.PlanVariable, error) { | ||
c, err := copyStructureCopy(old) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c.(*tfjson.PlanVariable), nil | ||
} | ||
|
||
// copyStateResource copies a StateResource value and returns the copy. | ||
func copyStateResource(old *tfjson.StateResource) (*tfjson.StateResource, error) { | ||
c, err := copyStructureCopy(old) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c.(*tfjson.StateResource), nil | ||
} | ||
|
||
// copyStateOutput copies a StateOutput value and returns the copy. | ||
func copyStateOutputs(old map[string]*tfjson.StateOutput) (map[string]*tfjson.StateOutput, error) { | ||
c, err := copystructure.Copy(old) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return c.(map[string]*tfjson.StateOutput), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package sanitize | ||
|
||
import ( | ||
"testing" | ||
|
||
tfjson "github.com/hashicorp/terraform-json" | ||
) | ||
|
||
func TestCopyStructureCopy(t *testing.T) { | ||
in := tfjson.UnknownConstantValue | ||
out, err := copyStructureCopy(in) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if in != out { | ||
t.Fatal("did not shallow copy") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package sanitize | ||
|
||
import ( | ||
tfjson "github.com/hashicorp/terraform-json" | ||
) | ||
|
||
// SanitizeChange traverses a Change and replaces all values at | ||
// the particular locations marked by BeforeSensitive AfterSensitive | ||
// with the value supplied as replaceWith. | ||
// | ||
// A new change is issued. | ||
func SanitizeChange(old *tfjson.Change, replaceWith interface{}) (*tfjson.Change, error) { | ||
result, err := copyChange(old) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
result.Before = sanitizeChangeValue(result.Before, result.BeforeSensitive, replaceWith) | ||
result.After = sanitizeChangeValue(result.After, result.AfterSensitive, replaceWith) | ||
|
||
return result, nil | ||
} | ||
|
||
func sanitizeChangeValue(old, sensitive, replaceWith interface{}) interface{} { | ||
// Only expect deep types that we would normally see in JSON, so | ||
// arrays and objects. | ||
switch x := old.(type) { | ||
case []interface{}: | ||
if filterSlice, ok := sensitive.([]interface{}); ok { | ||
for i := range filterSlice { | ||
if i >= len(x) { | ||
break | ||
} | ||
|
||
x[i] = sanitizeChangeValue(x[i], filterSlice[i], replaceWith) | ||
} | ||
} | ||
case map[string]interface{}: | ||
if filterMap, ok := sensitive.(map[string]interface{}); ok { | ||
for filterKey := range filterMap { | ||
if value, ok := x[filterKey]; ok { | ||
x[filterKey] = sanitizeChangeValue(value, filterMap[filterKey], replaceWith) | ||
} | ||
} | ||
} | ||
} | ||
|
||
if shouldFilter, ok := sensitive.(bool); ok && shouldFilter { | ||
return replaceWith | ||
} | ||
|
||
return old | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package sanitize | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
tfjson "github.com/hashicorp/terraform-json" | ||
) | ||
|
||
type testChangeCase struct { | ||
name string | ||
old *tfjson.Change | ||
expected *tfjson.Change | ||
} | ||
|
||
func changeCases() []testChangeCase { | ||
return []testChangeCase{ | ||
{ | ||
name: "basic", | ||
old: &tfjson.Change{ | ||
Before: map[string]interface{}{ | ||
"foo": map[string]interface{}{"a": "foo"}, | ||
"bar": map[string]interface{}{"a": "foo"}, | ||
"baz": map[string]interface{}{"a": "foo"}, | ||
"qux": map[string]interface{}{ | ||
"a": map[string]interface{}{ | ||
"b": "foo", | ||
}, | ||
"c": "bar", | ||
}, | ||
"quxx": map[string]interface{}{ | ||
"a": map[string]interface{}{ | ||
"b": "foo", | ||
}, | ||
"c": "bar", | ||
}, | ||
}, | ||
After: map[string]interface{}{ | ||
"one": map[string]interface{}{"x": "one"}, | ||
"two": map[string]interface{}{"x": "one"}, | ||
"three": map[string]interface{}{"x": "one"}, | ||
"four": map[string]interface{}{ | ||
"x": map[string]interface{}{ | ||
"y": "one", | ||
}, | ||
"z": "two", | ||
}, | ||
"five": map[string]interface{}{ | ||
"x": map[string]interface{}{ | ||
"y": "one", | ||
}, | ||
"z": "two", | ||
}, | ||
}, | ||
BeforeSensitive: map[string]interface{}{ | ||
"foo": map[string]interface{}{}, | ||
"bar": true, | ||
"baz": map[string]interface{}{"a": true}, | ||
"qux": map[string]interface{}{}, | ||
"quxx": map[string]interface{}{"c": true}, | ||
}, | ||
AfterSensitive: map[string]interface{}{ | ||
"one": map[string]interface{}{}, | ||
"two": true, | ||
"three": map[string]interface{}{"x": true}, | ||
"four": map[string]interface{}{}, | ||
"five": map[string]interface{}{"z": true}, | ||
}, | ||
}, | ||
expected: &tfjson.Change{ | ||
Before: map[string]interface{}{ | ||
"foo": map[string]interface{}{"a": "foo"}, | ||
"bar": DefaultSensitiveValue, | ||
"baz": map[string]interface{}{"a": DefaultSensitiveValue}, | ||
"qux": map[string]interface{}{ | ||
"a": map[string]interface{}{ | ||
"b": "foo", | ||
}, | ||
"c": "bar", | ||
}, | ||
"quxx": map[string]interface{}{ | ||
"a": map[string]interface{}{ | ||
"b": "foo", | ||
}, | ||
"c": DefaultSensitiveValue, | ||
}, | ||
}, | ||
After: map[string]interface{}{ | ||
"one": map[string]interface{}{"x": "one"}, | ||
"two": DefaultSensitiveValue, | ||
"three": map[string]interface{}{"x": DefaultSensitiveValue}, | ||
"four": map[string]interface{}{ | ||
"x": map[string]interface{}{ | ||
"y": "one", | ||
}, | ||
"z": "two", | ||
}, | ||
"five": map[string]interface{}{ | ||
"x": map[string]interface{}{ | ||
"y": "one", | ||
}, | ||
"z": DefaultSensitiveValue, | ||
}, | ||
}, | ||
BeforeSensitive: map[string]interface{}{ | ||
"foo": map[string]interface{}{}, | ||
"bar": true, | ||
"baz": map[string]interface{}{"a": true}, | ||
"qux": map[string]interface{}{}, | ||
"quxx": map[string]interface{}{"c": true}, | ||
}, | ||
AfterSensitive: map[string]interface{}{ | ||
"one": map[string]interface{}{}, | ||
"two": true, | ||
"three": map[string]interface{}{"x": true}, | ||
"four": map[string]interface{}{}, | ||
"five": map[string]interface{}{"z": true}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func TestSanitizeChange(t *testing.T) { | ||
for i, tc := range changeCases() { | ||
tc := tc | ||
t.Run(tc.name, func(t *testing.T) { | ||
actual, err := SanitizeChange(tc.old, DefaultSensitiveValue) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if diff := cmp.Diff(tc.expected, actual); diff != "" { | ||
t.Errorf("SanitizeChange() mismatch (-expected +actual):\n%s", diff) | ||
} | ||
|
||
if diff := cmp.Diff(changeCases()[i].old, tc.old); diff != "" { | ||
t.Errorf("SanitizeChange() altered original (-expected +actual):\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.