fix: run DWH verification with read-only registry access
This commit is contained in:
@@ -48,6 +48,18 @@ var socketOwner = func(path string) (uint32, error) {
|
||||
return stat.Uid, nil
|
||||
}
|
||||
|
||||
var socketParentOwner = func(path string) (uint32, error) {
|
||||
info, err := os.Lstat(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("socket parent owner is unavailable")
|
||||
}
|
||||
return stat.Uid, nil
|
||||
}
|
||||
|
||||
// New returns the HTTP verifier. It has no network listener and is safe to use
|
||||
// with an httptest server only for synthetic test registries.
|
||||
func New(store *registry.Store, logger *log.Logger, now func() time.Time) http.Handler {
|
||||
@@ -101,7 +113,7 @@ func ListenAndServe(ctx context.Context, config Config) error {
|
||||
if err := validateSocketParent(config.Socket); err != nil {
|
||||
return err
|
||||
}
|
||||
store, err := registry.Open(config.RegistryRoot)
|
||||
store, err := registry.OpenReadOnly(config.RegistryRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -164,6 +176,20 @@ func validateSocketParent(socket string) error {
|
||||
if !info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
||||
return errors.New("socket parent is not a directory")
|
||||
}
|
||||
if info.Mode().Perm()&0o022 != 0 {
|
||||
return errors.New("socket parent is group or world writable")
|
||||
}
|
||||
stat, ok := info.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
return errors.New("socket parent owner is unavailable")
|
||||
}
|
||||
owner, err := socketParentOwner(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.Uid != uint32(os.Geteuid()) || owner != stat.Uid {
|
||||
return errors.New("socket parent owner mismatch")
|
||||
}
|
||||
resolved, err := filepath.EvalSymlinks(parent)
|
||||
if err != nil || resolved != parent {
|
||||
return errors.New("socket parent is not canonical")
|
||||
|
||||
@@ -212,6 +212,7 @@ func TestVerifyMapsMultipleLegacyRecordsToServiceUnavailable(t *testing.T) {
|
||||
|
||||
func TestListenAndServeFailsClosedForInvalidPathsAndCollisions(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
parent := t.TempDir()
|
||||
for _, tc := range []struct {
|
||||
name, registryRoot, socket string
|
||||
@@ -252,8 +253,47 @@ func TestListenAndServeFailsClosedForInvalidPathsAndCollisions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenAndServeRejectsUnsafeSocketParents(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
for _, mode := range []os.FileMode{0o770, 0o702} {
|
||||
t.Run("mode "+mode.String(), func(t *testing.T) {
|
||||
parent := t.TempDir()
|
||||
if err := os.Chmod(parent, mode); err != nil {
|
||||
t.Fatalf("Chmod(%v) error = %v", mode, err)
|
||||
}
|
||||
socket := filepath.Join(parent, "verify.sock")
|
||||
err := ListenAndServe(context.Background(), Config{RegistryRoot: root, Socket: socket, Logger: log.New(&bytes.Buffer{}, "", 0), Now: time.Now})
|
||||
if err == nil {
|
||||
t.Fatalf("ListenAndServe(parent mode %v) error = nil, want refusal", mode)
|
||||
}
|
||||
if _, err := os.Lstat(socket); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("unsafe parent created socket: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenAndServeRejectsForeignOwnedSocketParent(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
parent := t.TempDir()
|
||||
socket := filepath.Join(parent, "verify.sock")
|
||||
previous := socketParentOwner
|
||||
t.Cleanup(func() { socketParentOwner = previous })
|
||||
socketParentOwner = func(string) (uint32, error) { return uint32(os.Geteuid()) + 1, nil }
|
||||
err := ListenAndServe(context.Background(), Config{RegistryRoot: root, Socket: socket, Logger: log.New(&bytes.Buffer{}, "", 0), Now: time.Now})
|
||||
if err == nil {
|
||||
t.Fatal("ListenAndServe() error = nil, want foreign-parent refusal")
|
||||
}
|
||||
if _, err := os.Lstat(socket); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("foreign parent created socket: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListenAndServeReclaimsOnlyOwnedStaleSocketAndCleansUpOnCancellation(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
socket := filepath.Join(t.TempDir(), "verify.sock")
|
||||
stale, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"})
|
||||
if err != nil {
|
||||
@@ -290,6 +330,7 @@ func TestListenAndServeReclaimsOnlyOwnedStaleSocketAndCleansUpOnCancellation(t *
|
||||
|
||||
func TestListenAndServeRefusesStaleSocketWithForeignOwner(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
socket := filepath.Join(t.TempDir(), "verify.sock")
|
||||
stale, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"})
|
||||
if err != nil {
|
||||
@@ -313,6 +354,7 @@ func TestListenAndServeRefusesStaleSocketWithForeignOwner(t *testing.T) {
|
||||
|
||||
func TestListenAndServeRefusesLiveSocket(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
socket := filepath.Join(t.TempDir(), "verify.sock")
|
||||
live, err := net.ListenUnix("unix", &net.UnixAddr{Name: socket, Net: "unix"})
|
||||
if err != nil {
|
||||
@@ -330,6 +372,7 @@ func TestListenAndServeRefusesLiveSocket(t *testing.T) {
|
||||
|
||||
func TestListenAndServeCleanupRefusesChangedSocketPath(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
initializeRegistry(t, root)
|
||||
socket := filepath.Join(t.TempDir(), "verify.sock")
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
errs := make(chan error, 1)
|
||||
@@ -363,6 +406,17 @@ func TestListenAndServeCleanupRefusesChangedSocketPath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func initializeRegistry(t *testing.T, root string) {
|
||||
t.Helper()
|
||||
store, err := registry.Open(root)
|
||||
if err != nil {
|
||||
t.Fatalf("Open() error = %v", err)
|
||||
}
|
||||
if err := store.Close(); err != nil {
|
||||
t.Fatalf("Close() error = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForReadySocket(t *testing.T, socket string) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
|
||||
Reference in New Issue
Block a user