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") } }) } }