Files
diskless-workstation/files/etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp
T
Audiobuzz f9f7c7c940 secure-ntp: fix NTS defects surfaced by the F-03 bench
NTS proven end-to-end on comet-beacon-54 (chronyc authdata cookies > 0); these
close the gaps the bench found so a clean build/serving run reaches that state
without manual patching:

- dispatcher (10-chrony-dhcp-ntp): fall back to resolv.conf search/domain when NM
  doesn't export DHCP4_DOMAIN_NAME on the firing event, so the NTS source is
  written reliably at boot (tonight it needed a manual dispatcher run).
- STIG vs secure_ntp (P2, security): STIG's chronyd_specify_remote_server re-injects
  an UNAUTHENTICATED 'server 0.us.pool.ntp.mil maxpoll 16' AFTER the secure_ntp
  chrony step commented servers out -> a silent fallback that defeats NTS's
  DoS-only guarantee (host not fail-closed). Unselect chronyd_specify_remote_server
  + chronyd_or_ntpd_set_maxpoll in diskless-tailoring.xml, and re-strip any
  server/pool in 2c-STIG-postfix when secure_ntp=true. Documented: stig-deviations #17.
- nts-cert.sh (P3): the container's chrony user (non-root) can't read a 0640
  host-owned key -> NTS-KE TLS aborts ('non-properly terminated'). chown the key to
  the ntp container's chrony uid + keep 0640 (0644 works but chrony warns
  world-readable); 0644 fallback if the uid can't be found.

Bench-verified 2026-07-11; docs updated in docs/ssh-host-certificate-design.md's
sibling secure-ntp-design.md flow. F-03/P-18 stays open until a clean build shows
chrony.conf has no plain server and authdata cookies>0 as the only source.
2026-07-11 11:21:46 +00:00

67 lines
2.9 KiB
Bash

#!/bin/bash
# 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:
#
# 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).
#
# 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.
MODE="@NTP_MODE@" # nts | dhcp
PREFIX="@NTP_PREFIX@" # nts: server name = <PREFIX>.<dhcp-domain>
interface="$1"
action="$2"
case "$action" in
up|dhcp4-change|reapply) ;;
*) exit 0 ;;
esac
dir=/run/chrony-dhcp
file="$dir/${interface}.sources"
mkdir -p "$dir"
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