secure-ntp: label /run/chrony-dhcp so chronyd can read the NTS source under SELinux
Second bench defect after removing the distro 20-chrony-dhcp clobberer (secure-ntp-5a3eaed, comet-beacon-54): the dispatcher wrote the correct `server ntp.fleet.lab iburst nts` line, but chrony loaded ZERO sources -- `chronyc reload sources` returned 200 OK yet authdata/sources stayed empty. Root cause (AVC): chronyd_t was DENIED read on the .sources files because they and /run/chrony-dhcp itself were labeled generic var_run_t; chronyd_t may only read its own chronyd_var_run_t. The dispatcher's `mkdir -p` creates the dir as var_run_t and files inherit it. The distro 20-chrony-dhcp we removed had been restorecon-ing them to the readable label, so removing it exposed a latent labelling gap. Stock RHEL policy already maps /run/chrony-dhcp -> chronyd_var_run_t (a restorecon relabels it and chrony immediately reaches NTS Cook 8). Fix: ship /etc/tmpfiles.d/chrony-dhcp.conf so systemd-tmpfiles pre-creates the dir with that fcontext at sysinit, before any NM event -- the dispatcher's files then inherit chronyd_var_run_t and chronyd can read them. No SELinux allow rules needed (bench-verified: after the fix a real NM event wrote the source as chronyd_var_run_t, no AVC, authdata -> NTS Cook 8). Applies in both secure_ntp modes. Also: the dispatcher now skips `lo` -- it was writing a spurious lo.sources duplicate of the same server (the distro script had skipped loopback). BENCH-VERIFY on the next clean build: a normal boot leaves the source file labeled chronyd_var_run_t (ls -Z), no lo.sources, and authdata reaches NTS/cookies>0 with no manual step.
This commit is contained in:
@@ -282,7 +282,14 @@ instead of working around it.
|
||||
line in the shared `/run/chrony-dhcp/<iface>.sources` with the plain option-42
|
||||
server — silently defeating NTS (authdata Mode `-`, Cook 0). The provisioner now
|
||||
`rm`s it (NTS then reached Cook 8 on the bench). Don't re-add a competing writer to
|
||||
that file. **Toggle:** `secure_ntp=false` reverts the dispatcher to the legacy
|
||||
that file. **Second bench finding (2026-07-12, FIXED):** removing that distro script
|
||||
also removed the `restorecon` that had been labelling the sources — the dispatcher's
|
||||
`mkdir -p` leaves `/run/chrony-dhcp` + files as generic `var_run_t`, which
|
||||
`chronyd_t` is SELinux-DENIED reading (loads 0 sources, `authdata` empty). Fixed by
|
||||
`/etc/tmpfiles.d/chrony-dhcp.conf` pre-creating the dir with the stock
|
||||
`chronyd_var_run_t` fcontext at sysinit, so the dispatcher's files inherit a readable
|
||||
label (no allow rules). The dispatcher also now skips `lo` (was writing a spurious
|
||||
duplicate). **Toggle:** `secure_ntp=false` reverts the dispatcher to the legacy
|
||||
unauthenticated **DHCP option 42** path (plain `server <ip> iburst`).
|
||||
- `fleet` Plymouth theme: confirm (a) the script plugin package actually
|
||||
installed (`/usr/lib64/plymouth/script.so` — else the build fell back to
|
||||
|
||||
@@ -457,6 +457,16 @@ build {
|
||||
# dispatcher supersedes it in BOTH modes, so drop it unconditionally. Leave
|
||||
# 20-chrony-onoffline (only marks sources online/offline — harmless).
|
||||
"rm -f /usr/lib/NetworkManager/dispatcher.d/20-chrony-dhcp",
|
||||
# ...but the distro script was ALSO (accidentally) labelling /run/chrony-dhcp so
|
||||
# chronyd could read the sources. Removing it exposed that gap: our dispatcher's
|
||||
# `mkdir -p` makes the dir generic var_run_t and the source files inherit it, and
|
||||
# chronyd_t (which only reads chronyd_var_run_t) is SELinux-DENIED reading them →
|
||||
# NTS silently loads 0 sources (bench 2026-07-12, comet-beacon-54). This tmpfiles.d
|
||||
# entry pre-creates /run/chrony-dhcp at sysinit with the stock chronyd_var_run_t
|
||||
# fcontext (before any NM event), so the dispatcher's files inherit a readable
|
||||
# label — no SELinux allow rules needed. Applies in BOTH modes.
|
||||
"install -D -m0644 /tmp/etc-stage/tmpfiles.d/chrony-dhcp.conf /etc/tmpfiles.d/chrony-dhcp.conf",
|
||||
"restorecon /etc/tmpfiles.d/chrony-dhcp.conf",
|
||||
"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",
|
||||
"if [ \"${var.secure_ntp}\" = \"true\" ]; then echo '==> secure NTP: NTS via <${var.ntp_prefix}>.<dhcp-domain> (fleet-CA-authenticated)'; { printf '\\n# Secure NTP (NTS, RFC 8915) — verify the server against the fleet CA. docs/secure-ntp-design.md\\n'; printf 'ntstrustedcerts /etc/dot1x/radius-ca.pem\\n'; printf 'nocerttimecheck 1\\n'; } >> /etc/chrony.conf; else echo '==> legacy NTP: unauthenticated DHCP option-42'; fi",
|
||||
|
||||
@@ -146,10 +146,31 @@ and tcp/4460 were **all already correct**; the collision was the sole defect.
|
||||
|
||||
Fix: the provisioner removes `/usr/lib/NetworkManager/dispatcher.d/20-chrony-dhcp`
|
||||
at build time (our dispatcher supersedes it in both modes); `20-chrony-onoffline`
|
||||
is left in place (harmless — it only marks sources online/offline). **BENCH-VERIFY
|
||||
on the next clean build:** with the distro script gone, a normal boot leaves
|
||||
`/run/chrony-dhcp/<iface>.sources` = the NTS line and `chronyc authdata` reaches
|
||||
`NTS`/cookies > 0 with no manual intervention.
|
||||
is left in place (harmless — it only marks sources online/offline).
|
||||
|
||||
**2026-07-12 (comet-beacon-54, `secure-ntp-5a3eaed`) — SELinux label on the source
|
||||
files (FIXED).** With the clobberer gone the dispatcher wrote the correct
|
||||
`server ntp.fleet.lab iburst nts` line, but chrony still loaded **zero** sources —
|
||||
`chronyc reload sources` returned `200 OK` yet `authdata`/`sources` stayed empty.
|
||||
Root cause (AVC): `chronyd_t` was **denied read** on the `.sources` files because
|
||||
they (and `/run/chrony-dhcp` itself) were labeled generic **`var_run_t`** —
|
||||
`chronyd_t` may only read its own **`chronyd_var_run_t`**. The dispatcher's
|
||||
`mkdir -p` creates the dir as `var_run_t` and files inherit it; the distro
|
||||
`20-chrony-dhcp` we removed had been the thing `restorecon`-ing them to the readable
|
||||
label, so removing it exposed a latent labelling gap. Stock RHEL policy already maps
|
||||
`/run/chrony-dhcp` → `chronyd_var_run_t` (a `restorecon -R` relabels it and chrony
|
||||
then reaches `NTS Cook 8`). Fix: ship `/etc/tmpfiles.d/chrony-dhcp.conf`
|
||||
(`d /run/chrony-dhcp …`) so systemd-tmpfiles pre-creates the dir with the correct
|
||||
fcontext at sysinit — files the dispatcher writes then **inherit** `chronyd_var_run_t`
|
||||
(bench-verified: after the fix a real NM event wrote `enp0s31f6.sources` as
|
||||
`chronyd_var_run_t`, no AVC, `authdata` → `NTS Cook 8`). No SELinux allow rules
|
||||
needed. Also fixed: the dispatcher now **skips `lo`** (it was writing a spurious
|
||||
`lo.sources` duplicate of the same server, which the distro script had skipped).
|
||||
|
||||
**BENCH-VERIFY on the next clean build:** a normal boot leaves
|
||||
`/run/chrony-dhcp/<iface>.sources` = the NTS line **labeled `chronyd_var_run_t`**
|
||||
(`ls -Z`), no `lo.sources`, and `chronyc -N authdata` reaches `NTS`/cookies > 0 with
|
||||
**no manual intervention**.
|
||||
|
||||
## Toggle
|
||||
|
||||
|
||||
@@ -27,7 +27,11 @@
|
||||
# /usr/lib/NetworkManager/dispatcher.d/20-chrony-dhcp writing option-42 servers to
|
||||
# the SAME <iface>.sources. NM merges /etc + /usr/lib sorted by name, so it runs
|
||||
# AFTER us and would clobber the NTS line — the provisioner removes it (it defeated
|
||||
# NTS on the bench: chrony fell back to plain NTP). This script supersedes it.
|
||||
# NTS on the bench: chrony fell back to plain NTP). This script supersedes it. It
|
||||
# was ALSO (accidentally) labelling the sources chronyd_var_run_t so chronyd could
|
||||
# read them; we restore that via /etc/tmpfiles.d/chrony-dhcp.conf (see the dir note
|
||||
# below) — without it, under enforcing SELinux chronyd can't read the sources and
|
||||
# loads nothing (bench 2026-07-12).
|
||||
|
||||
MODE="@NTP_MODE@" # nts | dhcp
|
||||
PREFIX="@NTP_PREFIX@" # nts: server name = <PREFIX>.<dhcp-domain>
|
||||
@@ -40,6 +44,15 @@ case "$action" in
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
# Loopback carries no DHCP NTP config; acting on it just writes a spurious
|
||||
# lo.sources duplicate of the same server (the distro 20-chrony-dhcp skipped lo —
|
||||
# we must too, or chrony sees two files for one server). Skip it.
|
||||
[ "$interface" = lo ] && exit 0
|
||||
|
||||
# /run/chrony-dhcp is pre-created with the chronyd_var_run_t SELinux label by
|
||||
# /etc/tmpfiles.d/chrony-dhcp.conf at boot, so the files we write below inherit a
|
||||
# label chronyd_t can READ (else, under enforcing, chronyd is denied and loads no
|
||||
# sources — bench 2026-07-12). This mkdir is just a fallback if tmpfiles didn't run.
|
||||
dir=/run/chrony-dhcp
|
||||
file="$dir/${interface}.sources"
|
||||
mkdir -p "$dir"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Pre-create chrony's runtime NTP source directory with the CORRECT SELinux label
|
||||
# at boot, before any NetworkManager event fires. The NM dispatcher
|
||||
# (10-chrony-dhcp-ntp) writes <iface>.sources here for chronyd's
|
||||
# `sourcedir /run/chrony-dhcp`.
|
||||
#
|
||||
# Why this exists: the dispatcher's own `mkdir -p /run/chrony-dhcp` creates the dir
|
||||
# as generic `var_run_t`, and the source files inherit it — but `chronyd_t` is only
|
||||
# allowed to read `chronyd_var_run_t`, so under enforcing SELinux chronyd is DENIED
|
||||
# reading them and silently loads ZERO sources (bench 2026-07-12, comet-beacon-54:
|
||||
# NTS never activated — `chronyc authdata` empty; AVC `denied { read } … chronyd_t
|
||||
# … var_run_t`). The stock RHEL policy already maps /run/chrony-dhcp →
|
||||
# `chronyd_var_run_t`, and systemd-tmpfiles applies that fcontext when it creates
|
||||
# the dir here at sysinit (well before the dispatcher runs). Files the dispatcher
|
||||
# then creates inside inherit `chronyd_var_run_t`, so chronyd can read them — no
|
||||
# SELinux allow rules needed (verified on the bench).
|
||||
#
|
||||
# The distro chrony NM dispatcher (/usr/lib/.../20-chrony-dhcp) used to apply this
|
||||
# label via a per-file restorecon; we removed it because it clobbered our NTS source
|
||||
# (see diskless.pkr.hcl + the 10-chrony-dhcp-ntp header), so we restore the labelling
|
||||
# here instead. Needed in BOTH secure_ntp modes (chronyd must read the sourcedir
|
||||
# either way).
|
||||
d /run/chrony-dhcp 0755 root root - -
|
||||
Reference in New Issue
Block a user