fix(auth): hand off privileges before state creation; fail closed on store errors
Fresh boots opened the owner/token/session stores before EnsureDataRoot dropped to the host user, leaving a root-owned maestro/ state dir whose EACCES surfaced as a bogus ERR_INVALID_CREDENTIALS on /api/auth/setup. main() now runs the handoff first; EnsureDataRoot heals pre-existing root-owned state dirs while still root (healOwnerState). Storage failures from the owner store now answer 500 ERR_INTERNAL everywhere (status/setup/login/change-password/RequireSession/ GateWebSocket) instead of blaming credentials or re-advertising setup mode; SetupRequired returns the store error instead of swallowing it, and FileOwnerStore.Verify propagates read errors rather than masking them as unknown-user.
This commit is contained in:
@@ -202,6 +202,9 @@ func EnsureDataRoot() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := healOwnerState(root, identity); err != nil {
|
||||
return fmt.Errorf("failed to hand owner state dir to uid %d: %w", identity.uid, err)
|
||||
}
|
||||
if err := os.Chown(root, identity.uid, identity.gid); err != nil {
|
||||
return fmt.Errorf("failed to chown data root %s to %d:%d: %w", root, identity.uid, identity.gid, err)
|
||||
}
|
||||
@@ -212,6 +215,33 @@ func EnsureDataRoot() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// healOwnerState gives the instance-owner gate's state directory to the
|
||||
// runtime user. Boots before the privilege drop opened the gate's stores as
|
||||
// root first, leaving a root-owned <root>/maestro behind; after the handoff
|
||||
// every write then failed with EACCES (surfacing as a bogus
|
||||
// ERR_INVALID_CREDENTIALS on /api/auth/setup). Repairs such installs here,
|
||||
// while the process can still chown. Missing dirs are fine — nothing to heal.
|
||||
func healOwnerState(root string, id runtimeIdentity) error {
|
||||
dir := filepath.Join(root, "maestro")
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("read owner state dir: %w", err)
|
||||
}
|
||||
if err := os.Chown(dir, id.uid, id.gid); err != nil {
|
||||
return fmt.Errorf("chown %s: %w", dir, err)
|
||||
}
|
||||
for _, e := range entries {
|
||||
p := filepath.Join(dir, e.Name())
|
||||
if err := os.Chown(p, id.uid, id.gid); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("chown %s: %w", p, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeIdentity is the user the container should run as, plus any
|
||||
// supplementary groups needed to keep working (e.g. the docker socket group).
|
||||
type runtimeIdentity struct {
|
||||
|
||||
Reference in New Issue
Block a user