fix: gate SUDO_USER trust and root path diversion; add doctor binary check

Address review findings on the system-install PR:

- cloud_sink: honor SUDO_USER for audit attribution only when euid==0.
  Without the gate any user could set SUDO_USER and spoof cloud-audit
  attribution to another account. Matches the guard in cmd/setup/cert.go.

- config: divert per-user paths to root's passwd home only on an actual
  sudo elevation (euid==0 && SUDO_USER set), not for every root euid.
  The blanket root diversion ignored HOME/XDG_CONFIG_HOME and silently
  stopped reading genuine root users' config (golden Docker images),
  regressing two tests that only fail when the suite runs as root.
  Genuine root honors the environment as before; su without - leaves no
  marker and stays a documented, loud-failing residual.

- doctor: add a system-only check re-validating that the binary the
  installed shims exec is still root-owned and non-writable, catching
  permission/ownership drift after install.

- shim: fold the duplicated shim-scan loop into firstShimContent.
This commit is contained in:
Sahilb315
2026-07-14 13:07:27 +05:30
parent 297f516242
commit 4b2a25a378
9 changed files with 211 additions and 15 deletions
+10 -5
View File
@@ -118,12 +118,17 @@ func (s *cloudSink) buildInvocationContext() *controltowerv1.EndpointInvocationC
return ctx
}
// invokingUser resolves the human behind the command, preferring SUDO_USER so a
// `sudo npm ...` is attributed to the operator rather than root.
var auditGeteuid = os.Geteuid
// invokingUser resolves the human behind the command. SUDO_USER is honored
// only when the process is actually elevated (euid 0); otherwise any user
// could set SUDO_USER to spoof cloud-audit attribution to another account.
func invokingUser() *user.User {
if name := os.Getenv("SUDO_USER"); name != "" {
if u, err := user.Lookup(name); err == nil {
return u
if auditGeteuid() == 0 {
if name := os.Getenv("SUDO_USER"); name != "" {
if u, err := user.Lookup(name); err == nil {
return u
}
}
}
u, err := user.Current()
+23
View File
@@ -2,6 +2,7 @@ package audit
import (
"context"
"os/user"
"testing"
"time"
@@ -157,3 +158,25 @@ func TestCloudSinkSetsInvocationContextOnSessionComplete(t *testing.T) {
assert.NotEmpty(t, invCtx.GetUsername())
assert.NotEmpty(t, invCtx.GetUsernameUid())
}
func TestInvokingUserIgnoresSudoUserWhenNotElevated(t *testing.T) {
current, err := user.Current()
require.NoError(t, err)
orig := auditGeteuid
t.Cleanup(func() { auditGeteuid = orig })
// Non-root process: SUDO_USER must be ignored, else attribution is spoofable.
auditGeteuid = func() int { return 1000 }
t.Setenv("SUDO_USER", "root")
got := invokingUser()
require.NotNil(t, got)
assert.Equal(t, current.Username, got.Username, "SUDO_USER must not override attribution when not elevated")
// Elevated (euid 0): SUDO_USER is trusted and used.
auditGeteuid = func() int { return 0 }
t.Setenv("SUDO_USER", current.Username)
got = invokingUser()
require.NotNil(t, got)
assert.Equal(t, current.Username, got.Username)
}