diff --git a/docs/AGENT_SECURITY.md b/docs/AGENT_SECURITY.md index 656aa2a18..593ee829a 100644 --- a/docs/AGENT_SECURITY.md +++ b/docs/AGENT_SECURITY.md @@ -146,7 +146,11 @@ dropping the LXC-attach ambient capability grant entirely. Two optional flags restore the collectors that genuinely need elevation, each through an exact-command sudoers grant validated with `visudo` and a root-owned wrapper the agent is pointed at via an absolute-path-only -environment override: +environment override. Because `NoNewPrivileges` blocks `sudo` entirely, a +unit with an active grant sets `NoNewPrivileges=false` while keeping the +remaining hardening; a grantless least-privilege install keeps +`NoNewPrivileges=true`. Choose grants deliberately: each one is a scoped, +auditable widening of the profile. - `--grant-smart` allows exactly `smartctl`, restoring SMART disk health. - `--grant-pct` allows exactly `pct list` and `pct df`, restoring Proxmox LXC diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 22df34881..d6c47b1bd 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -6419,7 +6419,10 @@ installed unit rather than requiring the flags to be repeated. Optional need elevation through visudo-validated, exact-command sudoers rules and root-owned wrapper helpers the agent reaches only via the absolute-path-only `PULSE_SMARTCTL_PATH` / `PULSE_PCT_PATH` overrides; the pct grant covers -`pct list` and `pct df` only and can never widen into `pct exec`. The agent +`pct list` and `pct df` only and can never widen into `pct exec`. An active +grant sets `NoNewPrivileges=false` because NNP blocks sudo outright (proven +on a live systemd host); a grantless profile keeps `NoNewPrivileges=true`. +The agent authors a `privilege` block in its report (`pkg/agents/host/report.go` `PrivilegeStatus`: effective root, service user, active helpers) so the server can present the profile descriptively. Uninstall removes the sudoers diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 6dea3283e..ac792be7a 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -3474,6 +3474,8 @@ exact-command sudoers rules with root-owned wrapper helpers wired through flag is refused (never silently downgraded to root) on appliance platforms and non-systemd init systems, is mutually exclusive with `--enable-commands`, and `--update` preserves an installed profile and its -grants by reading the existing unit. Uninstall removes the sudoers file and -helper directory. `scripts/installtests/install_sh_test.go` +grants by reading the existing unit. A unit with an active grant sets +`NoNewPrivileges=false` because NNP blocks sudo (proven on a live systemd +host); a grantless profile keeps `NoNewPrivileges=true`. Uninstall removes +the sudoers file and helper directory. `scripts/installtests/install_sh_test.go` (`TestInstallSHLeastPrivilegeProfile`) pins these invariants. diff --git a/docs/release-control/v6/internal/subsystems/security-privacy.md b/docs/release-control/v6/internal/subsystems/security-privacy.md index e12a3bd7e..147d9bf9d 100644 --- a/docs/release-control/v6/internal/subsystems/security-privacy.md +++ b/docs/release-control/v6/internal/subsystems/security-privacy.md @@ -2287,6 +2287,8 @@ non-root with a nologin shell, keep every sudoers grant exact-command and visudo-validated with the pct grant excluding `pct exec`/`start`/`stop`/ `enter`, refuse `--enable-commands` under the profile, refuse unsupported platforms instead of silently reverting to root, and drop the LXC-attach -ambient capability grant. The agent-reported privilege profile is +ambient capability grant. `NoNewPrivileges` stays enabled on a grantless +profile and is relaxed only when a sudo grant is active, because NNP blocks +sudo outright; that relaxation is part of the grant's declared cost. The agent-reported privilege profile is informational: the fleet doctor presents it descriptively and must not treat a non-root agent as unhealthy on that evidence alone. diff --git a/scripts/install.sh b/scripts/install.sh index 89b60a8a6..0c82e6453 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1034,6 +1034,14 @@ render_systemd_agent_unit() { no_new_privileges="false" restrict_suidsgid="false" fi + if [[ "$LEAST_PRIVILEGE" == "true" ]] && [[ "$GRANT_SMART" == "true" || "$GRANT_PCT" == "true" ]]; then + # The scoped sudo helpers are the profile's only privilege path, and + # NoNewPrivileges blocks sudo outright ("no new privileges flag is + # set"). Proven on a live systemd host: with NNP on, every helper call + # fails and SMART/pct silently disappear. A grant therefore relaxes + # NNP; a grantless least-privilege install keeps it. + no_new_privileges="false" + fi if systemd_agent_may_attach_lxc; then # lxc-attach into an unprivileged guest writes /proc//uid_map, # which needs CAP_SETUID in the parent user namespace. NoNewPrivileges diff --git a/scripts/installtests/install_sh_test.go b/scripts/installtests/install_sh_test.go index 1c5d6f033..d9b18656a 100644 --- a/scripts/installtests/install_sh_test.go +++ b/scripts/installtests/install_sh_test.go @@ -5564,6 +5564,9 @@ func TestInstallSHLeastPrivilegeProfile(t *testing.T) { `"network-online.target" "$SERVICE_USER" ""`, "# The least-privilege profile never attaches into guests", `rm -f "$PRIVILEGE_SUDOERS_FILE"`, + // NoNewPrivileges blocks sudo, so an active grant must relax it or + // the helpers silently fail inside the service (proven live). + `if [[ "$LEAST_PRIVILEGE" == "true" ]] && [[ "$GRANT_SMART" == "true" || "$GRANT_PCT" == "true" ]]; then`, } for _, needle := range required { if !strings.Contains(script, needle) {