package configkit import ( "encoding/json" "errors" "fmt" "io" "io/fs" "os" "path/filepath" "reflect" "runtime" "strings" "testing" ) func TestLoadSecretsDirLoadsCanonicalSortedValues(t *testing.T) { directory := t.TempDir() writeSecretTestFile(t, directory, "CONFIGKIT_SECRET_Z", "last\n\n") writeSecretTestFile(t, directory, "CONFIGKIT_SECRET_A", "first\r\n") writeSecretTestFile(t, directory, "CONFIGKIT_SECRET_EMPTY", "") selected := directory if runtime.GOOS != "windows" { selected = filepath.Join(t.TempDir(), "selected") if err := os.Symlink(directory, selected); err != nil { t.Fatalf("Symlink() error = %v", err) } } for _, name := range []string{"CONFIGKIT_SECRET_A", "CONFIGKIT_SECRET_EMPTY", "CONFIGKIT_SECRET_Z"} { t.Setenv(name, "temporary") if err := os.Unsetenv(name); err != nil { t.Fatalf("Unsetenv(%q) error = %v", name, err) } } report, err := LoadSecretsDir(selected) if err != nil { t.Fatalf("LoadSecretsDir() error = %v", err) } if got := os.Getenv("CONFIGKIT_SECRET_A"); got != "first" { t.Fatalf("LF value = %q, want first", got) } if got := os.Getenv("CONFIGKIT_SECRET_Z"); got != "last\n" { t.Fatalf("two-LF value = %q, want one retained LF", got) } if value, present := os.LookupEnv("CONFIGKIT_SECRET_EMPTY"); !present || value != "" { t.Fatalf("empty value = %q, %v; want present empty", value, present) } canonical := canonicalTestPath(t, directory) want := []SecretFileResult{ {Name: "CONFIGKIT_SECRET_A", Path: filepath.Join(canonical, "CONFIGKIT_SECRET_A"), Status: SecretFileLoaded}, {Name: "CONFIGKIT_SECRET_EMPTY", Path: filepath.Join(canonical, "CONFIGKIT_SECRET_EMPTY"), Status: SecretFileLoaded}, {Name: "CONFIGKIT_SECRET_Z", Path: filepath.Join(canonical, "CONFIGKIT_SECRET_Z"), Status: SecretFileLoaded}, } if got := report.Entries(); !reflect.DeepEqual(got, want) { t.Fatalf("Entries() = %#v, want %#v", got, want) } entries := report.Entries() entries[0].Name = "mutated" if report.Entries()[0].Name != want[0].Name { t.Fatal("Entries() did not return a detached slice") } } func TestLoadSecretsDirPreservesExistingWithoutReading(t *testing.T) { directory := t.TempDir() writeSecretTestFile(t, directory, "EMPTY_WINNER", strings.Repeat("CANARY", int(secretFileByteLimit))) writeSecretTestFile(t, directory, "VALUE_WINNER", "CANARY-CONTENT") environment := map[string]string{"EMPTY_WINNER": "", "VALUE_WINNER": "existing"} opened := 0 access := testSecretsAccess() access.openFile = func(string) (*os.File, error) { opened++ return nil, errors.New("must not open") } report, err := loadSecretsDir(directory, mapSecretsEnvironment(environment), access) if err != nil { t.Fatalf("loadSecretsDir() error = %v", err) } if opened != 0 { t.Fatalf("opened skipped files %d times", opened) } want := []SecretFileResult{ {Name: "EMPTY_WINNER", Path: filepath.Join(canonicalTestPath(t, directory), "EMPTY_WINNER"), Status: SecretFileSkippedExisting, ExistingEmpty: true}, {Name: "VALUE_WINNER", Path: filepath.Join(canonicalTestPath(t, directory), "VALUE_WINNER"), Status: SecretFileSkippedExisting}, } if got := report.Entries(); !reflect.DeepEqual(got, want) { t.Fatalf("Entries() = %#v, want %#v", got, want) } } func TestLoadSecretsDirValidatesAllMetadataBeforeReadingOrWriting(t *testing.T) { for _, test := range []struct { name string make func(*testing.T, string) code Code }{ {name: "invalid name", make: func(t *testing.T, directory string) { writeSecretTestFile(t, directory, "1INVALID", "CANARY") }, code: CodeSecretsInvalidName}, {name: "directory", make: func(t *testing.T, directory string) { if err := os.Mkdir(filepath.Join(directory, "BROKEN"), 0o700); err != nil { t.Fatal(err) } }, code: CodeSecretsNotRegular}, {name: "symlink", make: func(t *testing.T, directory string) { if runtime.GOOS == "windows" { t.Skip("symlink setup differs on Windows") } writeSecretTestFile(t, directory, "TARGET", "CANARY") if err := os.Symlink(filepath.Join(directory, "TARGET"), filepath.Join(directory, "BROKEN")); err != nil { t.Fatal(err) } }, code: CodeSecretsNotRegular}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() writeSecretTestFile(t, directory, "A_VALID", "first") test.make(t, directory) writes := 0 access := testSecretsAccess() opened := 0 access.openFile = func(path string) (*os.File, error) { opened++; return os.Open(path) } report, err := loadSecretsDir(directory, secretsEnvironment{ lookup: func(string) (string, bool) { return "", false }, set: func(string, string) error { writes++; return nil }, }, access) requireSecretsCode(t, err, test.code) if writes != 0 || opened != 0 { t.Fatalf("writes = %d, opens = %d; want no preflight activity", writes, opened) } if report == nil || len(report.Entries()) < 2 { t.Fatalf("report = %#v, want enumerated entries", report) } }) } } func TestLoadSecretsDirPreflightFailuresDoNotWrite(t *testing.T) { for _, test := range []struct { name string body string code Code }{ {name: "file limit", body: strings.Repeat("x", int(secretFileByteLimit)+1), code: CodeSecretsLimitExceeded}, {name: "NUL", body: "before\x00CANARY", code: CodeSecretsInvalidValue}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() writeSecretTestFile(t, directory, "A_FIRST", "first") writeSecretTestFile(t, directory, "Z_FAILED", test.body) writes := 0 report, err := loadSecretsDir(directory, secretsEnvironment{ lookup: func(string) (string, bool) { return "", false }, set: func(string, string) error { writes++; return nil }, }, testSecretsAccess()) requireSecretsCode(t, err, test.code) if writes != 0 { t.Fatalf("writes = %d, want zero", writes) } entries := report.Entries() if entries[0].Status != SecretFileNotApplied || entries[1].Status != SecretFileFailed { t.Fatalf("statuses = %#v", entries) } }) } } func TestLoadSecretsDirClassifiesReadFailuresWithoutLeakingThem(t *testing.T) { for _, test := range []struct { name string err error code Code }{ {name: "permission", err: fs.ErrPermission, code: CodeSecretsPermission}, {name: "missing", err: fs.ErrNotExist, code: CodeSecretsMissing}, {name: "other", err: errors.New("READ-ERROR-CANARY"), code: CodeSecretsRead}, } { t.Run(test.name, func(t *testing.T) { directory := t.TempDir() writeSecretTestFile(t, directory, "SECRET", "CREDENTIAL-CANARY") access := testSecretsAccess() access.openFile = func(string) (*os.File, error) { return nil, test.err } writes := 0 report, err := loadSecretsDir(directory, secretsEnvironment{ lookup: func(string) (string, bool) { return "", false }, set: func(string, string) error { writes++; return nil }, }, access) requireSecretsCode(t, err, test.code) if writes != 0 || report.Entries()[0].Status != SecretFileFailed { t.Fatalf("writes = %d, entries = %#v", writes, report.Entries()) } if strings.Contains(fmt.Sprintf("%v %#v", err, err), "CANARY") { t.Fatalf("error leaked underlying failure: %v %#v", err, err) } }) } } func TestLoadSecretsDirEnforcesEnumerationAndTotalBounds(t *testing.T) { t.Run("entries", func(t *testing.T) { directory := t.TempDir() for index := 0; index <= secretsEntryLimit; index++ { writeSecretTestFile(t, directory, fmt.Sprintf("SECRET_%04d", index), "") } report, err := loadSecretsDir(directory, mapSecretsEnvironment(map[string]string{}), testSecretsAccess()) requireSecretsCode(t, err, CodeSecretsLimitExceeded) if len(report.Entries()) != secretsEntryLimit+1 { t.Fatalf("entries = %d, want %d bounded enumerated entries", len(report.Entries()), secretsEntryLimit+1) } }) t.Run("total bytes", func(t *testing.T) { directory := t.TempDir() for index := 0; index < 9; index++ { writeSecretTestFile(t, directory, fmt.Sprintf("SECRET_%02d", index), strings.Repeat("x", int(secretFileByteLimit))) } writes := 0 report, err := loadSecretsDir(directory, secretsEnvironment{ lookup: func(string) (string, bool) { return "", false }, set: func(string, string) error { writes++; return nil }, }, testSecretsAccess()) requireSecretsCode(t, err, CodeSecretsLimitExceeded) if writes != 0 || report.Entries()[8].Status != SecretFileFailed { t.Fatalf("writes = %d, final status = %q", writes, report.Entries()[8].Status) } }) } func TestLoadSecretsDirApplyReportsPartialFailureAndPresenceRace(t *testing.T) { directory := t.TempDir() for _, name := range []string{"A_LOADED", "B_RACED", "C_FAILED", "D_WAITING"} { writeSecretTestFile(t, directory, name, "CANARY-"+name) } values := map[string]string{} lookups := map[string]int{} environment := secretsEnvironment{ lookup: func(name string) (string, bool) { lookups[name]++ if name == "B_RACED" && lookups[name] == 2 { values[name] = "" } value, present := values[name] return value, present }, set: func(name, value string) error { if name == "C_FAILED" { return errors.New("CANARY set error") } values[name] = value return nil }, } report, err := loadSecretsDir(directory, environment, testSecretsAccess()) requireSecretsCode(t, err, CodeSecretsApplyFailed) entries := report.Entries() wantStatuses := []SecretFileStatus{SecretFileLoaded, SecretFileSkippedExisting, SecretFileFailed, SecretFileNotApplied} for index, want := range wantStatuses { if entries[index].Status != want { t.Fatalf("entry %d status = %q, want %q", index, entries[index].Status, want) } } if !entries[1].ExistingEmpty || values["A_LOADED"] != "CANARY-A_LOADED" { t.Fatalf("partial report/state = %#v, %#v", entries, values) } if _, present := values["D_WAITING"]; present { t.Fatal("entry after apply failure was written") } } func TestLoadSecretsDirFailuresAreSafeAndDetached(t *testing.T) { directory := t.TempDir() writeSecretTestFile(t, directory, "SECRET", "CREDENTIAL-CANARY\x00") report, err := loadSecretsDir(directory, mapSecretsEnvironment(map[string]string{}), testSecretsAccess()) secretsErr := requireSecretsCode(t, err, CodeSecretsInvalidValue) diagnostics := secretsErr.Diagnostics() diagnostics[0].Message = "mutated" if secretsErr.Diagnostics()[0].Message == "mutated" { t.Fatal("Diagnostics() did not return a detached slice") } var rendered strings.Builder if _, writeErr := report.WriteTo(&rendered); writeErr != nil { t.Fatal(writeErr) } jsonReport, marshalErr := json.Marshal(report) if marshalErr != nil { t.Fatal(marshalErr) } outputs := []string{err.Error(), fmt.Sprintf("%v %#v", err, err), fmt.Sprintf("%v %#v", report, report), rendered.String(), string(jsonReport)} for _, output := range outputs { if strings.Contains(output, "CREDENTIAL-CANARY") { t.Fatalf("safe output leaked content: %q", output) } } if secretsErr.Diagnostics()[0].Primary != nil || secretsErr.Diagnostics()[0].Path.String() != "" { t.Fatalf("bootstrap diagnostic fabricated source location/path: %#v", secretsErr.Diagnostics()[0]) } } func TestSecretsReportWriteToPropagatesWriterFailures(t *testing.T) { report := &SecretsReport{entries: []SecretFileResult{{Name: "A\nB", Path: "/tmp/A\nB", Status: SecretFileLoaded}}} var output strings.Builder written, err := report.WriteTo(&output) if err != nil || written != int64(output.Len()) || strings.Contains(output.String(), "A\nB") || !strings.Contains(output.String(), `A\nB`) { t.Fatalf("WriteTo() = (%d, %v), output %q", written, err, output.String()) } failure := errors.New("writer CANARY") written, err = report.WriteTo(errorAfterWriter{limit: 5, err: failure}) if written != 5 || !errors.Is(err, failure) { t.Fatalf("failing WriteTo() = (%d, %v)", written, err) } written, err = report.WriteTo(shortWriter{}) if written == 0 || !errors.Is(err, io.ErrShortWrite) { t.Fatalf("short WriteTo() = (%d, %v)", written, err) } } func TestLoadSecretsDirRejectsMissingAndInvalidDirectory(t *testing.T) { report, err := LoadSecretsDir("") requireSecretsCode(t, err, CodeSecretsInvalidPath) if report == nil || len(report.Entries()) != 0 { t.Fatalf("empty path report = %#v", report) } report, err = LoadSecretsDir(filepath.Join(t.TempDir(), "missing")) requireSecretsCode(t, err, CodeSecretsMissing) if report == nil || len(report.Entries()) != 0 { t.Fatalf("missing path report = %#v", report) } file := filepath.Join(t.TempDir(), "FILE") if err := os.WriteFile(file, nil, 0o600); err != nil { t.Fatal(err) } _, err = LoadSecretsDir(file) requireSecretsCode(t, err, CodeSecretsInvalidPath) } func testSecretsAccess() secretsAccess { return secretsAccess{openDirectory: os.Open, lstat: os.Lstat, openFile: os.Open} } func mapSecretsEnvironment(values map[string]string) secretsEnvironment { return secretsEnvironment{ lookup: func(name string) (string, bool) { value, present := values[name]; return value, present }, set: func(name, value string) error { values[name] = value; return nil }, } } func writeSecretTestFile(t *testing.T, directory, name, value string) { t.Helper() if err := os.WriteFile(filepath.Join(directory, name), []byte(value), 0o600); err != nil { t.Fatalf("WriteFile(%q) error = %v", name, err) } } func requireSecretsCode(t *testing.T, err error, code Code) *SecretsError { t.Helper() var secretsErr *SecretsError if !errors.As(err, &secretsErr) { t.Fatalf("error = %T %v, want *SecretsError", err, err) } diagnostics := secretsErr.Diagnostics() if len(diagnostics) != 1 || diagnostics[0].Code != code { t.Fatalf("diagnostics = %#v, want code %q", diagnostics, code) } return secretsErr } type errorAfterWriter struct { limit int err error } func (w errorAfterWriter) Write(data []byte) (int, error) { return min(w.limit, len(data)), w.err }