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
+17 -16
View File
@@ -68,16 +68,16 @@ func TestParseUptime(t *testing.T) {
}
}
func TestParseChrony(t *testing.T) {
out := `Reference ID : C0A8320A (192.168.50.10)
Stratum : 3
Ref time (UTC) : Fri Jul 10 06:00:00 2026
System time : 0.000012345 seconds slow of NTP time
Last offset : +0.000001234 seconds
Frequency : 12.345 ppm slow
Leap status : Normal
func TestParseTrackingLog(t *testing.T) {
// A realistic tracking.log: banner + header + two data lines. parseTrackingLog
// must take the LAST data line, skip banner/header, and read source/stratum/offset.
log := `====================================================================
Date (UTC) Time IP Address St Freq ppm Skew ppm Offset L Co Offset sd
====================================================================
2026-07-10 05:59:00 192.168.50.10 3 -1.234 0.100 +0.000002000 N 4 1.0e-07
2026-07-10 06:00:00 192.168.50.10 3 -1.250 0.090 -0.000012345 N 4 1.0e-07
`
c := parseChrony(out)
c := parseTrackingLog([]byte(log))
if c.state != "synced" {
t.Errorf("state = %q, want synced", c.state)
}
@@ -87,17 +87,18 @@ Leap status : Normal
if c.stratum != 3 {
t.Errorf("stratum = %d, want 3", c.stratum)
}
// "slow of NTP time" -> negative offset (sign-sensitive check).
// last line's Offset is -0.000012345 (sign-sensitive: must not read the +2e-6 above).
if c.offset >= 0 || math.Abs(c.offset+0.000012345) > 1e-9 {
t.Errorf("offset = %v, want -0.000012345", c.offset)
}
// unsynced when Leap status isn't Normal.
if c := parseChrony("Leap status : Not synchronised\n"); c.state != "unsynced" {
t.Errorf("state = %q, want unsynced", c.state)
// unsynchronised reference: L='?' and 0.0.0.0 source.
uns := "2026-07-10 06:01:00 0.0.0.0 0 0.000 0.000 +0.000000000 ? 0 0.0e+00\n"
if c := parseTrackingLog([]byte(uns)); c.state != "unsynced" || c.source != "" {
t.Errorf("unsynced parse = %+v, want state=unsynced source=''", c)
}
// empty / unavailable.
if c := parseChrony(""); c.state != "unsynced" || c.source != "" {
t.Errorf("empty parse = %+v", c)
// empty / header-only -> zero chrony{}.
if c := parseTrackingLog([]byte(" Date (UTC) Time IP Address\n")); c.state != "" {
t.Errorf("header-only parse = %+v, want zero", c)
}
}