Add explicit secrets directory bootstrap
This commit is contained in:
377
secrets_test.go
Normal file
377
secrets_test.go
Normal file
@@ -0,0 +1,377 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user