agent: read chrony status from tracking.log instead of the command socket
Bench on comet-beacon-54 proved every command-socket route is blocked for the hardened agent: chronyd's unix socket is owner-write-only (0755, so even a chrony-group member can't connect), and STIG closes the UDP command port (cmdport 0). Rather than re-open a port or expose chrony's privileged socket, enable chronyd's tracking log and have the agent READ it — no port, no socket, no group, no SELinux domain transition, full hardening intact. - chrony.conf: enable 'log tracking' (idempotent) so chronyd appends source/stratum/offset to /var/log/chrony/tracking.log. Write volume is a ~130B line per clock update (backing off to ~maxpoll when stable) and capped by the chrony RPM's logrotate — negligible on the RAM overlay. cmdport 0 stays, so no STIG deviation. - agent: chronyTracking() now reads the last data line of tracking.log via parseTrackingLog() (source f[2], stratum f[3], offset f[6], leap/sync f[7]); replaces the chronyc exec + parseChrony. Unit test swapped accordingly. - revert the two abandoned approaches: drop the bindcmdaddress line (394ad10) and the fleet_agent_chronyc SELinux nnp_transition module + provisioner block (e6fd4a2, .te deleted) — no chronyc exec means no transition to allow. Removed stig-deviations #16 and #17 (both waivers no longer needed). Needs a workstation image rebuild + reboot. BENCH-VERIFY: tracking.log is world-readable so the DynamicUser agent can read it; under enforcing unconfined_service_t can read chronyd_var_log_t (add a minimal file-read allow if denied); and the offset sign matches chronyc on a synced host.
This commit is contained in:
+11
-27
@@ -405,17 +405,17 @@ build {
|
||||
"restorecon /etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp",
|
||||
"sed -i -E 's/^[[:space:]]*(pool|server)[[:space:]]/#&/' /etc/chrony.conf",
|
||||
"grep -q '^sourcedir /run/chrony-dhcp' /etc/chrony.conf || echo 'sourcedir /run/chrony-dhcp' >> /etc/chrony.conf",
|
||||
# Bind chronyd's command socket on LOOPBACK UDP so the fleet-agent can query
|
||||
# clock health (`chronyc -h 127.0.0.1 tracking`). chrony 4.x (RHEL 9) defaults
|
||||
# the command interface to the UNIX socket /run/chrony/chronyd.sock ONLY
|
||||
# (root or the chrony group + a writable /run/chrony for chronyc's reply
|
||||
# socket). The agent runs hardened — DynamicUser (not in chrony group),
|
||||
# ProtectSystem=strict (/run read-only) — so it can't use the unix socket; a
|
||||
# loopback UDP command socket needs no fs write or group and rides the unit's
|
||||
# already-allowed AF_INET. Localhost-only + monitoring-only (state changes
|
||||
# still require the root unix socket), so the exposure is minimal. Documented
|
||||
# in docs/stig-deviations.md #17.
|
||||
"grep -q '^bindcmdaddress 127.0.0.1' /etc/chrony.conf || echo 'bindcmdaddress 127.0.0.1' >> /etc/chrony.conf",
|
||||
# Enable chrony's tracking log so the fleet-agent can report clock health
|
||||
# (source/stratum/offset) WITHOUT touching chronyd's command interface. That
|
||||
# interface is unusable by the hardened agent: the unix socket is
|
||||
# owner-write-only (0755) and the agent is DynamicUser + ProtectSystem=strict
|
||||
# (/run read-only), and STIG closes the UDP command port (cmdport 0). Instead
|
||||
# chronyd appends source/stratum/offset to /var/log/chrony/tracking.log, which
|
||||
# the agent simply READS (world-readable; no port, no socket, no SELinux domain
|
||||
# transition). Write volume is tiny (~one ~130B line per clock update, backing
|
||||
# off to ~maxpoll when stable) and capped by the chrony RPM's logrotate; on the
|
||||
# RAM overlay this is negligible. Keeps cmdport 0 intact — no STIG deviation.
|
||||
"grep -qE '^log ([^#]*[[:space:]])?tracking' /etc/chrony.conf || echo 'log tracking' >> /etc/chrony.conf",
|
||||
|
||||
# Inject the fleet NIC drivers into the dracut config
|
||||
"sed -i 's/@NIC_DRIVERS@/${var.nic_drivers}/' /etc/dracut.conf.d/90-diskless.conf",
|
||||
@@ -910,22 +910,6 @@ build {
|
||||
"semodule -i /tmp/fapolicyd_tmpfs.pp",
|
||||
"rm -f /tmp/fapolicyd_tmpfs.mod /tmp/fapolicyd_tmpfs.pp",
|
||||
"semodule -l | grep -q '^fapolicyd_tmpfs' && echo '==> SELinux module fapolicyd_tmpfs installed' || { echo 'ERROR: fapolicyd_tmpfs module not installed' >&2; exit 1; }",
|
||||
# SELinux: let the fleet-agent's chrony probe transition to chronyc_t under
|
||||
# NoNewPrivileges. The agent (unconfined_service_t) shells out to `chronyc -n
|
||||
# tracking` for clock offset/source/stratum; the shipped policy transitions
|
||||
# chronyc into chronyc_t, but the hardened unit's NNP (explicit + forced by
|
||||
# DynamicUser/SystemCallFilter/MDWE) makes SELinux require process2:nnp_transition
|
||||
# for that move, which the base policy doesn't grant -> `denied { nnp_transition }`
|
||||
# -> chronyc never runs -> all chrony_* health fields stay empty. Ship a minimal
|
||||
# allow (docs/stig-deviations.md). Installed unconditionally like the modules
|
||||
# above (the rule is inert without the agent). checkpolicy ensured above. Same
|
||||
# policy-store-rebuild reproducibility caveat as chrony/fapolicyd.
|
||||
"echo '==> supplemental: SELinux module fleet_agent_chronyc (fleet-agent chronyc under NNP)'",
|
||||
"checkmodule -M -m -o /tmp/fleet_agent_chronyc.mod /tmp/stig/fleet-agent-chronyc.te",
|
||||
"semodule_package -o /tmp/fleet_agent_chronyc.pp -m /tmp/fleet_agent_chronyc.mod",
|
||||
"semodule -i /tmp/fleet_agent_chronyc.pp",
|
||||
"rm -f /tmp/fleet_agent_chronyc.mod /tmp/fleet_agent_chronyc.pp",
|
||||
"semodule -l | grep -q '^fleet_agent_chronyc' && echo '==> SELinux module fleet_agent_chronyc installed' || { echo 'ERROR: fleet_agent_chronyc module not installed' >&2; exit 1; }",
|
||||
"rm -rf /tmp/stig",
|
||||
"echo '==> STIG remediation complete (report at /root/stig-report.html, ARF at /root/stig-arf.xml)'",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user