//go:build linux package securefile import ( "bytes" "errors" "os" "path/filepath" "testing" ) func TestOpenDirAndReadFileAcceptProtectedRegularFile(t *testing.T) { root := t.TempDir() path := filepath.Join(root, "record.json") want := []byte(`{"record":"synthetic"}`) writeFile(t, path, want, 0o640) dir, err := OpenDir(root) if err != nil { t.Fatalf("OpenDir() error = %v", err) } t.Cleanup(func() { _ = dir.Close() }) got, err := dir.ReadFile("record.json", 4096) if err != nil { t.Fatalf("ReadFile() error = %v", err) } if !bytes.Equal(got, want) { t.Fatalf("ReadFile() = %q, want %q", got, want) } } func TestProtectedPathsRejectSymlinks(t *testing.T) { t.Run("root", func(t *testing.T) { parent := t.TempDir() target := t.TempDir() root := filepath.Join(parent, "registry") if err := os.Symlink(target, root); err != nil { t.Fatalf("Symlink() error = %v", err) } if _, err := OpenDir(root); err == nil { t.Fatal("OpenDir() error = nil, want symlink refusal") } }) t.Run("directory", func(t *testing.T) { root := t.TempDir() target := t.TempDir() if err := os.Symlink(target, filepath.Join(root, "active")); err != nil { t.Fatalf("Symlink() error = %v", err) } dir, err := OpenDir(root) if err != nil { t.Fatalf("OpenDir() error = %v", err) } t.Cleanup(func() { _ = dir.Close() }) if _, err := dir.OpenDir("active"); err == nil { t.Fatal("OpenDir(active) error = nil, want symlink refusal") } }) t.Run("record", func(t *testing.T) { root := t.TempDir() dir, err := OpenDir(root) if err != nil { t.Fatalf("OpenDir() error = %v", err) } t.Cleanup(func() { _ = dir.Close() }) target := filepath.Join(root, "target.json") writeFile(t, target, []byte(`{"safe":false}`), 0o640) if err := os.Symlink(target, filepath.Join(root, "record.json")); err != nil { t.Fatalf("Symlink() error = %v", err) } if _, err := dir.ReadFile("record.json", 4096); err == nil { t.Fatal("ReadFile() error = nil, want symlink refusal") } }) t.Run("secret", func(t *testing.T) { root := t.TempDir() target := filepath.Join(root, "target.secret") writeFile(t, target, []byte("synthetic-secret"), 0o600) secret := filepath.Join(root, "secret") if err := os.Symlink(target, secret); err != nil { t.Fatalf("Symlink() error = %v", err) } if _, err := ReadSecret(secret, 128); err == nil { t.Fatal("ReadSecret() error = nil, want symlink refusal") } }) } func TestProtectedPathsRejectUnsafeModes(t *testing.T) { t.Run("root", func(t *testing.T) { root := t.TempDir() if err := os.Chmod(root, 0o770); err != nil { t.Fatalf("Chmod() error = %v", err) } if _, err := OpenDir(root); err == nil { t.Fatal("OpenDir() error = nil, want unsafe mode refusal") } }) t.Run("child directory", func(t *testing.T) { root := t.TempDir() child := filepath.Join(root, "active") if err := os.Mkdir(child, 0o770); err != nil { t.Fatalf("Mkdir() error = %v", err) } if err := os.Chmod(child, 0o770); err != nil { t.Fatalf("Chmod() error = %v", err) } dir, err := OpenDir(root) if err != nil { t.Fatalf("OpenDir() error = %v", err) } t.Cleanup(func() { _ = dir.Close() }) if _, err := dir.OpenDir("active"); err == nil { t.Fatal("OpenDir(active) error = nil, want unsafe mode refusal") } }) t.Run("record", func(t *testing.T) { root := t.TempDir() path := filepath.Join(root, "record.json") writeFile(t, path, []byte(`{"unsafe":true}`), 0o660) dir, err := OpenDir(root) if err != nil { t.Fatalf("OpenDir() error = %v", err) } t.Cleanup(func() { _ = dir.Close() }) if _, err := dir.ReadFile("record.json", 4096); err == nil { t.Fatal("ReadFile() error = nil, want unsafe mode refusal") } }) t.Run("secret", func(t *testing.T) { root := t.TempDir() secret := filepath.Join(root, "secret") writeFile(t, secret, []byte("synthetic-secret"), 0o640) if _, err := ReadSecret(secret, 128); err == nil { t.Fatal("ReadSecret() error = nil, want non-0600 refusal") } }) } func TestReadFileRejectsOversizeContent(t *testing.T) { root := t.TempDir() path := filepath.Join(root, "record.json") writeFile(t, path, bytes.Repeat([]byte{'a'}, 4097), 0o640) dir, err := OpenDir(root) if err != nil { t.Fatalf("OpenDir() error = %v", err) } t.Cleanup(func() { _ = dir.Close() }) if _, err := dir.ReadFile("record.json", 4096); err == nil { t.Fatal("ReadFile() error = nil, want bounded-read refusal") } } func TestReadSecretAndCreateSecretUse0600AndExclusiveCreate(t *testing.T) { root := t.TempDir() input := filepath.Join(root, "input.secret") want := []byte("synthetic-secret") writeFile(t, input, want, 0o600) got, err := ReadSecret(input, 128) if err != nil { t.Fatalf("ReadSecret() error = %v", err) } if !bytes.Equal(got, want) { t.Fatalf("ReadSecret() = %q, want %q", got, want) } output := filepath.Join(root, "output.secret") file, err := CreateSecret(output) if err != nil { t.Fatalf("CreateSecret() error = %v", err) } if _, err := file.Write(want); err != nil { _ = file.Close() t.Fatalf("Write() error = %v", err) } if err := file.Sync(); err != nil { _ = file.Close() t.Fatalf("Sync() error = %v", err) } if err := file.Close(); err != nil { t.Fatalf("Close() error = %v", err) } info, err := os.Stat(output) if err != nil { t.Fatalf("Stat() error = %v", err) } if got, want := info.Mode().Perm(), os.FileMode(0o600); got != want { t.Fatalf("output mode = %04o, want %04o", got, want) } if _, err := CreateSecret(output); err == nil { t.Fatal("CreateSecret(existing) error = nil, want exclusive-create refusal") } else if !errors.Is(err, os.ErrExist) { t.Fatalf("CreateSecret(existing) error = %v, want os.ErrExist", err) } } func writeFile(t *testing.T, path string, data []byte, mode os.FileMode) { t.Helper() if err := os.WriteFile(path, data, mode); err != nil { t.Fatalf("WriteFile(%q) error = %v", path, err) } if err := os.Chmod(path, mode); err != nil { t.Fatalf("Chmod(%q) error = %v", path, err) } }