agent: query chrony over loopback UDP command socket (unix socket unusable under sandbox)
Root cause of the blank chrony fields (found on comet-beacon-54): chrony 4.x (RHEL 9) defaults its command interface to the UNIX socket only (/run/chrony/chronyd.sock), usable by root or the chrony group with a writable /run/chrony. The fleet-agent unit is hardened — DynamicUser (group tss, not chrony) + ProtectSystem=strict (/run read-only) — so it cannot open the unix socket nor create chronyc's reply socket. Bench proof: normal-user 'chronyc tracking' -> 506 Cannot talk to daemon; only sudo worked; 'chronyc -h 127.0.0.1' failed and ss showed no UDP :323. So the probe always failed and every chrony_* field stayed empty. SELinux permissive did NOT help — it was never (only) an SELinux problem; the nnp_transition module (prev commit) is still needed for the enforcing exec transition. Fix: bind chronyd's command socket on 127.0.0.1 (bindcmdaddress in chrony.conf, idempotent in the 2c provisioner) and point the probe at it (chronyc -n -h 127.0.0.1 tracking). UDP needs no fs write or group and rides the unit's already-allowed AF_INET. Loopback + monitoring-only (state changes still need the root unix socket), so exposure is minimal. Rejected the unix-socket route (chrony group + ReadWritePaths=/run/chrony) because /run/chrony is 0750 (group has no write). Documented in docs/stig-deviations.md #17; .te bench note updated for the enforcing UDP path. Needs a workstation image rebuild + reboot.
This commit is contained in:
@@ -305,8 +305,15 @@ type chrony struct {
|
||||
|
||||
// chronyTracking runs `chronyc tracking` once and parses it. Best-effort: a zero
|
||||
// chrony{} (state "") if chronyc is unavailable — never fails the snapshot.
|
||||
//
|
||||
// -h 127.0.0.1 forces the LOOPBACK UDP command socket instead of chronyd's unix
|
||||
// socket. Under the agent's hardened unit (DynamicUser — not in the chrony group;
|
||||
// ProtectSystem=strict — /run read-only) the unix socket at /run/chrony is
|
||||
// unreachable (can't open it, nor create chronyc's reply socket there). UDP needs
|
||||
// no fs write or group and rides the unit's already-allowed AF_INET. The image
|
||||
// binds this socket via `bindcmdaddress 127.0.0.1` in chrony.conf.
|
||||
func chronyTracking() chrony {
|
||||
out, err := runShort("chronyc", "-n", "tracking")
|
||||
out, err := runShort("chronyc", "-n", "-h", "127.0.0.1", "tracking")
|
||||
if err != nil {
|
||||
return chrony{}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user