Merge remote-tracking branch 'origin/main' into poam-hardening
# Conflicts: # CLAUDE.md
This commit is contained in:
@@ -1,16 +1,40 @@
|
||||
#!/bin/bash
|
||||
# NetworkManager dispatcher: feed DHCP option 42 (NTP servers) into chrony.
|
||||
# NetworkManager dispatcher: point chrony at the fleet NTP server, learned from
|
||||
# DHCP at runtime (no FQDN baked into the image). Two modes, baked at build time:
|
||||
#
|
||||
# Replaces the old dhclient.d/chrony.sh bridge — we use NetworkManager's internal
|
||||
# DHCP now (the network-manager dracut port), and NM exports the lease's NTP
|
||||
# servers to dispatcher scripts as $DHCP4_NTP_SERVERS. chrony.conf carries
|
||||
# `sourcedir /run/chrony-dhcp`, so writing <iface>.sources there and reloading
|
||||
# makes chronyd use the DHCP-provided server. The image's baked public pool is
|
||||
# stripped (see the provisioner), so the host syncs ONLY from the lab NTP server
|
||||
# — no time egress from the workstation.
|
||||
# nts (secure_ntp=true, default): AUTHENTICATED time. Derive the server NAME
|
||||
# from the DHCP-provided domain as <PREFIX>.<domain> and write
|
||||
# `server NAME iburst nts`. chrony verifies the NTS-KE TLS cert against the
|
||||
# fleet CA (chrony.conf: ntstrustedcerts). Because the domain comes from
|
||||
# DHCP, a domain change needs only a new DHCP domain + reissued NTS cert —
|
||||
# no image rebuild. A DHCP/NTP/DNS spoofer can only DoS (no fleet-CA-signed
|
||||
# cert for the name it redirects to => NTS fails => no sync); it cannot
|
||||
# shift the clock. See docs/secure-ntp-design.md (F-03/P-18).
|
||||
#
|
||||
# Installed 0755 root:root at /etc/NetworkManager/dispatcher.d/. Lenient on
|
||||
# purpose: a hiccup here must never break networking.
|
||||
# dhcp (secure_ntp=false): LEGACY, UNAUTHENTICATED. Write the DHCP option-42
|
||||
# server IPs as plain `server <ip> iburst`. Lab/interop only.
|
||||
#
|
||||
# We use NetworkManager's internal DHCP (the network-manager dracut port); NM
|
||||
# exports the lease's domain as $DHCP4_DOMAIN_NAME (option 15) / $DHCP4_DOMAIN_SEARCH
|
||||
# (option 119) and NTP servers as $DHCP4_NTP_SERVERS (option 42). chrony.conf
|
||||
# carries `sourcedir /run/chrony-dhcp`, so writing <iface>.sources there and
|
||||
# reloading makes chronyd use them. The baked public pool is stripped (provisioner).
|
||||
#
|
||||
# Installed 0755 root:root. Lenient on purpose: a hiccup here must never break
|
||||
# networking. @NTP_MODE@/@NTP_PREFIX@ are substituted at build time.
|
||||
#
|
||||
# NB: the chrony package ships its OWN dispatcher at
|
||||
# /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. 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>
|
||||
|
||||
interface="$1"
|
||||
action="$2"
|
||||
@@ -20,17 +44,42 @@ 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"
|
||||
|
||||
if [ -n "${DHCP4_NTP_SERVERS:-}" ]; then
|
||||
: > "$file"
|
||||
for s in $DHCP4_NTP_SERVERS; do
|
||||
printf 'server %s iburst\n' "$s" >> "$file"
|
||||
done
|
||||
# Pick up the new sources without restarting chronyd.
|
||||
chronyc reload sources >/dev/null 2>&1 || true
|
||||
if [ "$MODE" = nts ]; then
|
||||
# Prefer option 15 (domain-name); fall back to the first option-119 search
|
||||
# domain; finally fall back to resolv.conf's search/domain. NM does not export
|
||||
# $DHCP4_DOMAIN_NAME on every dispatcher event — at boot the source wasn't
|
||||
# written because the firing event carried no DHCP domain in env, yet
|
||||
# resolv.conf already had `search <domain>` (bench 2026-07-11). Reading it here
|
||||
# makes the NTS source get written reliably regardless of which event fires.
|
||||
domain="${DHCP4_DOMAIN_NAME:-}"
|
||||
[ -z "$domain" ] && domain="${DHCP4_DOMAIN_SEARCH%% *}"
|
||||
[ -z "$domain" ] && domain=$(awk '/^(search|domain)[[:space:]]/{print $2; exit}' /etc/resolv.conf 2>/dev/null)
|
||||
if [ -n "$domain" ]; then
|
||||
printf 'server %s.%s iburst nts\n' "$PREFIX" "$domain" > "$file"
|
||||
# Pick up the new source without restarting chronyd.
|
||||
chronyc reload sources >/dev/null 2>&1 || true
|
||||
fi
|
||||
else
|
||||
if [ -n "${DHCP4_NTP_SERVERS:-}" ]; then
|
||||
: > "$file"
|
||||
for s in $DHCP4_NTP_SERVERS; do
|
||||
printf 'server %s iburst\n' "$s" >> "$file"
|
||||
done
|
||||
chronyc reload sources >/dev/null 2>&1 || true
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -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 - -
|
||||
@@ -171,5 +171,20 @@
|
||||
<xccdf:select idref="xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_reboot" selected="false"/>
|
||||
<xccdf:select idref="xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction" selected="false"/>
|
||||
|
||||
<!-- NTP time source (secure_ntp): the fleet configures chrony's time source
|
||||
DYNAMICALLY at runtime — a fleet-CA-authenticated NTS server derived from
|
||||
the DHCP domain (secure_ntp=true) or DHCP option-42 (false) — via the NM
|
||||
dispatcher + `sourcedir /run/chrony-dhcp`, NOT a static baked `server`
|
||||
line. These rules remediate by INJECTING an unauthenticated
|
||||
`server <pool> maxpoll 16` into chrony.conf; under secure_ntp that plain
|
||||
server is a silent fallback that DEFEATS the NTS DoS-only guarantee (a
|
||||
DNS/NTP spoofer could shift the clock through it — the exact F-03 risk).
|
||||
Unselect so remediation adds no plain server; 2c-STIG-postfix also
|
||||
re-strips any server/pool when secure_ntp=true (belt-and-suspenders).
|
||||
The STIG intent (time IS synchronised, authenticated under NTS) is met.
|
||||
Justified in docs/stig-deviations.md #17. -->
|
||||
<xccdf:select idref="xccdf_org.ssgproject.content_rule_chronyd_specify_remote_server" selected="false"/>
|
||||
<xccdf:select idref="xccdf_org.ssgproject.content_rule_chronyd_or_ntpd_set_maxpoll" selected="false"/>
|
||||
|
||||
</xccdf:Profile>
|
||||
</xccdf:Tailoring>
|
||||
|
||||
Reference in New Issue
Block a user