-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
sanitize: allow for the sanitization of sensitive values
This adds a new package and functions for sanitization of values marked as sensitive in the plan, where we can get particular data to do it. This data is derived in a number of ways, also documented in the top-level SanitizePlan function: * ResourceChanges are sanitized based on BeforeSensitive and AfterSensitive fields. * Variables are sanitized based on variable config data found in the root module of the Config. * PlannedValues are sanitized based on the values found in AfterSensitive in ResourceChanges. Outputs are sanitized according to the appropriate sensitivity flags provided for the output. * PriorState is sanitized based on the values found in BeforeSensitive in ResourceChanges. Outputs are sanitized according to the appropriate sensitivity flags provided for the output. * OutputChanges are sanitized based on the values found in BeforeSensitive and AfterSensitive. This generally means that any sensitive output will have OutputChange fully obfuscated as the BeforeSensitive and AfterSensitive in outputs are opaquely the same.
- Loading branch information
1 parent
dd1a819
commit 1a30f72
Showing
13 changed files
with
1,738 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,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.