138 lines
5.0 KiB
Go
138 lines
5.0 KiB
Go
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestSecretResolutionAndMutationAuditCoverTypedContainers(t *testing.T) {
|
|
t.Parallel()
|
|
type record struct {
|
|
Token Secret `yaml:"token"`
|
|
}
|
|
type inline struct {
|
|
Token Secret `yaml:"inline_token"`
|
|
}
|
|
type config struct {
|
|
Inline *inline `yaml:",inline"`
|
|
Pointer *Secret `yaml:"pointer"`
|
|
Indirect **Secret `yaml:"indirect"`
|
|
Values map[string]Secret `yaml:"values"`
|
|
Pointers map[string]*Secret `yaml:"pointers"`
|
|
Records map[string]record `yaml:"records"`
|
|
PointerRecords map[string]*record `yaml:"pointer_records"`
|
|
Slice []record `yaml:"slice"`
|
|
Array [1]Secret `yaml:"array"`
|
|
Absent *Secret `yaml:"absent"`
|
|
NilMap map[string]Secret `yaml:"nil_map"`
|
|
NilSlice []Secret `yaml:"nil_slice"`
|
|
}
|
|
const input = `inline_token: WALK_SECRET
|
|
pointer: WALK_SECRET
|
|
indirect: WALK_SECRET
|
|
values: {"a/b~c": WALK_SECRET}
|
|
pointers: {entry: WALK_SECRET, absent: null}
|
|
records: {entry: {token: WALK_SECRET}}
|
|
pointer_records: {entry: {token: WALK_SECRET}, absent: null}
|
|
slice: [{token: WALK_SECRET}]
|
|
array: [WALK_SECRET]
|
|
`
|
|
assertResolved := func(t *testing.T, value *config) {
|
|
t.Helper()
|
|
if value.Inline == nil || value.Pointer == nil || value.Indirect == nil || *value.Indirect == nil ||
|
|
value.Pointers["entry"] == nil || value.PointerRecords["entry"] == nil || len(value.Slice) != 1 {
|
|
t.Fatal("typed secret containers were not decoded")
|
|
}
|
|
for _, reference := range []Secret{
|
|
value.Inline.Token, *value.Pointer, **value.Indirect, value.Values["a/b~c"],
|
|
*value.Pointers["entry"], value.Records["entry"].Token, value.PointerRecords["entry"].Token,
|
|
value.Slice[0].Token, value.Array[0],
|
|
} {
|
|
if !reference.IsResolved() || reference.EnvName() != "WALK_SECRET" || reference.Value() != "WALK-CREDENTIAL-CANARY" {
|
|
t.Fatal("a typed secret did not retain its resolved state")
|
|
}
|
|
}
|
|
if value.Absent != nil || value.NilMap != nil || value.NilSlice != nil ||
|
|
value.Pointers["absent"] != nil || value.PointerRecords["absent"] != nil {
|
|
t.Fatal("nil secret containers changed")
|
|
}
|
|
}
|
|
for _, test := range []struct {
|
|
name string
|
|
path string
|
|
mutate func(*config)
|
|
}{
|
|
{name: "read-only validation"},
|
|
{name: "inline pointer", path: "/inline_token", mutate: func(value *config) {
|
|
value.Inline.Token = SecretEnv(value.Inline.Token.EnvName())
|
|
}},
|
|
{name: "pointer", path: "/pointer", mutate: func(value *config) {
|
|
*value.Pointer = SecretEnv(value.Pointer.EnvName())
|
|
}},
|
|
{name: "pointer chain", path: "/indirect", mutate: func(value *config) {
|
|
**value.Indirect = SecretEnv((**value.Indirect).EnvName())
|
|
}},
|
|
{name: "map value", path: "/values/a~1b~0c", mutate: func(value *config) {
|
|
value.Values["a/b~c"] = SecretEnv(value.Values["a/b~c"].EnvName())
|
|
}},
|
|
{name: "map pointer", path: "/pointers/entry", mutate: func(value *config) {
|
|
*value.Pointers["entry"] = SecretEnv(value.Pointers["entry"].EnvName())
|
|
}},
|
|
{name: "map record", path: "/records/entry/token", mutate: func(value *config) {
|
|
entry := value.Records["entry"]
|
|
entry.Token = SecretEnv(entry.Token.EnvName())
|
|
value.Records["entry"] = entry
|
|
}},
|
|
{name: "map record pointer", path: "/pointer_records/entry/token", mutate: func(value *config) {
|
|
entry := value.PointerRecords["entry"]
|
|
entry.Token = SecretEnv(entry.Token.EnvName())
|
|
}},
|
|
{name: "slice record", path: "/slice/0/token", mutate: func(value *config) {
|
|
value.Slice[0].Token = SecretEnv(value.Slice[0].Token.EnvName())
|
|
}},
|
|
{name: "array", path: "/array/0", mutate: func(value *config) {
|
|
value.Array[0] = SecretEnv(value.Array[0].EnvName())
|
|
}},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
lookups := 0
|
|
validated := false
|
|
result, err := testLoadWithSecretLookup(Bytes("secret-containers.yml", []byte(input)), func(name string) (string, bool) {
|
|
lookups++
|
|
if name != "WALK_SECRET" {
|
|
t.Fatalf("unexpected secret name %q", name)
|
|
}
|
|
return "WALK-CREDENTIAL-CANARY", true
|
|
}, Options[config]{Definition: Definition[config]{Validate: func(value *config, _ *Validation) {
|
|
validated = true
|
|
assertResolved(t, value)
|
|
if test.mutate != nil {
|
|
test.mutate(value)
|
|
}
|
|
}}})
|
|
if !validated || lookups != 1 {
|
|
t.Fatalf("validation ran = %v, lookups = %d", validated, lookups)
|
|
}
|
|
if test.mutate == nil {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
assertResolved(t, &result.Config)
|
|
return
|
|
}
|
|
if result != nil {
|
|
t.Fatal("validator mutation published a result")
|
|
}
|
|
loadError := requireSecretLoadCode(t, err, CodeValidateMutation)
|
|
diagnostics := loadError.Diagnostics()
|
|
if len(diagnostics) != 1 || diagnostics[0].Path.String() != test.path {
|
|
t.Fatalf("mutation diagnostics = %#v, want one at %s", diagnostics, test.path)
|
|
}
|
|
if strings.Contains(fmt.Sprintf("%v %#v", err, err), "WALK-CREDENTIAL-CANARY") {
|
|
t.Fatal("validator mutation error exposed a credential")
|
|
}
|
|
})
|
|
}
|
|
}
|