Files
ThothII/tools/dwh-auth/internal/securefile/securefile_linux_test.go

337 lines
9.7 KiB
Go

//go:build linux
package securefile
import (
"bytes"
"errors"
"os"
"path/filepath"
"syscall"
"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("setgid protected directories", func(t *testing.T) {
root := t.TempDir()
if err := os.Chmod(root, 0o750|os.ModeSetgid); err != nil {
t.Fatalf("Chmod(root) error = %v", err)
}
dir, err := OpenDir(root)
if err != nil {
t.Fatalf("OpenDir(2750 root) error = %v", err)
}
t.Cleanup(func() { _ = dir.Close() })
for _, mode := range []os.FileMode{
0o750 | os.ModeSetuid,
0o750 | os.ModeSetgid | os.ModeSetuid,
0o750 | os.ModeSticky,
0o770 | os.ModeSetgid,
0o752 | os.ModeSetgid,
} {
path := t.TempDir()
if err := os.Chmod(path, mode); err != nil {
t.Fatalf("Chmod(%v) error = %v", mode, err)
}
if _, err := OpenDir(path); err == nil {
t.Fatalf("OpenDir(%v) error = nil, want unsafe-mode refusal", mode)
}
}
})
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 TestLockSharedAllowsReadersAndBlocksExclusiveWriter(t *testing.T) {
root := t.TempDir()
first, err := OpenDir(root)
if err != nil {
t.Fatalf("OpenDir(first) error = %v", err)
}
defer first.Close()
second, err := OpenDir(root)
if err != nil {
t.Fatalf("OpenDir(second) error = %v", err)
}
defer second.Close()
third, err := OpenDir(root)
if err != nil {
t.Fatalf("OpenDir(third) error = %v", err)
}
defer third.Close()
if first.fd == second.fd || second.fd == third.fd || first.fd == third.fd {
t.Fatal("OpenDir() reused a descriptor; want separate lock file descriptions")
}
type sharedLocker interface {
LockShared(string) (*Lock, error)
}
firstLocker, ok := any(first).(sharedLocker)
if !ok {
t.Fatal("Dir lacks shared advisory locking")
}
secondLocker, ok := any(second).(sharedLocker)
if !ok {
t.Fatal("Dir lacks shared advisory locking")
}
if _, err := firstLocker.LockShared("registry.lock"); err == nil {
t.Fatal("LockShared(missing) error = nil, want preprovisioned-lock refusal")
}
if _, err := os.Lstat(filepath.Join(root, "registry.lock")); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("LockShared(missing) created lock: %v", err)
}
bootstrap, err := third.Lock("registry.lock")
if err != nil {
t.Fatalf("Lock(bootstrap) error = %v", err)
}
if err := bootstrap.Close(); err != nil {
t.Fatalf("Close(bootstrap) error = %v", err)
}
if info, err := os.Stat(filepath.Join(root, "registry.lock")); err != nil || info.Mode().Perm() != 0o640 {
t.Fatalf("bootstrapped lock mode = (%v, %v), want 0640", info, err)
}
firstShared, err := firstLocker.LockShared("registry.lock")
if err != nil {
t.Fatalf("LockShared(first) error = %v", err)
}
defer firstShared.Close()
secondShared, err := secondLocker.LockShared("registry.lock")
if err != nil {
t.Fatalf("LockShared(second) error = %v", err)
}
defer secondShared.Close()
probe, err := os.OpenFile(filepath.Join(root, "registry.lock"), os.O_RDWR, 0)
if err != nil {
t.Fatalf("OpenFile(lock probe) error = %v", err)
}
if err := syscall.Flock(int(probe.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil {
_ = syscall.Flock(int(probe.Fd()), syscall.LOCK_UN)
_ = probe.Close()
t.Fatal("exclusive lock acquired while separate shared locks were held")
} else if !errors.Is(err, syscall.EWOULDBLOCK) && !errors.Is(err, syscall.EAGAIN) {
_ = probe.Close()
t.Fatalf("nonblocking exclusive lock error = %v, want EWOULDBLOCK", err)
}
if err := probe.Close(); err != nil {
t.Fatalf("Close(lock probe) error = %v", err)
}
if err := secondShared.Close(); err != nil {
t.Fatalf("Close(second shared) error = %v", err)
}
secondShared = nil
if err := firstShared.Close(); err != nil {
t.Fatalf("Close(first shared) error = %v", err)
}
firstShared = nil
exclusive, err := third.Lock("registry.lock")
if err != nil {
t.Fatalf("Lock(exclusive after shared release) error = %v", err)
}
if err := exclusive.Close(); err != nil {
t.Fatalf("Close(exclusive) error = %v", 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)
}
}