image: SELinux allow fleet-agent chronyc probe under NoNewPrivileges
fleet-agent reports clock health via 'chronyc -n tracking', but the hardened
unit runs with NoNewPrivileges (explicit + forced by DynamicUser/SystemCallFilter/
MemoryDenyWriteExecute). SELinux transitions the exec'd chronyc into chronyc_t,
and NNP forbids that domain change without process2:nnp_transition — which the
base policy doesn't grant for unconfined_service_t -> chronyc_t. Under enforcing
the exec is denied (avc: denied { nnp_transition } comm=fleet-agent, seen on
comet-beacon-54), so chronyc never runs and every chrony_* health field stays
empty (Monitor chrony row shows '—'). uptime/cpu/mem are unaffected (no exec).
Ship a minimal local module (files/stig/fleet-agent-chronyc.te) granting only
that one nnp_transition to the MORE-confined chronyc_t domain — the safe
direction the permission exists to gate — loosening nothing else in the agent
sandbox. Built + semodule -i'd in 2c-STIG alongside the existing
chrony_dhcp_dispatcher / fapolicyd_tmpfs modules. Justified in
docs/stig-deviations.md #16. Needs a workstation image rebuild + reboot; same
policy-store-rebuild reproducibility re-check as #14/#15.
This commit is contained in:
@@ -248,6 +248,45 @@ surface rather than detecting abuse of it. Requires a write primitive to a root-
|
||||
path either way (already-privileged context), which is fapolicyd's defense-in-depth
|
||||
threat model — not an unprivileged bypass.
|
||||
|
||||
## 16. Local SELinux policy module `fleet_agent_chronyc` (added 2026-07-10)
|
||||
|
||||
Not a STIG-rule waiver — a **minimal local `allow` module** shipped so the
|
||||
`fleet-agent` telemetry client can read chrony status under enforcing SELinux +
|
||||
NoNewPrivileges. Source: `files/stig/fleet-agent-chronyc.te`; built + `semodule
|
||||
-i`'d in the 2c-STIG provisioner (right after #15).
|
||||
|
||||
**The one rule:** `allow unconfined_service_t chronyc_t:process2 nnp_transition;`
|
||||
|
||||
**Why:** `fleet-agent` reports the host's clock health (offset / NTP source /
|
||||
stratum) by shelling out to `chronyc -n tracking`. The agent runs as
|
||||
`unconfined_service_t`, and the shipped chrony policy has a `type_transition` that
|
||||
moves the executed `chronyc` into the confined `chronyc_t` domain. But the
|
||||
`fleet-agent.service` unit is hardened with **`NoNewPrivileges`** — set explicitly
|
||||
and *also* forced implicitly by `DynamicUser=yes`, `SystemCallFilter=`, and
|
||||
`MemoryDenyWriteExecute=yes` — and SELinux forbids an NNP process from
|
||||
transitioning domains unless `process2:nnp_transition` is granted for that
|
||||
source→target pair. The base policy grants it for some daemons but not
|
||||
`unconfined_service_t → chronyc_t`, so under enforcing the exec is **denied**
|
||||
(audit `comet-beacon-54` 2026-07-10: `avc: denied { nnp_transition }
|
||||
comm="fleet-agent" scontext=…unconfined_service_t tcontext=…chronyc_t
|
||||
tclass=process2 permissive=0`). Result: `chronyc` never runs, the agent's
|
||||
`chronyTracking()` returns empty, and every `chrony_*` health field stays unset
|
||||
(the manager's Monitor chrony row shows `—`). The other probes are unaffected —
|
||||
`uptime_seconds`/cpu/mem read `/proc` directly and need no domain transition.
|
||||
|
||||
**Scope:** one perm (`nnp_transition`) on one class (`process2`), two well-known
|
||||
types. It permits a transition to a **more-confined** domain (`chronyc_t`) — the
|
||||
exact safe direction `nnp_transition` exists to gate — and **loosens nothing** in
|
||||
the agent's own sandbox (NNP, DynamicUser, syscall filter, MDWE all stay). It does
|
||||
not grant any file read/write. Installed unconditionally alongside #14/#15 (the
|
||||
rule is inert on a build without the agent). **BENCH-VERIFY:** (a) the AVC is gone
|
||||
on an enforcing boot; (b) the manager's Monitor chrony row populates with
|
||||
offset/source/stratum from the live NTP source; (c) no **follow-on** `chronyc_t`
|
||||
AVC under the agent's mount namespace (extend the `.te` if any appears); (d)
|
||||
**reproducibility** — `semodule -i` rebuilds the policy store, so re-check verity
|
||||
hash stability across two builds (same concern as #12/#14/#15). NB: only shipped
|
||||
when `enable_stig=true`; a non-STIG enforcing build would still see the AVC.
|
||||
|
||||
## Things that are NOT deviations (kept / enforced)
|
||||
|
||||
- **SELinux enforcing** (`selinux --enforcing`; labels preserved into the erofs).
|
||||
|
||||
Reference in New Issue
Block a user