216 lines
5.9 KiB
Go
216 lines
5.9 KiB
Go
//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)
|
|
}
|
|
}
|