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:
Audiobuzz
2026-07-10 09:36:37 +00:00
parent 394ad10824
commit 4ca0b6dd06
5 changed files with 78 additions and 218 deletions
-44
View File
@@ -1,44 +0,0 @@
# Raw module-policy syntax (compiled with `checkmodule -M -m`), NOT the refpolicy
# `policy_module()` m4 macro — that macro is only expanded by the refpolicy
# Makefile and checkmodule rejects it. See chrony-dhcp-dispatcher.te for the why.
module fleet_agent_chronyc 1.0.0;
########################################
# Let the fleet-agent's chrony probe transition to chronyc_t under NoNewPrivileges.
#
# fleet-agent (telemetry) shells out to `chronyc -n tracking` to report the host's
# clock offset / NTP source / stratum. The agent runs as unconfined_service_t, and
# the shipped chrony policy has a type_transition so executing chronyc moves the
# child into the confined chronyc_t domain. BUT the fleet-agent unit is hardened
# with NoNewPrivileges (set explicitly AND forced by DynamicUser + SystemCallFilter
# + MemoryDenyWriteExecute), and SELinux forbids an NNP process from transitioning
# domains unless process2:nnp_transition is granted for that source->target pair.
# The shipped policy has no such grant, so under enforcing the exec is DENIED:
# avc: denied { nnp_transition } comm="fleet-agent"
# scontext=...:unconfined_service_t tcontext=...:chronyc_t tclass=process2
# chronyc never runs, chronyTracking() returns empty, and every chrony_* health
# field stays unset (the UI shows chrony as "—"). uptime/cpu/mem are unaffected —
# they read /proc directly and need no domain transition.
#
# This single allow lets the transition proceed to the MORE-confined chronyc_t
# domain (the safe direction NNP's nnp_transition permission exists to gate), with
# no loosening of the agent's own sandbox. Minimal + auditable; justified in
# docs/stig-deviations.md #16.
#
# NB this is necessary but NOT sufficient: the agent reaches chronyd over the
# LOOPBACK UDP command socket (`chronyc -h 127.0.0.1 tracking`; the image binds it
# via `bindcmdaddress 127.0.0.1`, see stig-deviations #17) because the unix socket
# is unusable under the agent's sandbox. Under enforcing, once chronyc runs in
# chronyc_t it must also be allowed to reach chronyd_t on UDP 323 — the base chrony
# policy is expected to cover chronyc_t's command traffic, but BENCH-VERIFY on the
# first enforcing boot and, if a FOLLOW-ON chronyc_t AVC appears (e.g. a corenet
# udp_socket/name_connect denial to chronyd), extend here.
########################################
require {
type unconfined_service_t;
type chronyc_t;
class process2 nnp_transition;
}
allow unconfined_service_t chronyc_t:process2 nnp_transition;