Merge remote-tracking branch 'origin/main' into poam-hardening
# Conflicts: # CLAUDE.md
This commit is contained in:
@@ -36,6 +36,14 @@ files/hostapi/ Go host-info agent (fleet-hostapi): re
|
|||||||
loopback :9099 /v1/host for the kiosk SPA
|
loopback :9099 /v1/host for the kiosk SPA
|
||||||
(HOST-API.md contract). Built in 2b-hostapi,
|
(HOST-API.md contract). Built in 2b-hostapi,
|
||||||
fapolicyd-trusted, golang removed in 2d.
|
fapolicyd-trusted, golang removed in 2d.
|
||||||
|
files/ssh-hostcert/ Go SSH host-cert client (fleet-ssh-hostcert):
|
||||||
|
at boot signs this host's per-boot SSH host
|
||||||
|
key with the fleet SSH CA via the enrol-broker
|
||||||
|
host plane (TPM-identity mTLS), so admins trust
|
||||||
|
via one @cert-authority line instead of per-boot
|
||||||
|
TOFU (F-02/P-17). Built in 2b-ssh-hostcert,
|
||||||
|
fapolicyd-trusted; enable_ssh_hostcert flag.
|
||||||
|
docs/ssh-host-certificate-design.md.
|
||||||
scripts/extract-artifacts.sh qcow2 -> kernel/initramfs/erofs+verity/cmdline
|
scripts/extract-artifacts.sh qcow2 -> kernel/initramfs/erofs+verity/cmdline
|
||||||
scripts/build-oci.sh ukify + sbsign + roothash gate + OCI image
|
scripts/build-oci.sh ukify + sbsign + roothash gate + OCI image
|
||||||
scripts/build-ipxe-oci.sh sbsign snponly.efi -> FROM-scratch fleet-ipxe image
|
scripts/build-ipxe-oci.sh sbsign snponly.efi -> FROM-scratch fleet-ipxe image
|
||||||
@@ -156,7 +164,11 @@ instead of working around it.
|
|||||||
1. **Signing keys never enter the build VM or this repo.** `sb_key` /
|
1. **Signing keys never enter the build VM or this repo.** `sb_key` /
|
||||||
`sb_cert` are CI/HSM-provided paths on the build host. Never commit key
|
`sb_cert` are CI/HSM-provided paths on the build host. Never commit key
|
||||||
material, never copy keys into provisioner steps, never echo them in
|
material, never copy keys into provisioner steps, never echo them in
|
||||||
logs. Unsigned builds are lab-only and must keep their loud warning.
|
logs. Unsigned builds are **local-lab-only** (a bare `packer build` without
|
||||||
|
keys) and must keep their loud warning; **CI fails closed** — the
|
||||||
|
`gitea-main-ci.yaml`/`ipxe-image.yaml` "Production build gate" aborts unless
|
||||||
|
`SB_KEY`/`SB_CERT` (and `FLEET_CA` for the UKI build) are set, so no unsigned
|
||||||
|
or placeholder-CA artifact is producible via CI (POA&M P-03/P-05).
|
||||||
1. **Reproducibility of the root image is a security property.**
|
1. **Reproducibility of the root image is a security property.**
|
||||||
`mkfs.erofs` runs with fixed UUID and `-T 0`; the verity root hash must
|
`mkfs.erofs` runs with fixed UUID and `-T 0`; the verity root hash must
|
||||||
be a pure function of the input tree. Don’t introduce timestamps,
|
be a pure function of the input tree. Don’t introduce timestamps,
|
||||||
@@ -214,8 +226,10 @@ instead of working around it.
|
|||||||
are evaluated, and `stig-deviations.sh` flags an INVALID SCAN + exits 2. Also
|
are evaluated, and `stig-deviations.sh` flags an INVALID SCAN + exits 2. Also
|
||||||
confirm the `...content_profile_stig` profile id exists in the chosen
|
confirm the `...content_profile_stig` profile id exists in the chosen
|
||||||
datastream; (b) remediation must not break
|
datastream; (b) remediation must not break
|
||||||
boot/login (NM/NVMe, key-only sshd, kiosk, SELinux enforcing) — `enable_stig=false`
|
boot/login (NM/NVMe, key-only sshd, kiosk, SELinux enforcing) — waive the
|
||||||
is the escape hatch; (c) **reproducibility**: build twice and confirm identical
|
offending rule in the tailoring as a justified deviation (STIG is now
|
||||||
|
**enforced-true** by Packer variable validation — `enable_stig=false` is no
|
||||||
|
longer a build path; POA&M P-03); (c) **reproducibility**: build twice and confirm identical
|
||||||
`root_hash` in verity.json — if it drifts, oscap wrote something
|
`root_hash` in verity.json — if it drifts, oscap wrote something
|
||||||
nondeterministic (timestamp/uuid/key); find + normalize it in 2d hygiene;
|
nondeterministic (timestamp/uuid/key); find + normalize it in 2d hygiene;
|
||||||
(d) read the generated `stig-deviations.md` — every REVIEW row is a real finding
|
(d) read the generated `stig-deviations.md` — every REVIEW row is a real finding
|
||||||
@@ -247,13 +261,36 @@ instead of working around it.
|
|||||||
**re-check reproducibility** (semanage rebuilds the policy store → possible
|
**re-check reproducibility** (semanage rebuilds the policy store → possible
|
||||||
verity-hash drift). The triage waivers/decisions live in the tailoring +
|
verity-hash drift). The triage waivers/decisions live in the tailoring +
|
||||||
docs/stig-deviations.md #12; the generated report should show 0–1 REVIEW.
|
docs/stig-deviations.md #12; the generated report should show 0–1 REVIEW.
|
||||||
- NTP via DHCP option 42 → chrony (no public-pool egress): confirm on a booted
|
- **NTP is `secure_ntp=true` by default (NTS, RFC 8915) — CA-authenticated time**
|
||||||
host `chronyc sources` lists the DHCP-handed lab server (not `*.pool.ntp.org`).
|
(closes F-03/P-18; docs/secure-ntp-design.md). **No FQDN is baked:** the NM
|
||||||
Assumes (a) NM exports the lease's NTP servers to the dispatcher as
|
dispatcher (`10-chrony-dhcp-ntp`, mode-aware) derives the server name at runtime
|
||||||
`$DHCP4_NTP_SERVERS`, and (b) RHEL 9 `/etc/chrony.conf` honours
|
as `<ntp_prefix>.<DHCP-provided-domain>` (default `ntp`.`$DHCP4_DOMAIN_NAME`,
|
||||||
`sourcedir /run/chrony-dhcp` (the provisioner appends it if absent). If sources
|
e.g. `ntp.fleet.lab`), writes `server … iburst nts` into `sourcedir
|
||||||
are empty, the dispatcher didn't fire or the env var name differs — check
|
/run/chrony-dhcp`, and chrony verifies the NTS-KE TLS cert against the **fleet
|
||||||
`journalctl -u NetworkManager` and `ls /run/chrony-dhcp/`.
|
CA** (`/etc/dot1x/radius-ca.pem` — reused; baked as `ntstrustedcerts`). Only the
|
||||||
|
`ntp` label + the CA are baked, so the **site domain can change without a
|
||||||
|
rebuild** (new DHCP domain + reissued NTS cert). A DHCP/NTP/DNS spoofer can only
|
||||||
|
DoS (no fleet-CA-signed cert for the name it redirects to ⇒ NTS fails ⇒ no sync),
|
||||||
|
never shift the clock. Serving needs: DHCP **option 15** (domain-name) handed to
|
||||||
|
hosts, a **fleet-signed** NTS cert with DNS SAN `ntp.<domain>`
|
||||||
|
(`serving/chrony/nts-cert.sh`), and **tcp/4460** (NTS-KE) open + udp/123. Confirm
|
||||||
|
on a booted host: `chronyc -N authdata` shows the source in `NTS` mode, cookies
|
||||||
|
> 0. `nocerttimecheck 1` lets a cold boot clock (no RTC) bootstrap the first sync.
|
||||||
|
**Bench finding (2026-07-11, FIXED):** the chrony RPM ships its OWN NM dispatcher
|
||||||
|
`/usr/lib/NetworkManager/dispatcher.d/20-chrony-dhcp`; NM merges `/etc` + `/usr/lib`
|
||||||
|
dispatchers by name, so it ran AFTER our `10-chrony-dhcp-ntp` and overwrote the NTS
|
||||||
|
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. **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`).
|
||||||
- TLS audit offload (2f → `serving/audit-collector`, gtls/6514, since
|
- TLS audit offload (2f → `serving/audit-collector`, gtls/6514, since
|
||||||
2026-07-12): the plaintext-514 path was bench-verified; the TLS upgrade is
|
2026-07-12): the plaintext-514 path was bench-verified; the TLS upgrade is
|
||||||
NOT yet. On a booted host confirm the sender is ESTABLISHED to **:6514**
|
NOT yet. On a booted host confirm the sender is ESTABLISHED to **:6514**
|
||||||
@@ -265,6 +302,7 @@ instead of working around it.
|
|||||||
same ufw trap as 514), image built with the real fleet CA. The three
|
same ufw trap as 514), image built with the real fleet CA. The three
|
||||||
`rsyslog_encrypt_offload_*` STIG rules are enforced (waivers dropped) and
|
`rsyslog_encrypt_offload_*` STIG rules are enforced (waivers dropped) and
|
||||||
must PASS in the report.
|
must PASS in the report.
|
||||||
|
- `fleet` Plymouth theme: confirm (a) the script plugin package actually
|
||||||
installed (`/usr/lib64/plymouth/script.so` — else the build fell back to
|
installed (`/usr/lib64/plymouth/script.so` — else the build fell back to
|
||||||
`spinner`, logged as a WARNING), (b) the scripted theme + a font got bundled
|
`spinner`, logged as a WARNING), (b) the scripted theme + a font got bundled
|
||||||
into the initramfs (`lsinitrd | grep -E 'fleet.script|script.so'`), and (c) it
|
into the initramfs (`lsinitrd | grep -E 'fleet.script|script.so'`), and (c) it
|
||||||
@@ -320,7 +358,7 @@ These bit us bringing the build up on Rocky 9.8; they are easy to regress.
|
|||||||
stack stays in the image, dormant. The matching lab DHCP/HTTP/NVMe-oF serving
|
stack stays in the image, dormant. The matching lab DHCP/HTTP/NVMe-oF serving
|
||||||
stack lives in `serving/` (docker compose). Re-enable security by dropping the
|
stack lives in `serving/` (docker compose). Re-enable security by dropping the
|
||||||
var / setting `DISABLE_DOT1X: "false"` in the CI workflow.
|
var / setting `DISABLE_DOT1X: "false"` in the CI workflow.
|
||||||
- **STIG toggle**: `enable_stig=true` (default) applies DISA STIG via
|
- **STIG toggle** (now **enforced-true**, not just default): `enable_stig=true` applies DISA STIG via
|
||||||
OpenSCAP/SSG in a provisioner, tailored for the diskless/UKI/kiosk architecture
|
OpenSCAP/SSG in a provisioner, tailored for the diskless/UKI/kiosk architecture
|
||||||
(`files/stig/diskless-tailoring.xml` unselects the N/A or
|
(`files/stig/diskless-tailoring.xml` unselects the N/A or
|
||||||
dangerous-to-remediate rules; every waiver is justified in
|
dangerous-to-remediate rules; every waiver is justified in
|
||||||
@@ -332,8 +370,11 @@ These bit us bringing the build up on Rocky 9.8; they are easy to regress.
|
|||||||
**report-only**: it publishes `stig-report.html` + `stig-arf.xml` +
|
**report-only**: it publishes `stig-report.html` + `stig-arf.xml` +
|
||||||
`stig-deviations.md` (generated by `scripts/stig-deviations.sh`) as artifacts
|
`stig-deviations.md` (generated by `scripts/stig-deviations.sh`) as artifacts
|
||||||
and never fails on score. The scanner tooling is removed from the shipped image
|
and never fails on score. The scanner tooling is removed from the shipped image
|
||||||
(2d hygiene). `enable_stig=false` is the escape hatch if hardening regresses
|
(2d hygiene). **`enable_stig` is enforced `true` by Packer variable validation
|
||||||
the boot/secure-path bench. NEVER let a waiver hide a real finding — anything
|
(POA&M P-03) — a build with it false fails `packer validate`/`build`; there is
|
||||||
|
no unhardened build path.** If hardening regresses the boot/secure-path bench,
|
||||||
|
waive the offending rule in the tailoring as a justified deviation (never by
|
||||||
|
disabling STIG). NEVER let a waiver hide a real finding — anything
|
||||||
non-pass that isn't in the register shows as **REVIEW** in the report.
|
non-pass that isn't in the register shows as **REVIEW** in the report.
|
||||||
- **Boot splash toggle**: `boot_splash=true` (default) bakes `rhgb quiet` for the
|
- **Boot splash toggle**: `boot_splash=true` (default) bakes `rhgb quiet` for the
|
||||||
Plymouth splash; `false` bakes `plymouth.enable=0` for a verbose console boot —
|
Plymouth splash; `false` bakes `plymouth.enable=0` for a verbose console boot —
|
||||||
|
|||||||
+63
-6
@@ -164,6 +164,18 @@ variable "audit_host" {
|
|||||||
default = "audit.fleet.lab"
|
default = "audit.fleet.lab"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "secure_ntp" {
|
||||||
|
type = bool
|
||||||
|
description = "true (default): SECURE, CA-AUTHENTICATED time via NTS (RFC 8915). The NM dispatcher derives the NTP server name at runtime as <ntp_prefix>.<DHCP-provided-domain> (NO FQDN baked — the domain can change without a rebuild) and chrony verifies the server's NTS-KE TLS cert against the fleet CA (/etc/dot1x/radius-ca.pem — same anchor as RADIUS/agent). A DHCP/NTP/DNS spoofer can then only DoS (cause no-sync), never shift the clock to resurrect expired/revoked certs (closes F-03/P-18; docs/secure-ntp-design.md). The serving NTP server needs a fleet-signed NTS cert with SAN <ntp_prefix>.<domain> (serving/chrony/nts-cert.sh). false: legacy UNAUTHENTICATED DHCP option-42 NTP (the dispatcher writes the handed IPs) — lab/interop only."
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ntp_prefix" {
|
||||||
|
type = string
|
||||||
|
description = "secure_ntp=true only: the label prepended to the DHCP-provided domain to form the NTS NTP server name (<ntp_prefix>.<domain>, e.g. ntp.fleet.lab). Deliberately NOT a full FQDN — only this label + the fleet CA are baked, so the environment's domain can change (new DHCP domain + reissued NTS cert) without rebuilding the image. Must match the serving NTS cert SAN and resolve via dnsmasq."
|
||||||
|
default = "ntp"
|
||||||
|
}
|
||||||
|
|
||||||
variable "sb_key" {
|
variable "sb_key" {
|
||||||
type = string
|
type = string
|
||||||
description = "Secure Boot signing key (PEM) for the UKI. Empty = unsigned lab build. Production: CI/HSM-held; never enters the build VM."
|
description = "Secure Boot signing key (PEM) for the UKI. Empty = unsigned lab build. Production: CI/HSM-held; never enters the build VM."
|
||||||
@@ -179,7 +191,7 @@ variable "sb_cert" {
|
|||||||
|
|
||||||
variable "fleet_ca" {
|
variable "fleet_ca" {
|
||||||
type = string
|
type = string
|
||||||
description = "Path on the BUILD HOST to the fleet CA PEM baked to /etc/dot1x/radius-ca.pem — the trust anchor wpa_supplicant uses for RADIUS EAP-TLS, the fleet-agent uses to verify the fleet-manager, AND rsyslog uses to verify the audit collector (TLS offload, 2f). Defaults to the COMMITTED PLACEHOLDER (files/etc/dot1x/radius-ca.pem), which is NOT a usable CA (auth/telemetry/log-offload can't succeed with it). Override per-environment (CI secret -> temp file) with your real step-ca root (or a root+intermediate bundle for robustness). Never commit real internal CA material (CLAUDE.md)."
|
description = "Path on the BUILD HOST to the fleet CA PEM baked to /etc/dot1x/radius-ca.pem — the trust anchor wpa_supplicant uses for RADIUS EAP-TLS, the fleet-agent uses to verify the fleet-manager, rsyslog uses to verify the audit collector (TLS offload, 2f), AND chrony uses to verify the NTS time server (ntstrustedcerts, secure_ntp). Defaults to the COMMITTED PLACEHOLDER (files/etc/dot1x/radius-ca.pem), which is NOT a usable CA (auth/telemetry/log-offload/NTS can't succeed with it). Override per-environment (CI secret -> temp file) with your real step-ca root (or a root+intermediate bundle for robustness). Never commit real internal CA material (CLAUDE.md)."
|
||||||
default = "files/etc/dot1x/radius-ca.pem"
|
default = "files/etc/dot1x/radius-ca.pem"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,15 +433,51 @@ build {
|
|||||||
# secure mode, reserving the physical NIC for wpa_supplicant.
|
# secure mode, reserving the physical NIC for wpa_supplicant.
|
||||||
"chmod 0755 /usr/lib/dracut/modules.d/95dot1x-macsec/*.sh",
|
"chmod 0755 /usr/lib/dracut/modules.d/95dot1x-macsec/*.sh",
|
||||||
|
|
||||||
# ---- NTP from DHCP (option 42) -> chrony, no public-pool egress --------
|
# ---- NTP: CA-authenticated NTS (default) or legacy DHCP option 42 ------
|
||||||
# NM dispatcher writes DHCP-provided NTP servers into chrony's sourcedir
|
# BOTH modes run through the SAME machinery: the NM dispatcher writes a
|
||||||
# (replaces the removed dhclient.d/chrony.sh bridge). Strip the baked public
|
# source file into chrony's `sourcedir /run/chrony-dhcp`; the baked public
|
||||||
# pool so the host syncs ONLY from the lab NTP server handed via DHCP — the
|
# pool is stripped so the host never egresses to *.pool.ntp.org.
|
||||||
# workstation never reaches *.pool.ntp.org.
|
# secure_ntp=true (NTS): the dispatcher derives the server NAME at runtime
|
||||||
|
# as <ntp_prefix>.<DHCP-provided-domain> and writes `server NAME iburst
|
||||||
|
# nts`. chrony verifies the NTS-KE TLS cert against the FLEET CA
|
||||||
|
# (ntstrustedcerts below). NOTHING host- or domain-specific is baked
|
||||||
|
# (only the `ntp` label + the CA) — the domain can change without a
|
||||||
|
# rebuild. A DHCP/NTP/DNS spoofer can only DoS; it can't present a
|
||||||
|
# fleet-CA-signed cert for the name it redirects to, so it can't shift
|
||||||
|
# the clock (F-03/P-18). No fallback to unauthenticated time.
|
||||||
|
# nocerttimecheck 1: a diskless host may boot with a bad/epoch clock (no
|
||||||
|
# reliable RTC), failing the cert validity window (chicken-and-egg);
|
||||||
|
# allow the FIRST measurement without the cert-time check to bootstrap,
|
||||||
|
# then enforce. Isolated LAN + one-measurement window bound the trade-off.
|
||||||
|
# secure_ntp=false (legacy): the dispatcher writes the DHCP option-42 IPs
|
||||||
|
# as plain `server <ip> iburst` — UNAUTHENTICATED, lab/interop only.
|
||||||
|
# The dispatcher is mode-aware via baked @NTP_MODE@/@NTP_PREFIX@ tokens.
|
||||||
"install -D -m0755 /tmp/etc-stage/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp /etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp",
|
"install -D -m0755 /tmp/etc-stage/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp /etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp",
|
||||||
|
"if [ \"${var.secure_ntp}\" = \"true\" ]; then NTP_MODE=nts; else NTP_MODE=dhcp; fi",
|
||||||
|
"sed -i \"s/@NTP_MODE@/$NTP_MODE/; s/@NTP_PREFIX@/${var.ntp_prefix}/\" /etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp",
|
||||||
"restorecon /etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp",
|
"restorecon /etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp",
|
||||||
|
# Remove the chrony package's OWN NM dispatcher. NetworkManager merges
|
||||||
|
# dispatchers from /etc AND /usr/lib sorted by name, so /usr/lib/.../20-chrony-dhcp
|
||||||
|
# runs AFTER our 10-chrony-dhcp-ntp and writes the DHCP option-42 server to the
|
||||||
|
# SAME /run/chrony-dhcp/<iface>.sources — clobbering our NTS line and silently
|
||||||
|
# defeating authenticated time (bench 2026-07-11, comet-beacon-54: chrony fell
|
||||||
|
# back to plain NTP, authdata Mode '-', Cook 0; removing it → NTS Cook 8). Our
|
||||||
|
# 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",
|
"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",
|
"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",
|
||||||
# Enable chrony's tracking log so the fleet-agent can report clock health
|
# Enable chrony's tracking log so the fleet-agent can report clock health
|
||||||
# (source/stratum/offset) WITHOUT touching chronyd's command interface. That
|
# (source/stratum/offset) WITHOUT touching chronyd's command interface. That
|
||||||
# interface is unusable by the hardened agent: the unix socket is
|
# interface is unusable by the hardened agent: the unix socket is
|
||||||
@@ -1064,6 +1112,15 @@ build {
|
|||||||
" echo '==> fapolicyd: watch_fs now includes overlay (guards the volatile-overlay root)'",
|
" echo '==> fapolicyd: watch_fs now includes overlay (guards the volatile-overlay root)'",
|
||||||
" fi",
|
" fi",
|
||||||
"fi",
|
"fi",
|
||||||
|
# secure_ntp fail-closed: STIG's chronyd_specify_remote_server remediation
|
||||||
|
# re-adds an UNAUTHENTICATED `server <pool> maxpoll 16` to chrony.conf AFTER
|
||||||
|
# the secure_ntp chrony step commented the baked servers out (2c runs later).
|
||||||
|
# That plain server is a silent fallback that defeats NTS's DoS-only guarantee
|
||||||
|
# (a DNS/NTP spoofer could shift the clock through it — the F-03 risk). When
|
||||||
|
# secure_ntp=true, re-strip any server/pool so ONLY the NTS sourcedir +
|
||||||
|
# ntstrustedcerts remain. The rules are also unselected in the tailoring; this
|
||||||
|
# is belt-and-suspenders. Bench 2026-07-11; docs/stig-deviations.md #17.
|
||||||
|
"if [ \"${var.secure_ntp}\" = \"true\" ] && [ -f /etc/chrony.conf ]; then echo '==> secure_ntp: re-stripping STIG-injected plain NTP servers from chrony.conf (fail-closed: NTS only)'; sed -i -E 's/^[[:space:]]*(pool|server)[[:space:]]/#&/' /etc/chrony.conf; fi",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
-15
@@ -14,16 +14,32 @@ flipped back for a secure/production build (consolidated as item #8):
|
|||||||
- `nvmet_traddr` / CI `NVMET_TRADDR="192.168.50.10"` → hardcoded IP (should be a
|
- `nvmet_traddr` / CI `NVMET_TRADDR="192.168.50.10"` → hardcoded IP (should be a
|
||||||
per-env DNS name like `nvme.fleet.lab` once DNS-at-boot is trusted).
|
per-env DNS name like `nvme.fleet.lab` once DNS-at-boot is trusted).
|
||||||
- Unsigned UKI (`sb_key`/`sb_cert` not passed) → Secure Boot must be OFF on the
|
- Unsigned UKI (`sb_key`/`sb_cert` not passed) → Secure Boot must be OFF on the
|
||||||
test target. Production needs CI/HSM signing.
|
test target. Production needs CI/HSM signing. **NOTE (2026-07-11, `poam-hardening`):
|
||||||
- `enable_kiosk=true`; `boot_splash=true`; NTP via DHCP→serving-host chrony.
|
the main CI now fails closed** — a "Production build gate" aborts unless
|
||||||
- **`enable_stig` / CI `ENABLE_STIG="false"` RIGHT NOW** — temporarily disabled
|
`SB_KEY`/`SB_CERT`/`FLEET_CA` are set, so an unsigned/placeholder-CA build is no
|
||||||
to get a working box while the STIG regressions were diagnosed (now fixed,
|
longer producible via CI (only a local `packer build` without keys still yields
|
||||||
below). **Flip back to `true`** and bench-verify the hardened build.
|
the lab warning). The `fleet-ipxe` build is gated the same way.
|
||||||
- **`debug_shell=true` (DEBUG HOLE — passwordless root on tty2). REMOVE before
|
- `enable_kiosk=true`; `boot_splash=true`.
|
||||||
prod** (`-var debug_shell=false`). Note: it didn't actually come up on tty2 in
|
- **`secure_ntp` — DONE (2026-07-11, `secure-ntp`).** NTP is now **NTS
|
||||||
testing; diagnosis ended up done via offline `rootfs.erofs` mount + SMBIOS
|
(CA-authenticated)** by default: the host verifies the serving NTP server's
|
||||||
`enforcing=0`. SMBIOS cmdline-extra works on Proxmox only after a full VM
|
fleet-signed NTS-KE cert against `radius-ca.pem` (closes F-03/P-18;
|
||||||
**stop/start** (not `reset`).
|
docs/secure-ntp-design.md). **No baked FQDN** — the NM dispatcher derives the
|
||||||
|
server name from the DHCP domain at runtime (`ntp.<domain>`), so a site-domain
|
||||||
|
change needs no rebuild. `secure_ntp=false` reverts to the legacy unauthenticated
|
||||||
|
DHCP option-42 path. Serving needs `serving/chrony/nts-cert.sh` (DNS SAN
|
||||||
|
`ntp.<domain>`), DHCP option-15 domain handout, + tcp/4460 open. **BENCH-VERIFY**
|
||||||
|
`chronyc -N authdata` (NTS cookies) + `$DHCP4_DOMAIN_NAME` populated.
|
||||||
|
- **`enable_stig` — DONE (2026-07-11, `poam-hardening`).** CI `ENABLE_STIG="true"`
|
||||||
|
and, more importantly, the Packer template now **enforces** `enable_stig=true`
|
||||||
|
via variable validation — a build with it false fails `packer validate`/`build`.
|
||||||
|
There is no unhardened build path anymore. (Was temporarily off during the STIG
|
||||||
|
regression diagnosis; that escape hatch is gone.)
|
||||||
|
- **`debug_shell` — REMOVED (2026-07-11, `poam-hardening`).** The DEBUG HOLE
|
||||||
|
(passwordless root shell) and its `debug_shell` variable are deleted from the
|
||||||
|
template entirely; no build can bake it (closes POA&M P-02 / R-02). Offline
|
||||||
|
diagnosis via `rootfs.erofs` mount + SMBIOS `enforcing=0` remains the supported
|
||||||
|
path (SMBIOS cmdline-extra works on Proxmox only after a full VM **stop/start**,
|
||||||
|
not `reset`).
|
||||||
- **Admin SSH key MUST be ECDSA or RSA, NOT ed25519**, when STIG is on — the STIG
|
- **Admin SSH key MUST be ECDSA or RSA, NOT ed25519**, when STIG is on — the STIG
|
||||||
crypto policy drops ed25519 from sshd's accepted pubkey algorithms. Set the
|
crypto policy drops ed25519 from sshd's accepted pubkey algorithms. Set the
|
||||||
`SSH_AUTHORIZED_KEY` CI secret to an ECDSA/RSA key (see #2).
|
`SSH_AUTHORIZED_KEY` CI secret to an ECDSA/RSA key (see #2).
|
||||||
@@ -158,7 +174,8 @@ was incomplete:**
|
|||||||
tailoring; production TLS offload tracked under #8. **(DONE 2026-07-12 — 2f is
|
tailoring; production TLS offload tracked under #8. **(DONE 2026-07-12 — 2f is
|
||||||
now gtls/6514 and the three waivers are dropped; see #8.)**
|
now gtls/6514 and the three waivers are dropped; see #8.)**
|
||||||
- Diagnosis tooling notes: SMBIOS cmdline-extra works only after a Proxmox VM
|
- Diagnosis tooling notes: SMBIOS cmdline-extra works only after a Proxmox VM
|
||||||
**stop/start** (not `reset`); the debug-shell-on-tty2 never came up — the
|
**stop/start** (not `reset`); the debug-shell-on-tty2 never came up (and is now
|
||||||
|
removed from the template — see the build-posture section) — the
|
||||||
decisive diagnosis was **offline `rootfs.erofs` mount** + `ssh -vvv` +
|
decisive diagnosis was **offline `rootfs.erofs` mount** + `ssh -vvv` +
|
||||||
`enforcing=0`.
|
`enforcing=0`.
|
||||||
|
|
||||||
@@ -194,8 +211,9 @@ pass / N/A.
|
|||||||
`TYPE=sysadm_t ROLE=sysadm_r NOPASSWD:ALL` and maps the admin to **`staff_u`**
|
`TYPE=sysadm_t ROLE=sysadm_r NOPASSWD:ALL` and maps the admin to **`staff_u`**
|
||||||
(`semanage login`) so the sudo role transition is authorized. ⚠ **TWO BENCH
|
(`semanage login`) so the sudo role transition is authorized. ⚠ **TWO BENCH
|
||||||
GATES:** (a) admin SSH login + sudo + privileged ops must work under `staff_u`
|
GATES:** (a) admin SSH login + sudo + privileged ops must work under `staff_u`
|
||||||
confinement (escape hatch: `enable_stig=false` → reverts to a justified
|
confinement (if it locks out, waive the offending rule in the tailoring as a
|
||||||
deviation); (b) **reproducibility** — `semanage` rebuilds the policy store;
|
justified deviation — `enable_stig=false` is no longer available, STIG is
|
||||||
|
enforced-true); (b) **reproducibility** — `semanage` rebuilds the policy store;
|
||||||
confirm the verity hash is stable across two builds (normalize in 2d if it
|
confirm the verity hash is stable across two builds (normalize in 2d if it
|
||||||
drifts). Needs `policycoreutils-python-utils` (installed in 2c-STIG if absent).
|
drifts). Needs `policycoreutils-python-utils` (installed in 2c-STIG if absent).
|
||||||
|
|
||||||
@@ -297,8 +315,13 @@ and cleanup.
|
|||||||
"Permissions 640 too open" and refused to start). RHEL's `sshd-keygen@.service`
|
"Permissions 640 too open" and refused to start). RHEL's `sshd-keygen@.service`
|
||||||
regenerates them into the overlay before sshd starts. **⚠ Caveat:** the
|
regenerates them into the overlay before sshd starts. **⚠ Caveat:** the
|
||||||
ephemeral overlay means they regenerate **every boot** → the host key changes
|
ephemeral overlay means they regenerate **every boot** → the host key changes
|
||||||
per reboot (clients warn). Follow-up for a stable host identity: persist the
|
per reboot (clients warn). **RESOLVED (F-02/P-17, bench-verified 2026-07-11 on
|
||||||
host keys or derive them from the TPM (ties to the hostnqn/identity model).
|
comet-beacon-54):** rather than persist a key, each per-boot key is signed into
|
||||||
|
an OpenSSH host certificate by the fleet SSH CA via the enrol-broker host plane
|
||||||
|
at boot (`files/ssh-hostcert` → `fleet-ssh-hostcert`); admins trust one
|
||||||
|
`@cert-authority` line instead of TOFU. See
|
||||||
|
[ssh-host-certificate-design.md](ssh-host-certificate-design.md). Bench-surfaced
|
||||||
|
fixes on branch `poam-hardening` (`28d31e4`).
|
||||||
|
|
||||||
## [ ] 3. Roll the errata into design doc v3
|
## [ ] 3. Roll the errata into design doc v3
|
||||||
|
|
||||||
@@ -416,6 +439,10 @@ Path to getting here (don't re-discover):
|
|||||||
stripped from `/etc/chrony.conf`. Result: the workstation syncs time only
|
stripped from `/etc/chrony.conf`. Result: the workstation syncs time only
|
||||||
from the lab server — no external NTP egress. BENCH-VERIFY: on a booted host
|
from the lab server — no external NTP egress. BENCH-VERIFY: on a booted host
|
||||||
`chronyc sources` shows the lab server (the DHCP one), not a public pool.
|
`chronyc sources` shows the lab server (the DHCP one), not a public pool.
|
||||||
|
**SUPERSEDED (2026-07-11, `secure-ntp`) by `secure_ntp=true`:** the DHCP
|
||||||
|
option-42 path is now the *unauthenticated fallback*; the default is
|
||||||
|
NTS/CA-authenticated time (the DHCP source is no longer mixed in). See the
|
||||||
|
build-posture note above + docs/secure-ntp-design.md.
|
||||||
- **Image size:** Chromium+Mesa add hundreds of MB to the *uncompressed* erofs —
|
- **Image size:** Chromium+Mesa add hundreds of MB to the *uncompressed* erofs —
|
||||||
feeds the "shrink the rootfs" concern (and #7); reconsider whether a lighter
|
feeds the "shrink the rootfs" concern (and #7); reconsider whether a lighter
|
||||||
browser/render path is viable.
|
browser/render path is viable.
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
# Plan of Action & Milestones (POA&M) — Diskless Fleet Boot System
|
||||||
|
|
||||||
|
> **Protective marking:** `STARFLEET RESTRICTED — CLEARANCE LEVEL 2 — BRIDGE CREW / SECURITY DIVISION`
|
||||||
|
> *(marking scheme: `STARFLEET ROUTINE` ≈ OFFICIAL · `STARFLEET RESTRICTED` ≈
|
||||||
|
> OFFICIAL: Sensitive · `STARFLEET SECRET` ≈ PROTECTED · `OMEGA DIRECTIVE` ≈
|
||||||
|
> SECRET and above — see the [SAR](irap-sar.md) banner.)*
|
||||||
|
>
|
||||||
|
> **Source:** IRAP security assessment 2026-07-10 — see the
|
||||||
|
> [Security Assessment Report](irap-sar.md). Weaknesses derive from the SSP
|
||||||
|
> [Annex A residual-risk register](ssp-annex-a-ism-residual-risk.md) (R-NN) and
|
||||||
|
> the assessor's additional findings (F-NN, SAR §5.2). The repo's
|
||||||
|
> [TODO.md](TODO.md) remains the engineering-level working backlog; this POA&M
|
||||||
|
> is the authorisation-facing view and the two must be kept in sync.
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| POA&M version | 1.2 (adds P-24 / SAR F-08 — DNS/DHCP residual flows: kiosk HTTP, UKI rollback) |
|
||||||
|
| Date raised | 2026-07-10 |
|
||||||
|
| Last updated | 2026-07-11 |
|
||||||
|
| Review cadence | Monthly by System Manager; quarterly to the AO |
|
||||||
|
| Overall status | 0 of 24 items closed; P-02 engineering-complete (awaiting assessor M2) |
|
||||||
|
|
||||||
|
> **2026-07-11 remediation (`poam-hardening` branch).** The build pipeline is now
|
||||||
|
> a **fail-closed production profile** (POA&M P-03). Changes: (i) the passwordless
|
||||||
|
> debug shell was **deleted entirely** from the Packer template — no `debug_shell`
|
||||||
|
> variable, no `debug-shell.service` provisioner (P-02, R-02); (ii) `enable_stig`
|
||||||
|
> is enforced `true` by Packer **variable validation** — a build with it false
|
||||||
|
> fails `packer validate`/`build` (P-04); (iii) the CI "Production build gate"
|
||||||
|
> aborts the run unless `SB_KEY`, `SB_CERT` and `FLEET_CA` are set — no unsigned
|
||||||
|
> fallback, no placeholder RADIUS CA (P-03, P-05, P-06 CA wiring); the signed
|
||||||
|
> `fleet-ipxe` bootloader build is gated the same way. Not yet done in P-03 M1:
|
||||||
|
> `disable_dot1x=false` (blocked on P-01 secure-path bench) and distinct
|
||||||
|
> production-artifact tagging (M2). See the row-level status below.
|
||||||
|
|
||||||
|
**Severity** = assessor's pre-treatment rating. **Milestone dates** are the
|
||||||
|
assessor's recommended targets, to be confirmed by the System Owner on
|
||||||
|
appointment. Items marked **[GATE]** are conditions precedent for a production
|
||||||
|
ATO (SAR §7.2) — they cannot be risk-accepted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Production-ATO gating items
|
||||||
|
|
||||||
|
| # | Weakness | Source | Severity | Remediation actions & milestones | Owner | Target | Status |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| P-01 **[GATE]** | Secure path (802.1X EAP-TLS + MACsec) implemented but never verified end-to-end; disabled in current builds (`disable_dot1x=true`). | R-01 | **High** | M1: complete `serving/dot1x-bench/` harness config (swtpm + hostapd + FreeRADIUS) — 2026-08-07. M2: bench pass: port `Authorized`, macsec0 addressed + DHCP, MKA survives switch-root, NVMe root over macsec0, lease renews — 2026-08-21. M3: re-enable in CI (`DISABLE_DOT1X=false`) — 2026-08-28. M4: **assessor witness** at delta assessment. | Platform team | 2026-08-28 | Open |
|
||||||
|
| P-02 **[GATE]** | Passwordless root debug shell (`debug_shell=true`) present in lab builds — undocumented privileged access path. | R-02 | **High** | M1: ~~`debug_shell=false` default + CI assertion~~ **DONE (exceeded) 2026-07-11** — the `debug_shell` variable and the `debug-shell.service` provisioner were **removed entirely** from the template; no build can bake it. M2: assessor verifies absence on the assessed digest. | Platform team | 2026-07-31 | **M1 done — awaiting assessor (M2)** |
|
||||||
|
| P-03 **[GATE]** | No enforced production build profile — security toggles (dot1x, STIG, signing, debug shell, RADIUS CA) individually settable; drift detectable only by human review. | F-01 | **High** | M1: define a CI "production" profile hard-asserting `disable_dot1x=false`, `enable_stig=true`, `debug_shell=false`, `SB_KEY`/`SB_CERT` set, non-placeholder RADIUS CA — 2026-08-14. **Partly DONE 2026-07-11:** CI "Production build gate" hard-asserts `SB_KEY`/`SB_CERT`/`FLEET_CA` (fail-closed); `enable_stig=true` enforced by template validation; `debug_shell` removed. **Still open:** `disable_dot1x=false` (blocked on P-01) + M2 distinct production-artifact tagging. | Platform team + pipeline owner | 2026-08-21 | **M1 partial** |
|
||||||
|
| P-04 **[GATE]** | STIG hardening verified but `enable_stig` was toggled off during bring-up; production CI posture unconfirmed. | R-03 | Medium | M1: ~~`ENABLE_STIG=true` in CI~~ **DONE 2026-07-11** — `enable_stig` is enforced `true` by Packer variable validation (folds into P-03); `enable_stig=false` fails the build. M2: regenerate deviations report on the assessed build; confirm 0 unjustified REVIEW rows — 2026-08-21. | Platform team | 2026-08-21 | **M1 done** |
|
||||||
|
| P-05 **[GATE]** | Boot-chain signing + Secure Boot not the standing default (unsigned/SB-off builds possible; `db` not enrolled fleet-wide). | R-13 | Low (High impact if regressed) | M1: ~~CI signing secrets permanently set~~ **DONE 2026-07-11** — the CI Production build gate fails closed without `SB_KEY`/`SB_CERT`; no unsigned-UKI fallback remains (the signed `fleet-ipxe` build is gated too). M2: `db` enrolled on all production hosts via `fleet-sb-enroll`; enrolment recorded per host — 2026-09-11. M3: assessor spot-checks `mokutil --sb-state` + `sbverify` — delta assessment. | Platform team | 2026-09-11 | **M1 done** |
|
||||||
|
| P-06 **[GATE]** | Placeholder RADIUS CA — real EAP-TLS trust anchor not provisioned. | R-08 | Medium | M1: provision environment RADIUS CA out-of-band (classification permitting, not committed) — 2026-08-07 (pre-req for P-01 M2). | Environment owner | 2026-08-07 | Open |
|
||||||
|
| P-07 **[GATE]** | Governance layer absent: AO/System Owner/CISO unappointed; data classification unconfirmed; documentation unapproved. | SAR §1.4, §2.4.2 | **High** (blocks ATO structurally) | M1: appoint AO, System Owner, CISO — 2026-08-14. M2: System Owner confirms data classification; re-baseline SSP if above STARFLEET RESTRICTED — 2026-08-28. M3: AO approves documentation set — with ATO decision. | Organisation / System Owner | 2026-08-28 | Open |
|
||||||
|
| P-08 **[GATE]** | No Incident Response Plan, continuous-monitoring plan, or log-retention policy; volatile RAM overlay complicates forensics (no break-glass procedure). | R-12 | Medium | M1: author IRP incl. stateless-host forensics (capture-before-reboot guidance, collector/SIEM as evidence store) — 2026-09-04. M2: continuous-monitoring plan + log-retention policy — 2026-09-04. M3: tabletop exercise of the IRP — 2026-09-25. | SecOps | 2026-09-25 | Open |
|
||||||
|
| P-09 **[GATE]** | Audit-log offload plaintext on both hops (host→collector, collector→Wazuh); retention/archival unconfigured; Wazuh default credentials; dashboard/API unrestricted. | R-07 | Low–Medium | M1: rsyslog TLS (gtls/6514, fleet CA) host→collector; re-select the 3 waived `rsyslog_encrypt_offload_*` STIG rules — 2026-09-04 (also closes the DNS-spoofable audit-stream flow — F-08/P-24). M2: TLS collector→Wazuh — 2026-09-04. M3: rotate Wazuh credentials, restrict dashboard/API, set allowed-ips — 2026-08-21. M4: configure retention/archival per policy from P-08 — 2026-09-18. | Platform / SecOps | 2026-09-18 | Open |
|
||||||
|
| P-10 **[GATE]** | ISM control identifiers unreconciled against the May 2026 ISM release (representative numbering throughout Annex A). | SAR §2.4.1 (P-17 in SAR text) | Medium (assurance) | M1: reconcile all `ISM-NNNN*`; map any new May-2026 controls; disposition retired ones — 2026-08-21 (assessor task, System Owner to facilitate). | IRAP assessor + System Owner | 2026-08-21 | Open |
|
||||||
|
| P-11 **[GATE]** | Management/enrolment plane (fleet-manager, broker, step-ca fleet CA, InfluxDB) is an unauthorised connected system the host now trusts; fleet CA key protection unassessed. | R-14 | Medium | M1: author management-plane SSP (CA key protection, broker hardening incl. F-04 abuse controls, InfluxDB token/data, admin-plane exposure) — 2026-09-11. M2: separate assessment/authorisation or AO interim risk position covering the fleet CA — 2026-10-02. | Platform / SecOps | 2026-10-02 | Open |
|
||||||
|
|
||||||
|
## 2. Risk-acceptance candidates (production at STARFLEET ROUTINE, isolated LAN) — treat on roadmap
|
||||||
|
|
||||||
|
| # | Weakness | Source | Severity | Remediation actions & milestones | Owner | Target | Status |
|
||||||
|
|---|---|---|---|---|---|---|---|
|
||||||
|
| P-12 | NVMe/TCP root traffic plaintext on the LAN. | R-04 | Medium | M1: document transport decision (TLS/IPsec vs MACsec end-to-end across fabric) — 2026-09-25. M2: implement chosen transport protection — 2026-11-27. Interim: isolated LAN + dm-verity integrity accepted. | Platform / network | 2026-11-27 | Open |
|
||||||
|
| P-13 | Host admin access: NOPASSWD, fleet-generic, non-rotatable key; no human MFA on host SSH. | R-05 | Medium | M1: design per-host/rotatable access (SSH CA `TrustedUserCAKeys` or config agent) — 2026-10-16. M2: implement + bench under `staff_u` confinement — 2026-11-27. | Platform team | 2026-11-27 | Open |
|
||||||
|
| P-14 | AACA/AACP conformance not asserted; FIPS off; crypto-module evaluation unconfirmed. **Escalates to blocking if classification > STARFLEET RESTRICTED (P-07 M2).** | R-06 | Medium | M1: AACA/AACP conformance statement for EAP-TLS, MACsec-GCM, mTLS, SSH suites — 2026-09-25. M2: FIPS/evaluated-module decision alongside signing posture — 2026-10-30. | Security + platform | 2026-10-30 | Open |
|
||||||
|
| P-15 | Host firewall (firewalld) disabled on the workstation. | R-09 | Low–Medium | M1 (after P-01): re-enable with explicit allows (NVMe/TCP 4420, DHCP, log offload 6514, SSH; reciprocal egress rules) + bench NVMe/NM stability — 2026-09-25. | Platform / network | 2026-09-25 | Open |
|
||||||
|
| P-16 | Patch cadence / critical-CVE SLA undefined for the rebuild-redeploy model. | R-10 | Medium | M1: define cadence + CVE SLA — 2026-08-28. M2: automate rebuild triggers on upstream advisories — 2026-10-30. | Platform team | 2026-10-30 | Open |
|
||||||
|
| P-17 | SSH host keys regenerate every boot — admin connections are perpetual TOFU; LAN MITM undetectable client-side. | **F-02 (new — add to Annex A register)** | Medium | M1: add to risk register — 2026-07-24. M2: ~~stable TPM-derived host keys or SSH host CA~~ **IMPLEMENTED 2026-07-11 (`poam-hardening`)** — the **SSH host CA** path (Design A, broker-mediated): `fleet-ssh-hostcert` (files/ssh-hostcert) gets each host's per-boot key signed by the fleet SSH CA **via the enrol-broker host plane** (TPM-identity mTLS) at boot; admins trust via one `@cert-authority` line. Keeps step-ca behind the single front door. **BENCH-VERIFY** the broker↔step-ca SSH-CA contract + principal policy + SELinux (docs/ssh-host-certificate-design.md). M3: bench-verify end-to-end on a booted+enrolled host; roll the `@cert-authority` anchor to admins — 2026-10-16. | Platform team | 2026-10-16 | **M2 implemented — bench-verify (M3)** |
|
||||||
|
| P-18 | Time source unauthenticated (DHCP-handed NTP, no NTS) — host clock is an input to all certificate validation. | **F-03 (new — add to register)** | Low–Medium | M1: add to register — 2026-07-24. M2: ~~NTS or keyed NTP~~ **IMPLEMENTED 2026-07-11 (`secure-ntp`)** — **NTS (RFC 8915)**: the serving NTP server's NTS-KE TLS cert is fleet-CA-signed; the host (`secure_ntp=true`) verifies it against `radius-ca.pem` and syncs authenticated time. The server name is **derived at runtime from the DHCP domain** (`ntp.<domain>`) — no baked FQDN, so a site-domain change needs no rebuild. A DHCP/DNS spoofer can only DoS, not shift the clock. **BENCH-VERIFY** `chronyc authdata` NTS cookies + DHCP option-15 domain handout + tcp/4460 + STIG chrony (docs/secure-ntp-design.md). M3: bench-verify end-to-end; issue the NTS server cert per environment — 2026-10-30. | Platform team | 2026-10-30 | **M2 implemented — bench-verify (M3)** |
|
||||||
|
| P-19 | Fleet-wide availability SPOF: single unreplicated NVMe/TCP root target. | **F-05 (new — add to register)** | Medium (availability) | M1: interim `ctrl_loss_tmo`/`reconnect_delay` tuning at connect time — 2026-08-28. M2: multipath/ANA replica design (byte-identical same-UUID namespaces, multi-path discovery) — 2026-11-27. M3: document fleet RTO in IRP (P-08). | Platform team | 2026-11-27 | Open |
|
||||||
|
| P-20 | No remote attestation of TPM boot measurements (compromised boot not centrally detectable). | R-11 | Low–Medium | M1: design TPM quote → verifier flow (candidate: extend fleet-agent/manager, gated on P-11 authorisation) — 2027 roadmap. Interim compensation: SB + signed UKI + dm-verity + F-07 posture telemetry. | Platform team | 2027-Q1 | Open |
|
||||||
|
| P-21 | Runtime posture drift not fully monitored (SB state, fapolicyd enforcement, cmdline hash not in telemetry). | **F-07 (new)** | Low | M1: extend fleet-agent facts + manager drift alerts — 2026-09-25 (pairs with P-05 M2). | Platform team | 2026-09-25 | Open |
|
||||||
|
| P-22 | Cross-protocol TPM key reuse ("one identity, three stages") — domain-separation analysis and revocation blast-radius plan undocumented. | **F-06 (observation)** | Low | M1: add a short analysis + revocation procedure to the SSP — 2026-08-28. | Platform + security | 2026-08-28 | Open |
|
||||||
|
| P-23 | Agent reconcile actuators deferred; enabling them creates a remote host-mutation channel outside this assessment's scope. | R-15 | Low (v1) | Standing condition, not a dated milestone: **delta assessment of the command/authZ path required before any actuator is enabled** (SAR §7.3 revocation trigger). Keep behind an explicit build flag. | Platform team | At actuator build | Open (condition) |
|
||||||
|
| P-24 | DNS/DHCP untrusted-input pattern undocumented, and two flows escape it: **kiosk content over plain HTTP** (DNS/DHCP spoof → attacker page fullscreen on every workstation; Chromium PNA allowance to the loopback host-info agent reachable from it) and **UKI rollback** (a spoofer can serve an older, still-validly-signed UKI — Secure Boot does not prevent this). The plaintext audit offload was the third such flow; it is closed by P-09 M1 (cross-ref). DNSSEC/DoT is explicitly **not** the treatment — authenticate above resolution, per the system's own pattern. | **F-08 (new — add to register)** | Medium | M1: add F-08 to the Annex A register + state the "DNS/DHCP are untrusted; every security-relevant connection authenticates its peer above name resolution" principle in the SSP — 2026-08-14. M2: serve the kiosk over **TLS with a fleet-CA-signed cert** (serving nginx/kiosk proxy; pin the fleet CA via Chromium enterprise policy; kiosk URL → `https://`) — 2026-09-25. M3: record the UKI-rollback residual against R-13 and document the anti-rollback design decision (dbx revocation / TPM monotonic counter, design v2 §6) — 2026-11-27. | Platform team | 2026-11-27 | Open |
|
||||||
|
|
||||||
|
## 3. Milestone timeline (summary)
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-07 P-02 debug shell off ── P-17/P-18 register updates
|
||||||
|
2026-08 P-06 RADIUS CA ── P-01 secure-path bench ── P-03/P-04/P-05 prod profile+signing
|
||||||
|
P-07 roles+classification ── P-10 ISM reconciliation ── P-16 patch SLA ── P-22
|
||||||
|
P-24 register+SSP DNS principle
|
||||||
|
2026-09 P-08 IRP/monitoring ── P-09 log TLS+retention ── P-11 mgmt-plane SSP
|
||||||
|
P-15 firewall ── P-21 posture telemetry ── P-12/P-14 decisions ── P-24 kiosk TLS
|
||||||
|
2026-10 P-11 mgmt-plane authorisation ── P-13/P-17 access model ── P-14/P-18 crypto/NTS
|
||||||
|
── DELTA ASSESSMENT + production ATO decision window
|
||||||
|
2026-11 P-12 storage transport ── P-13 rotatable access ── P-19 multipath
|
||||||
|
P-24 anti-rollback decision
|
||||||
|
2027-Q1 P-20 remote attestation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Earliest realistic production-ATO decision: mid-October 2026**, contingent on
|
||||||
|
the delta assessment witnessing P-01 through P-11 closed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*End of POA&M. Maintain alongside [TODO.md](TODO.md); update item status at
|
||||||
|
each monthly review and re-issue to the AO quarterly or on material change.*
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
# Security Assessment Report (SAR) — Diskless Fleet Boot System
|
||||||
|
|
||||||
|
> **Protective marking:** `STARFLEET RESTRICTED — CLEARANCE LEVEL 2 — BRIDGE CREW / SECURITY DIVISION`
|
||||||
|
> *(Starfleet marking scheme used throughout this document set:
|
||||||
|
> `STARFLEET ROUTINE` ≈ OFFICIAL · `STARFLEET RESTRICTED` ≈ OFFICIAL: Sensitive ·
|
||||||
|
> `STARFLEET SECRET` ≈ PROTECTED · `OMEGA DIRECTIVE` ≈ SECRET and above.
|
||||||
|
> Confirm against the system's final data classification.)*
|
||||||
|
>
|
||||||
|
> **Assessment framework:** Australian Government PSPF + ASD *Information
|
||||||
|
> Security Manual* (ISM), May 2026 release (engagement target). This report is
|
||||||
|
> the IRAP assessor's Security Assessment Report produced under stage 4
|
||||||
|
> (**Assess**) of the ISM assessment and authorisation lifecycle, for use by the
|
||||||
|
> Authorising Officer at stage 5 (**Authorise**).
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| System assessed | Diskless Fleet Boot System ("the fleet") |
|
||||||
|
| SSP version assessed | v0.3 (2026-07-10) + Annex A v0.3 |
|
||||||
|
| Assessment type | Initial IRAP security assessment (design + implementation review; limited effectiveness witnessing) |
|
||||||
|
| Assessment period | 2026-07-10 |
|
||||||
|
| Assessor | IRAP Assessor (engaged) |
|
||||||
|
| Report version | 1.0 |
|
||||||
|
| Companion outputs | [Plan of Action & Milestones](irap-poam.md) (this assessment); SSP [Annex A](ssp-annex-a-ism-residual-risk.md) risk register |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive summary and determination
|
||||||
|
|
||||||
|
The Diskless Fleet Boot System is an unusually well-architected endpoint
|
||||||
|
platform whose central security proposition — **stateless, diskless hosts that
|
||||||
|
return to a cryptographically verified known-good state on every boot, with all
|
||||||
|
host identity anchored in hardware (TPM) and no per-host secrets in any
|
||||||
|
artifact** — is sound, clearly documented, and in several areas demonstrably
|
||||||
|
implemented and bench-verified. The engineering culture evidenced by the
|
||||||
|
documentation set is a material strength: findings are recorded honestly
|
||||||
|
(including a false-clean compliance scan and an ineffective application-control
|
||||||
|
configuration, both self-identified and fixed), deviations are registered and
|
||||||
|
justified, and verification claims are dated and traceable.
|
||||||
|
|
||||||
|
**Determination: the system is NOT yet recommended for a production Authority
|
||||||
|
to Operate.** The assessor recommends the AO consider a **time-limited interim
|
||||||
|
authorisation for continued lab/pre-production operation only**, with a
|
||||||
|
production ATO contingent on closure of the conditions in §7.
|
||||||
|
|
||||||
|
The determination rests on four grounds:
|
||||||
|
|
||||||
|
1. **The assessed configuration is not the target configuration.** The system's
|
||||||
|
headline preventive controls — 802.1X EAP-TLS network authentication and
|
||||||
|
MACsec link protection — are *disabled* in the current build
|
||||||
|
(`disable_dot1x=true`), have never been bench-verified end-to-end (R-01), and
|
||||||
|
the EAP-TLS trust anchor is a placeholder (R-08). The security architecture's
|
||||||
|
value is contingent on these controls; until they are demonstrated
|
||||||
|
operating, the assessment of them is a design review only.
|
||||||
|
2. **A known privileged back-door existed in lab builds — since removed.**
|
||||||
|
`debug_shell=true` baked a passwordless root shell (R-02). *Remediated
|
||||||
|
2026-07-11 (`poam-hardening`, POA&M P-02): the `debug_shell` variable and its
|
||||||
|
`debug-shell.service` provisioner were deleted from the template — no build can
|
||||||
|
bake it.* Assessor to verify absence on the assessed digest (P-02 M2).
|
||||||
|
3. **Boot-chain signing is proven but not the standing posture.** Secure Boot
|
||||||
|
enforcement of the signed UKI (and signed iPXE chain) has been verified on
|
||||||
|
real hardware — a significant achievement — but unsigned/SB-off builds remain
|
||||||
|
possible and `db` enrolment is not fleet-wide (R-13).
|
||||||
|
4. **The governance layer required for authorisation does not yet exist.** No
|
||||||
|
Authorising Officer, System Owner, or CISO is appointed; the data
|
||||||
|
classification is unconfirmed; there is no Incident Response Plan,
|
||||||
|
continuous-monitoring plan, or approved security documentation set. An ATO
|
||||||
|
cannot be granted without an AO, and the applicable ISM control baseline
|
||||||
|
cannot be finalised without a confirmed classification.
|
||||||
|
|
||||||
|
None of these grounds indicates architectural weakness; all are completion and
|
||||||
|
assurance gaps, most already self-identified in the SSP's own risk register.
|
||||||
|
The assessor **broadly concurs with the SSP Annex A risk register** (ratings and
|
||||||
|
treatments), with the adjustments and additional findings at §5.
|
||||||
|
|
||||||
|
## 2. Scope and methodology
|
||||||
|
|
||||||
|
### 2.1 What was assessed
|
||||||
|
|
||||||
|
- The **authorisation boundary** as defined in SSP §2.3: the running
|
||||||
|
workstation host — signed UKI, dracut initramfs (802.1X/MACsec module),
|
||||||
|
dm-verity/erofs root, tmpfs overlay, TPM-backed identity, STIG-hardened OS
|
||||||
|
configuration, kiosk, and the in-image `fleet-agent` telemetry client.
|
||||||
|
- Boundary crossings (a)–(g) per SSP §2.3, including the new v0.3
|
||||||
|
management-plane interface (outbound-only mTLS to the fleet-manager).
|
||||||
|
|
||||||
|
### 2.2 What was explicitly out of scope (connected systems)
|
||||||
|
|
||||||
|
The lab serving stack (DHCP/HTTP/NVMe-oF target, audit collector), the
|
||||||
|
server-side fleet-management plane (fleet-manager, admin UI, InfluxDB,
|
||||||
|
Traefik), the TPM enrolment broker and step-ca fleet CA, the RADIUS/802.1X
|
||||||
|
authenticator infrastructure, the Wazuh SIEM node, the network fabric, and
|
||||||
|
facility physical security. **These each require their own authorisation**; the
|
||||||
|
host's dependence on them is assessed here only at the interface. See finding
|
||||||
|
F-01 and risk R-14 — the AO should note that the host system's authorisation
|
||||||
|
is materially dependent on connected systems that are currently unauthorised.
|
||||||
|
|
||||||
|
### 2.3 Method and evidence base
|
||||||
|
|
||||||
|
- **Document review:** SSP v0.3, Annex A v0.3, architecture design v2 + errata,
|
||||||
|
fleet-management design, TPM enrolment design, STIG deviation register,
|
||||||
|
secure-boot key procedure, backlog/POA&M source (TODO.md).
|
||||||
|
- **Implementation-of-record review:** the repository is the implementation
|
||||||
|
(Packer template, dracut module, provisioners, CI workflows). Control
|
||||||
|
implementation statements were checked against the code and build gates
|
||||||
|
(e.g. the roothash coupling gate, the 0-rules-evaluated STIG scan guard, the
|
||||||
|
lsinitrd content assertions).
|
||||||
|
- **Verification evidence review:** dated bench-verification records for the
|
||||||
|
test boot path (2026-06-15/17), hardened build boot/login (2026-06-19/20),
|
||||||
|
audit offload end-to-end (2026-06-20), Wazuh decode+alert (2026-06-27),
|
||||||
|
Secure Boot enforcement on hardware (2026-06-20/22), fapolicyd overlay
|
||||||
|
effectiveness (2026-07-04), and fleet-agent TPM-mTLS telemetry (2026-07-10).
|
||||||
|
- **Not witnessed by the assessor:** the bench verifications above were
|
||||||
|
reviewed as records, not independently witnessed; the secure path (802.1X/
|
||||||
|
MACsec) has no verification record to review. Witness points for the
|
||||||
|
follow-up assessment are listed in §7.2.
|
||||||
|
|
||||||
|
### 2.4 Limitations and caveats
|
||||||
|
|
||||||
|
1. **ISM control-identifier currency.** The SSP self-declares its `ISM-NNNN`
|
||||||
|
identifiers as representative of a January 2026 baseline. The reconciliation
|
||||||
|
of every identifier against the May 2026 ISM release **remains open** and is
|
||||||
|
carried as POA&M item P-17. Control *intent* mappings were assessed and are
|
||||||
|
sound; individual numbers in Annex A must not be quoted to the AO as final.
|
||||||
|
2. **Classification not confirmed.** The assessment assumes the SSP's working
|
||||||
|
classification of STARFLEET ROUTINE / STARFLEET RESTRICTED. If the kiosk
|
||||||
|
renders or the workstation can reach STARFLEET SECRET data, the control
|
||||||
|
baseline changes materially (approved cryptography becomes mandatory — R-06
|
||||||
|
escalates from Medium to a blocking finding; gateway and media controls
|
||||||
|
tighten).
|
||||||
|
3. **Point-in-time.** This SAR reflects the system at 2026-07-10, lab
|
||||||
|
configuration, pre-production.
|
||||||
|
|
||||||
|
## 3. Assessment results by ISM guideline area
|
||||||
|
|
||||||
|
Effectiveness scale: **Effective** (implemented and operating as intended, with
|
||||||
|
evidence) · **Partially effective** (implemented with material gaps or lab-only
|
||||||
|
scope) · **Ineffective** (present but not achieving the control objective) ·
|
||||||
|
**Not implemented** · **Alternative control** (objective met by a justified
|
||||||
|
compensating mechanism) · **Not assessed** (no visibility / out of scope).
|
||||||
|
|
||||||
|
| ISM guideline area | Assessed effectiveness | Basis |
|
||||||
|
|---|---|---|
|
||||||
|
| Security documentation | **Partially effective** | High-quality SSP/annex/registers exist and are maintained; but no IRP, no continuous-monitoring plan, no AO approval, roles unappointed. |
|
||||||
|
| Cyber security roles | **Not implemented** | AO / System Owner / CISO all TBA. Blocking for authorisation. |
|
||||||
|
| OS & application hardening | **Effective** (current build) | STIG applied + bench-verified with a credible tailoring and deviation register; SELinux enforcing with three minimal documented local modules; fapolicyd allow-listing verified effective on the overlay root (post-2026-07-04 fix); USBGuard enforcing + verified; minimal install; kiosk lockdown. Residual: `enable_stig` must be confirmed on in production CI (R-03). |
|
||||||
|
| File integrity / anti-malware | **Alternative control — accepted** | dm-verity continuous block-level verification of a signed, reproducible root supersedes AIDE; immutable root + allow-listing + RAM-only overlay is a justified compensating posture for endpoint AV. The assessor accepts both N/A justifications. |
|
||||||
|
| Authentication (machine) | **Partially effective** | Design (EAP-TLS, non-exportable TPM key, cert in NV, no baked credentials) is strong and the same identity is demonstrated working for agent mTLS on hardware. But the 802.1X path itself is unverified and disabled in builds (R-01) and the RADIUS trust anchor is a placeholder (R-08) — the control is not currently operating. |
|
||||||
|
| Authentication (privileged users) | **Partially effective** | Key-only single admin, root locked, `staff_u` confinement with sudo role transition — good. No human MFA on host SSH; NOPASSWD sudo; fleet-generic non-rotatable key (R-05). Management-plane admin MFA (WebAuthn passkeys) is effective but sits in a connected system. |
|
||||||
|
| System management / patching | **Partially effective** | Reproducible, digest-pinned, atomic rebuild/redeploy model is excellent change control; signing keys correctly held outside repo/build. Gaps: patch cadence/SLA undefined (R-10); debug shell (R-02); actuator path deferred (R-15). |
|
||||||
|
| Event logging & monitoring | **Partially effective** | Centralised auditd offload verified end-to-end; Wazuh SIEM decode+alert verified; health telemetry adds operational visibility. Gaps: both log-offload hops plaintext, retention/archival unconfigured, Wazuh default credentials (R-07). |
|
||||||
|
| Networking | **Partially effective** | Port-based 802.1X design + segmentation + outbound-only agent are sound but the enforcement path is unverified (R-01); host firewall disabled (R-09). |
|
||||||
|
| Communications / link protection | **Not assessed → design sound** | MACsec implementation exists but has never been observed operating (MKA survival across switch-root specifically flagged by the system's own documentation as unverified). |
|
||||||
|
| Cryptography | **Partially effective** | Modern STIG crypto policy, TPM-held keys, HSM/CI signing keys. AACA/AACP conformance not asserted, FIPS off, NVMe/TCP root and log transport plaintext (R-04, R-06, R-07). Acceptable at STARFLEET ROUTINE on an isolated LAN; not above. |
|
||||||
|
| ICT equipment & media | **Effective** | Diskless/no-remanence posture is inherent and genuine; Secure Boot + signed UKI + dm-verity verified enforcing on hardware. Residual is default posture (R-13) and no remote attestation (R-11). |
|
||||||
|
| Supply chain / artifact integrity | **Effective** | FROM-scratch artifact, SHA256SUMS, digest pinning, reproducible builds, roothash hard gate in CI. |
|
||||||
|
|
||||||
|
## 4. Strengths worth the AO's attention
|
||||||
|
|
||||||
|
1. **Statelessness as a control.** No persistent local state, RAM-only overlay,
|
||||||
|
per-boot return to a verified root — this structurally eliminates whole ISM
|
||||||
|
control families (media sanitisation, data remanence, endpoint backup) and
|
||||||
|
sharply limits persistence for an attacker.
|
||||||
|
2. **Hardware-anchored identity, one key three uses.** Non-exportable TPM key
|
||||||
|
backing 802.1X, the NVMe hostnqn, and management-plane mTLS, with
|
||||||
|
human-anchored enrolment (OOB fingerprint + admin mTLS approval, no
|
||||||
|
auto-issue). See also observation F-06 on cross-protocol key reuse.
|
||||||
|
3. **Verification culture.** The false-clean STIG scan (datastream/CPE
|
||||||
|
mismatch evaluating 0 rules) and the inert fapolicyd overlay guard were both
|
||||||
|
found by the team's own effectiveness testing and fixed with build-time
|
||||||
|
guards. This is exactly the behaviour an assessor wants to see, and it
|
||||||
|
raises confidence in the other dated verification claims.
|
||||||
|
4. **Honest deviation management.** The STIG tailoring + per-build deviations
|
||||||
|
report with a triaged register (REVIEW rows driven to 0–1) is a model
|
||||||
|
pattern; waivers carry justifications rather than hiding findings.
|
||||||
|
5. **Recovery posture.** Rollback = revert one digest-pinned manifest commit;
|
||||||
|
compromised host recovery = reboot. Respond is structurally strong even
|
||||||
|
before an IRP exists.
|
||||||
|
|
||||||
|
## 5. Assessor findings
|
||||||
|
|
||||||
|
### 5.1 Concurrence with the SSP risk register
|
||||||
|
|
||||||
|
The assessor **concurs** with the ratings, compensating controls, and
|
||||||
|
treatments for R-01 through R-15 in Annex A §3, with these adjustments:
|
||||||
|
|
||||||
|
- **R-01 (secure path unverified) — concur High, emphasise centrality.** This
|
||||||
|
is the pivotal finding of the assessment. Until the swtpm + hostapd +
|
||||||
|
FreeRADIUS bench demonstrates port authorisation, MACsec establishment, MKA
|
||||||
|
survival across switch-root, and NVMe root mount over macsec0, the system's
|
||||||
|
primary network-preventive control set is unassessed. It must be *witnessed*,
|
||||||
|
not just recorded, at the follow-up assessment.
|
||||||
|
- **R-08 (placeholder RADIUS CA) — concur Medium, but couple to R-01.** These
|
||||||
|
close together or not at all: a verified secure path against a placeholder
|
||||||
|
trust anchor is not a verified control.
|
||||||
|
- **R-06 (AACA/AACP not asserted) — concur Medium at STARFLEET ROUTINE**, with
|
||||||
|
the explicit caveat that this becomes a **blocking finding if the
|
||||||
|
classification is confirmed STARFLEET SECRET or above** (see §2.4.2).
|
||||||
|
- **R-14 (management plane unauthorised connected system) — concur Medium.**
|
||||||
|
The host-side exposure limitation (outbound-only, telemetry-only, commands
|
||||||
|
inert) is genuine and was verified on hardware; however the host now
|
||||||
|
*trusts a new CA* (step-ca) whose key protection is unassessed. The AO
|
||||||
|
should treat the fleet CA as part of the system's effective trust boundary
|
||||||
|
even though it is administratively a connected system.
|
||||||
|
- **R-15 (deferred actuators) — concur Low, with a hard condition:** enabling
|
||||||
|
any actuator (reboot/drain/kiosk_url) without a delta assessment of the
|
||||||
|
command authorisation path would invalidate this SAR's characterisation of
|
||||||
|
the agent as read-only.
|
||||||
|
|
||||||
|
### 5.2 Additional assessor findings (not in the SSP register)
|
||||||
|
|
||||||
|
| ID | Finding | Severity | Detail / recommendation |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **F-01** | **Assessed build ≠ production configuration.** *Partly remediated 2026-07-11 (`poam-hardening`, POA&M P-03).* A fail-closed production profile now exists: the CI "Production build gate" aborts unless `SB_KEY`/`SB_CERT`/`FLEET_CA` are set (no unsigned fallback, no placeholder CA), `enable_stig=true` is enforced by Packer variable validation, and `debug_shell` is deleted from the template. **Two of the five toggles remain unpinned in the standing CI:** `disable_dot1x` is still `true` (blocked on the P-01 secure-path bench) and production artifacts are not yet distinctly tagged (P-03 M2). | **High** → **Medium** (residual) | Complete P-03: flip `disable_dot1x=false` after the P-01 bench and add distinct production-artifact tagging; make the follow-up assessment target *that* artifact digest. Individual toggles are tracked (R-01/02/03/08/13). |
|
||||||
|
| **F-02** | **SSH host keys regenerate every boot.** The ephemeral overlay means sshd host keys are new each boot, so administrators can never pin a host key — every connection is effectively trust-on-first-use, and a MITM of admin SSH on the LAN is undetectable by the client. *Remediation IMPLEMENTED 2026-07-11 (`poam-hardening`, POA&M P-17):* the **SSH host CA** treatment (Design A, broker-mediated) — `fleet-ssh-hostcert` gets each host's per-boot key signed by the fleet SSH CA **via the enrol-broker host plane** (TPM-identity mTLS) at boot; admins trust the fleet via one `@cert-authority` line. step-ca stays behind the single front door; fail-open (bare key if the broker/CA is unreachable). | **Medium** → **Low** (once bench-verified) | Bench-verify end-to-end (broker↔step-ca SSH-CA contract, host principal policy, SELinux for the root cert-installer) per docs/ssh-host-certificate-design.md; roll the `@cert-authority` anchor to admin workstations. A stable TPM-resident host key (Design B) is a deferred option, not required. |
|
||||||
|
| **F-03** | **Time source is unauthenticated.** chrony syncs solely from a DHCP-handed lab NTP server with no NTS or symmetric-key authentication. Certificate validity decisions (EAP-TLS, agent mTLS, future log TLS) depend on host time; an attacker on the pre-auth segment who can spoof DHCP/NTP could shift time to resurrect expired/revoked certificates. *Remediation IMPLEMENTED 2026-07-11 (`secure-ntp`, POA&M P-18):* **NTS (RFC 8915)** — the serving NTP server presents a **fleet-CA-signed** NTS-KE cert; the host (`secure_ntp=true`) verifies it against `radius-ca.pem` and syncs authenticated time (server name derived at runtime from the DHCP domain — no baked FQDN), and no longer mixes in the unauthenticated DHCP source. A DHCP/DNS spoofer can now only DoS, not shift the clock. | **Low–Medium** → **Low** (once bench-verified) | Bench-verify (`chronyc authdata` NTS cookies, tcp/4460, cold-clock `nocerttimecheck`, STIG chrony) per docs/secure-ntp-design.md; issue the per-environment NTS server cert. Compensating meanwhile: isolated LAN + cert-serial inventory gate. |
|
||||||
|
| **F-04** | **Enrolment broker pre-auth endpoint lacks abuse controls.** The one unauthenticated CSR endpoint is human-gated for *issuance* (good) but the SSP records no rate-limiting/queue-flooding controls; a flooded pending-approval queue is a plausible denial/enrolment-confusion vector. Noted inside R-14's treatment text; called out here so it is not lost in the connected-system SSP. | **Low (host) / Medium (plane)** | Carry explicitly into the management-plane SSP: rate limiting, pending-CSR TTL and queue caps, and alerting on anomalous CSR volume. |
|
||||||
|
| **F-05** | **Fleet-wide availability depends on a single NVMe/TCP target.** Every running host's root filesystem is served by one unreplicated SPDK process; its failure stalls or kills the entire fleet (the system's own analysis, TODO #6). For a fleet whose stated recovery model is "reboot", loss of the root target defeats that model. Availability of the root service is a security property of this system. | **Medium** | Add to the register (availability class). The drain-gated long-lived target design mitigates *operational* restarts; structural treatment is the already-identified multipath/ANA replica design plus documented RTO. Note `ctrl_loss_tmo` tuning as the cheap interim mitigation. |
|
||||||
|
| **F-06** | **Cross-protocol reuse of the TPM identity key.** The same key authenticates EAP-TLS, derives the NVMe hostnqn, and authenticates agent mTLS ("one identity, three stages"). This is an explicit, documented design choice with real benefits (one enrolment, one revocation point); the residual concern is cross-protocol signing-oracle exposure (a signature obtained in one protocol context being replayable in another) and a single revocation event disabling all three functions at once. | **Low (observation)** | No change required at STARFLEET ROUTINE. Document a short analysis in the SSP: confirm the protocols' signed structures are domain-separated (TLS 1.2 vs 1.3 CertificateVerify context strings largely provide this), and document the operational plan for the revocation blast radius. |
|
||||||
|
| **F-07** | **No continuous assurance that runtime posture matches assessed posture.** SELinux-permissive and version-drift are surfaced via fleet-agent telemetry (good), but nothing yet alerts on: Secure Boot state, debug-shell presence, or fapolicyd non-enforcing. The system has the telemetry channel to make assessed-posture drift visible fleet-wide. | **Low** | Extend agent facts to include SB state (`SecureBoot` EFI var), fapolicyd enforcement, and cmdline hash; alert on drift in the manager. Complements R-13. |
|
||||||
|
| **F-08** | **DNS/DHCP are untrusted inputs by design — but the pattern is undocumented and two flows escape it.** The architecture consistently authenticates endpoints *above* name resolution (manager → mTLS; NTP → NTS, post-P-18; UKI → Secure Boot signature; NVMe root → dm-verity), so DNSSEC/DoT would add little and is *not* recommended. However: (a) the **kiosk URL is plain HTTP** — a DNS or DHCP (option-15 domain) spoofer serves an arbitrary page fullscreen on every workstation's only user interface, with the Chromium policy's PNA allowance to the loopback host-info agent as reachable attack surface; (b) a spoofer can serve an **older, still-validly-signed UKI** (rollback) — Secure Boot does not prevent this; the design's anti-rollback mechanisms (dbx revocation / TPM monotonic counter, design v2 §6) are unimplemented and unmentioned in the register; (c) the plaintext **audit offload** is DNS-spoofable today — closed as a side effect of P-09's TLS, but that coverage was untraceable until now. | **Medium** (driver: kiosk content flow) | POA&M P-24. State the "DNS/DHCP untrusted; authenticate above resolution" principle explicitly in the SSP; serve the kiosk over TLS with a fleet-CA-signed cert (CA pinned via Chromium policy) rather than deploying DNSSEC; record the UKI-rollback residual against R-13 with the design-§6 anti-rollback treatment; cross-reference P-09 for the audit-stream closure. |
|
||||||
|
|
||||||
|
## 6. Essential Eight maturity (assessor view)
|
||||||
|
|
||||||
|
The SSP's self-assessment (§6) is broadly fair. Assessor adjustments:
|
||||||
|
application control is genuinely **strong post-2026-07-04** (and, notably, was
|
||||||
|
*over-claimed before* that fix — the honest correction is credited);
|
||||||
|
"restrict admin privileges" and "MFA" should both be read as **partial** until
|
||||||
|
the host SSH access model gains rotation and the management plane is
|
||||||
|
authorised; "patching" remains **process-only** until R-10 defines an SLA.
|
||||||
|
Against Essential Eight maturity levels, the system sits at approximately
|
||||||
|
**ML1–ML2 overall** with individual mitigations (application control, user
|
||||||
|
application hardening) performing at ML3-like strength — but E8 maturity
|
||||||
|
formally requires the *whole* mitigation strategy set at level, so no formal
|
||||||
|
level claim above ML1 should be made yet.
|
||||||
|
|
||||||
|
## 7. Recommendation to the Authorising Officer
|
||||||
|
|
||||||
|
### 7.1 Determination
|
||||||
|
|
||||||
|
- **Production ATO: not recommended at this time.**
|
||||||
|
- **Interim authorisation (lab/pre-production, isolated LAN, no production
|
||||||
|
data): recommended**, valid for a defined period (suggest ≤ 6 months),
|
||||||
|
conditional on the POA&M being maintained and on no production or
|
||||||
|
classified data being processed.
|
||||||
|
|
||||||
|
### 7.2 Conditions precedent for a production ATO
|
||||||
|
|
||||||
|
The following must be closed, then verified at a **delta assessment** at which
|
||||||
|
the assessor witnesses (not merely reviews) the evidence:
|
||||||
|
|
||||||
|
1. **Witness the secure-path bench test** (R-01 + R-08): real RADIUS CA; port
|
||||||
|
`Authorized`; macsec0 addressed; MKA survives switch-root; NVMe root
|
||||||
|
mounted over macsec0; lease renewal on macsec0 past expiry.
|
||||||
|
2. **Production build profile** (F-01): a signed, SB-enforcing, STIG-on,
|
||||||
|
debug-shell-off, dot1x-on build produced by CI with hard assertions;
|
||||||
|
assessor verifies `sbverify` on UKI + iPXE, `mokutil --sb-state` on sample
|
||||||
|
hosts, absence of the debug shell, and a valid STIG deviations report with
|
||||||
|
0 unjustified REVIEW rows on the assessed digest (R-02, R-03, R-13).
|
||||||
|
3. **Governance**: AO/System Owner/CISO appointed; data classification
|
||||||
|
confirmed by the System Owner; IRP, continuous-monitoring plan and
|
||||||
|
log-retention policy authored and approved (R-12, §2.1 gaps).
|
||||||
|
4. **Log transport and retention** (R-07): TLS on both offload hops; retention
|
||||||
|
configured; Wazuh credentials rotated and dashboard/API restricted.
|
||||||
|
5. **ISM reconciliation** (P-17): Annex A identifiers reconciled to the May
|
||||||
|
2026 ISM, unmapped new controls dispositioned.
|
||||||
|
6. **Management plane**: either a granted authorisation for the connected
|
||||||
|
system (R-14) or an AO-accepted interim risk position covering the fleet
|
||||||
|
CA's key protection specifically.
|
||||||
|
7. **Witness re-verifications**: reproducible build (two builds, identical
|
||||||
|
verity root hash — including post-`semanage` policy-store stability);
|
||||||
|
fapolicyd denial of a novel binary on the overlay root; USBGuard
|
||||||
|
mass-storage block with console HID intact; agent actuators inert (R-15).
|
||||||
|
|
||||||
|
Items R-04, R-05, R-09, R-10, R-11, F-02, F-03, F-05, F-08 may, at the AO's
|
||||||
|
discretion, be **accepted as residual risk with POA&M target dates** for a
|
||||||
|
production ATO at STARFLEET ROUTINE on an isolated/managed network, given the
|
||||||
|
compensating controls recorded in Annex A. They are not recommended for
|
||||||
|
indefinite acceptance.
|
||||||
|
|
||||||
|
### 7.3 Revocation triggers the AO should set
|
||||||
|
|
||||||
|
- Enabling fleet-agent actuators without a delta assessment (R-15 / §5.1).
|
||||||
|
- Processing data above the confirmed classification.
|
||||||
|
- Any unsigned or SB-off build deployed outside the lab (R-13 / F-01).
|
||||||
|
- Lapse of the POA&M review cadence (suggest quarterly).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*End of SAR. The consolidated remediation plan with owners, milestones and
|
||||||
|
target dates is at [irap-poam.md](irap-poam.md).*
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
# Secure NTP design — CA-authenticated time via NTS (F-03 / P-18)
|
||||||
|
|
||||||
|
> Status: **NTS proven end-to-end on the metal (2026-07-11, comet-beacon-54:
|
||||||
|
> `chronyc authdata` → NTS, Cook 8), pending a clean-build re-verify of the
|
||||||
|
> dispatcher-collision fix.** Branch `secure-ntp`. Closes SAR finding **F-03** /
|
||||||
|
> POA&M **P-18**. See "Bench findings" below.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The host clock is an input to **every** certificate-validity decision (EAP-TLS to
|
||||||
|
RADIUS, agent mTLS, the SSH host-cert flow, future log TLS). Today chrony syncs
|
||||||
|
from a **DHCP-handed** NTP server (option 42) with **no authentication**. An
|
||||||
|
attacker on the pre-auth segment who can spoof DHCP or NTP can shift the clock —
|
||||||
|
e.g. forward it to make an **expired/revoked** certificate look valid. The
|
||||||
|
isolated LAN and the cert-**serial** inventory gate are only partial compensations.
|
||||||
|
|
||||||
|
## Approach — NTS (RFC 8915), CA-signed server authentication
|
||||||
|
|
||||||
|
**NTS** is the modern, CA-based way to authenticate an NTP server. The server runs
|
||||||
|
an **NTS-KE** (key-establishment) endpoint over **TLS 1.3** on tcp/4460; the client
|
||||||
|
verifies the server's TLS certificate against a trust anchor, then uses the
|
||||||
|
negotiated keys to authenticate the NTP (udp/123) packets that carry the time.
|
||||||
|
|
||||||
|
This maps onto the fleet's existing PKI: the NTS-KE cert is issued by the **fleet
|
||||||
|
step-ca**, so it chains to the **same `radius-ca.pem`** the hosts already trust.
|
||||||
|
No new trust anchor.
|
||||||
|
|
||||||
|
Symmetric-key NTP and Autokey were rejected: neither is CA-based (symmetric needs
|
||||||
|
per-server shared secrets; Autokey is deprecated and broken).
|
||||||
|
|
||||||
|
## No baked FQDN — the server name is derived from the DHCP domain
|
||||||
|
|
||||||
|
NTS validates the server's TLS cert against the *name/address the client used*. We
|
||||||
|
deliberately **do not bake an NTP FQDN** into the image (a site's domain may change
|
||||||
|
and we don't want to rebuild images for that). Instead, at runtime the NM
|
||||||
|
dispatcher takes the **DHCP-provided domain** (`$DHCP4_DOMAIN_NAME`, option 15;
|
||||||
|
falls back to the first `$DHCP4_DOMAIN_SEARCH`, option 119) and forms:
|
||||||
|
|
||||||
|
```
|
||||||
|
server <ntp_prefix>.<dhcp-domain> iburst nts # e.g. ntp.fleet.lab
|
||||||
|
```
|
||||||
|
|
||||||
|
Only the **label** `ntp_prefix` (default `ntp`) and the **fleet CA** are baked —
|
||||||
|
both stable. Change the site domain? Hand a new DHCP domain and reissue the NTS
|
||||||
|
cert with the new SAN; **no image rebuild**. The cert uses a clean **DNS SAN**
|
||||||
|
(not an IP SAN, which deriving from DHCP option-42 IPs would have forced).
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
serving 'ntp' (chrony):
|
||||||
|
pool pool.ntp.org iburst # upstream (serving host only)
|
||||||
|
allow all # serve the boot LAN
|
||||||
|
ntsservercert /etc/chrony/nts/nts.crt # FLEET-signed, SAN = ntp.<domain>
|
||||||
|
ntsserverkey /etc/chrony/nts/nts.key
|
||||||
|
▲ tcp/4460 NTS-KE (TLS 1.3) + udp/123 authenticated NTP
|
||||||
|
│
|
||||||
|
dnsmasq: option 15 domain-name = <domain>; A ntp.<domain> -> serving host
|
||||||
|
│
|
||||||
|
host (secure_ntp=true):
|
||||||
|
NM dispatcher (10-chrony-dhcp-ntp, mode=nts):
|
||||||
|
domain=$DHCP4_DOMAIN_NAME -> /run/chrony-dhcp/<if>.sources:
|
||||||
|
server ntp.<domain> iburst nts
|
||||||
|
chrony.conf:
|
||||||
|
sourcedir /run/chrony-dhcp
|
||||||
|
ntstrustedcerts /etc/dot1x/radius-ca.pem # the fleet CA verifies the server
|
||||||
|
nocerttimecheck 1 # bootstrap a cold boot clock once
|
||||||
|
```
|
||||||
|
|
||||||
|
**Components**
|
||||||
|
|
||||||
|
| Piece | Where | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| mode + prefix, chrony NTS directives | `diskless.pkr.hcl` (chrony provisioner) | `secure_ntp` (default true), `ntp_prefix` (default `ntp`); bakes `ntstrustedcerts`/`nocerttimecheck` + `sourcedir` |
|
||||||
|
| runtime name derivation | `files/etc/NetworkManager/dispatcher.d/10-chrony-dhcp-ntp` | mode-aware: `nts` derives `ntp.<dhcp-domain>`; `dhcp` = legacy option-42 IPs |
|
||||||
|
| NTS server directives | `serving/chrony/chrony.conf` | `ntsservercert`/`ntsserverkey`/`ntsdumpdir` |
|
||||||
|
| NTS server cert issuance | `serving/chrony/nts-cert.sh` | `step ca certificate` (DNS SAN = `NTP_HOST` = `ntp.<domain>`) |
|
||||||
|
| compose mount + firewall + domain handout | `serving/docker-compose.yml` | mounts `./chrony/nts`; udp/123 **and tcp/4460**; DHCP option 15 |
|
||||||
|
| DNS resolution | dnsmasq `--address=/${NTP_HOST}/` + `NTP_HOST` in `defaults.env` | `ntp.fleet.lab` → serving host |
|
||||||
|
|
||||||
|
## Why `nocerttimecheck` — the chicken-and-egg
|
||||||
|
|
||||||
|
NTS-KE is TLS, so the client checks the server cert's **validity window** — which
|
||||||
|
needs a roughly-correct clock. A diskless host has no reliable RTC, so its boot
|
||||||
|
clock can be far enough off (or epoch) to make a *valid* cert look
|
||||||
|
not-yet-valid/expired, and NTS would never bootstrap. `nocerttimecheck 1` lets the
|
||||||
|
**first** measurement set the clock without the cert-time check; **every
|
||||||
|
subsequent** update enforces it. The trade-off (one measurement could accept an
|
||||||
|
expired cert) is bounded by the isolated LAN and the single-measurement window,
|
||||||
|
and is the standard chrony guidance for RTC-less devices.
|
||||||
|
|
||||||
|
## Security properties
|
||||||
|
|
||||||
|
- **Authenticated time:** a DHCP/NTP/**DNS** spoofer can only cause **no-sync
|
||||||
|
(DoS)** — it cannot present a cert that chains to the fleet CA for the name it
|
||||||
|
redirects you to, and NTS never falls back to unauthenticated time. So the clock
|
||||||
|
cannot be shifted (the whole point of F-03). This holds *because of NTS*, even
|
||||||
|
though DHCP/DNS themselves are unauthenticated.
|
||||||
|
- **No baked FQDN, no per-host state (invariant #1):** only the `ntp` label + the
|
||||||
|
fleet CA (already baked) are fixed; the domain is learned from DHCP at runtime.
|
||||||
|
- **No unauthenticated source mixed in:** in `nts` mode the dispatcher writes only
|
||||||
|
the NTS source; the option-42 IP handout is ignored.
|
||||||
|
- **Availability:** `local stratum 10` on the server keeps time serving if upstream
|
||||||
|
is down; a host that can't reach NTS-KE simply holds its clock (no bad time).
|
||||||
|
|
||||||
|
## Bench checklist (BENCH-VERIFY)
|
||||||
|
|
||||||
|
1. Serving: run `serving/chrony/nts-cert.sh` (after the CA is up); confirm
|
||||||
|
`serving/chrony/nts/{nts.crt,nts.key}` exist with **DNS SAN = `ntp.<domain>`**;
|
||||||
|
restart the `ntp` service; open **tcp/4460** + udp/123 on the boot NIC.
|
||||||
|
2. Domain handout: on a booted host confirm the DHCP domain arrived —
|
||||||
|
`nmcli -g DHCP4.OPTION.* dev show <if> | grep domain` or the dispatcher wrote
|
||||||
|
`/run/chrony-dhcp/<if>.sources` with `server ntp.<domain> iburst nts`.
|
||||||
|
3. Host: `chronyc -N authdata` shows the source in `NTS` mode with cookies > 0;
|
||||||
|
`chronyc sources` shows `ntp.<domain>` reachable and selected.
|
||||||
|
4. Spoof test: a rogue NTP/DHCP/DNS source is **not** trusted (NTS handshake fails
|
||||||
|
⇒ not selected) — DoS at worst, never a clock shift.
|
||||||
|
5. Cold-clock test: boot with the RTC set far off; confirm `nocerttimecheck 1`
|
||||||
|
lets the first sync land, then the clock tracks.
|
||||||
|
6. Domain-change test: change the DHCP domain + reissue the NTS cert with the new
|
||||||
|
SAN; confirm the SAME image picks up `ntp.<new-domain>` with no rebuild.
|
||||||
|
7. STIG: confirm remediation doesn't strip `ntstrustedcerts`/`nocerttimecheck` or
|
||||||
|
the `nts` option in the sourcedir (host is a client; STIG-compatible).
|
||||||
|
|
||||||
|
## Bench findings
|
||||||
|
|
||||||
|
**2026-07-11 (comet-beacon-54, `secure-ntp-d1621e9`) — the chrony package's own NM
|
||||||
|
dispatcher clobbered the NTS source (FIXED).** On the first clean-build deploy NTS
|
||||||
|
was inactive: `chronyc -N authdata` showed the source as a plain IP (`192.168.50.10`)
|
||||||
|
in Mode `-`, Cook 0, and the journal showed **no NTS-KE attempt at all** — chrony was
|
||||||
|
on plain NTP. Root cause: NetworkManager runs dispatcher scripts from **both**
|
||||||
|
`/etc/NetworkManager/dispatcher.d/` and `/usr/lib/NetworkManager/dispatcher.d/`,
|
||||||
|
merged and sorted by filename. The `chrony` RPM ships
|
||||||
|
`/usr/lib/NetworkManager/dispatcher.d/20-chrony-dhcp`, which writes the DHCP
|
||||||
|
option-42 server to the **same** `/run/chrony-dhcp/<iface>.sources` file our
|
||||||
|
`10-chrony-dhcp-ntp` uses. Because `20-` sorts after `10-`, it ran last and
|
||||||
|
**overwrote our `server ntp.fleet.lab iburst nts` line with `server 192.168.50.10
|
||||||
|
iburst`**, then reloaded chrony — silently defeating authenticated time (last writer
|
||||||
|
wins on a shared filename).
|
||||||
|
|
||||||
|
Proof + isolation: removing the distro script and hand-writing the NTS source
|
||||||
|
(`printf 'server ntp.fleet.lab iburst nts\n' > …`) + `chronyc reload sources`
|
||||||
|
immediately brought NTS up — `ntp.fleet.lab NTS … Cook 8`, zero NAKs — confirming
|
||||||
|
the host image, the fleet-signed NTS-KE cert, DNS (`ntp.fleet.lab`→serving host),
|
||||||
|
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).
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
`secure_ntp=false` reverts the dispatcher to the legacy unauthenticated DHCP
|
||||||
|
option-42 path (plain `server <ip> iburst`) for lab/interop. Keep it **true** for
|
||||||
|
production.
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
# SSH host-certificate design — killing per-boot TOFU (F-02 / P-17)
|
||||||
|
|
||||||
|
> Status: **BENCH-VERIFIED end-to-end 2026-07-11 on clean build `28d31e4`** (`comet-beacon-54`, real TPM):
|
||||||
|
> TPM-mTLS → broker → step-ca signing → cert install → admin login **by FQDN with
|
||||||
|
> no TOFU**, and per-boot auto-re-sign across a reboot. Closes SAR finding **F-02**
|
||||||
|
> / POA&M **P-17**. Signing is **broker-mediated** (host → enrol-broker host plane →
|
||||||
|
> step-ca), not host-direct. The defects the bench surfaced are fixed on branch
|
||||||
|
> `poam-hardening` (commit `28d31e4`) — see **Bench outcome (2026-07-11)** below.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The diskless root is an immutable erofs + a **RAM overlay**, so
|
||||||
|
`sshd-keygen@.service` regenerates the SSH **host keys on every boot** (see
|
||||||
|
`scripts/extract-artifacts.sh`, which strips baked host keys — a baked key would
|
||||||
|
be shared fleet-wide, invariant #1). Consequences:
|
||||||
|
|
||||||
|
- Administrators can never pin a host key — every `ssh` to a fleet host is
|
||||||
|
**trust-on-first-use**, and the key *changes each reboot* so even a cached entry
|
||||||
|
triggers the "REMOTE HOST IDENTIFICATION HAS CHANGED" warning.
|
||||||
|
- A **LAN man-in-the-middle** of admin SSH is undetectable client-side.
|
||||||
|
|
||||||
|
## Approaches considered
|
||||||
|
|
||||||
|
- **A — per-boot key + boot-time CA-signed host cert (CHOSEN).** Keep the
|
||||||
|
ephemeral per-boot host key; at boot, get it **signed by the fleet SSH CA** over
|
||||||
|
a TPM-authenticated request. Clients trust via one `@cert-authority` line. The
|
||||||
|
TPM only *authenticates the signing request*; it is not the host key.
|
||||||
|
- **B — dedicated stable TPM host key + `HostKeyAgent` + cert at enrolment.**
|
||||||
|
Hardware-anchored, CA-independent, stable host identity — but needs a new
|
||||||
|
persistent TPM handle, a host-key agent daemon (SELinux/fapolicyd/ordering), and
|
||||||
|
more provisioning. Deferred; a follow-on if a CA-independent hardware host key is
|
||||||
|
specifically wanted.
|
||||||
|
|
||||||
|
**The certificate — not the key's origin — is what removes TOFU.** A per-boot key
|
||||||
|
*with a cert* is fine (short-lived keys are good hygiene), so A delivers the
|
||||||
|
security outcome with the least new attack surface and no added cross-protocol key
|
||||||
|
reuse. See the chat rationale; A was chosen 2026-07-11.
|
||||||
|
|
||||||
|
## Architecture (Design A, broker-mediated)
|
||||||
|
|
||||||
|
The signing request goes **through the enrol-broker**, not directly to step-ca —
|
||||||
|
so step-ca stays behind the single front door (consistent with enrolment), and the
|
||||||
|
proven `step` CLI does the SSH signing server-side. The host authenticates to the
|
||||||
|
broker's new **host plane** with its TPM identity over mTLS.
|
||||||
|
|
||||||
|
```
|
||||||
|
boot ─▶ sshd-keygen@ecdsa ─▶ /etc/ssh/ssh_host_ecdsa_key(.pub) (ephemeral)
|
||||||
|
│
|
||||||
|
ssh-hostcert.timer (OnBootSec + hourly)
|
||||||
|
└─▶ fleet-ssh-hostcert:
|
||||||
|
1. TPM: LoadSigner(0x81000100) + ReadNVCert(0x01800100) → mTLS client cert
|
||||||
|
2. read ssh_host_ecdsa_key.pub
|
||||||
|
3. POST {publicKey, principals, keyID} ─(mTLS)─▶ enrol-broker host plane
|
||||||
|
4. write ssh_host_ecdsa_key-cert.pub + 60-hostcert.conf; reload sshd
|
||||||
|
|
||||||
|
enrol-broker host plane (POST /ssh/hostcert):
|
||||||
|
verify client cert to the fleet CA (⇒ enrolled host)
|
||||||
|
└─▶ step ssh certificate --host --sign (broker holds the provisioner) ─▶ step-ca SSH CA
|
||||||
|
└─▶ returns {cert}
|
||||||
|
|
||||||
|
client: @cert-authority *.fleet.lab <ssh_host_ca.pub> (one line, once)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Components**
|
||||||
|
|
||||||
|
| Piece | Where | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `fleet-ssh-hostcert` | `files/ssh-hostcert/` (Go, static, go-tpm) | TPM mTLS client; out-of-VM CI build like fleet-enrol/agent; deps pinned by go.sum |
|
||||||
|
| `ssh-hostcert.service` + `.timer` | `files/ssh-hostcert/` | **root** oneshot (writes `/etc/ssh`, reloads sshd), sandboxed; timer = boot + hourly renew |
|
||||||
|
| `ssh-hostcert.env` | `files/ssh-hostcert/` → `/etc/ssh-hostcert/` | fleet-generic broker URL / CA / paths |
|
||||||
|
| Packer `2b-ssh-hostcert` | `diskless.pkr.hcl` | gated on `enable_ssh_hostcert`; fapolicyd-trusts the binary in 2c-STIG-postfix |
|
||||||
|
| broker **host plane** (`/ssh/hostcert`) | `serving/enrol-broker/host.go` + `sshsigner.go` | mTLS (fleet-CA host certs); `step ssh certificate` signer; enabled by `BROKER_HOST_ADDR` |
|
||||||
|
| step-ca SSH CA enablement | `serving/dot1x-bench/step-ca/ssh-ca-bootstrap.sh` | SSH CA keys + provisioner `--ssh` + broker host-plane cert; prints the admin `@cert-authority` line |
|
||||||
|
|
||||||
|
## Wire contract (the BENCH-VERIFY seam)
|
||||||
|
|
||||||
|
- **Host → broker** (mTLS): `POST /ssh/hostcert` `{publicKey, principals, keyID}`
|
||||||
|
→ `{cert}`. The host's mTLS client cert is its TPM identity; the broker verifies
|
||||||
|
it to the fleet CA (`RequireAndVerifyClientCert`) — *that* is the authorisation
|
||||||
|
(an enrolled host). No token, no CA secret on the host.
|
||||||
|
- **Broker → step-ca**: `step ssh certificate --host --sign …` with the broker's
|
||||||
|
provisioner credential. The exact flags are the tunable part
|
||||||
|
(`serving/enrol-broker/sshsigner.go`); the CA must have its **SSH host CA
|
||||||
|
enabled** and the provisioner allowed to issue SSH host certs
|
||||||
|
(`ssh-ca-bootstrap.sh`).
|
||||||
|
|
||||||
|
**Principals — name alignment (bench 2026-07-11).** Admins connect by **FQDN**
|
||||||
|
(`host.<domain>`), but the host defaulted its principal to the **short** hostname,
|
||||||
|
so `ssh host.fleet.lab` failed with *"name is not a listed principal"* even though
|
||||||
|
the cert was CA-valid. Fixed host-side: `fleet-ssh-hostcert` now also derives
|
||||||
|
`<hostname>.<domain>` from the DHCP search domain (`/etc/resolv.conf`) at runtime —
|
||||||
|
never baked (invariant #1). step-ca's **default** SSH host template already passes
|
||||||
|
requested principals through, so **no custom template is needed** (verified).
|
||||||
|
**Still a sharp edge (defence-in-depth):** the broker forwards whatever principals
|
||||||
|
are requested; tightening `handleSSHHostCert` to validate them against the peer
|
||||||
|
cert (so a host can only obtain its own names) remains a TODO.
|
||||||
|
|
||||||
|
## Security properties
|
||||||
|
|
||||||
|
- **Invariant #1 (no per-host state):** the image carries only a fleet-generic
|
||||||
|
broker DNS name + CA path. The host identity, and therefore the cert, derive from
|
||||||
|
the TPM at runtime. Principals are the runtime hostname **and** its
|
||||||
|
`<hostname>.<domain>` FQDN derived from the DHCP search domain — both learned at
|
||||||
|
runtime, never baked.
|
||||||
|
- **step-ca stays behind the front door:** hosts reach the broker, not step-ca. The
|
||||||
|
broker holds the provisioner credential; a host can never self-sign.
|
||||||
|
- **Fail-open:** never installs a missing/broken `HostCertificate` — a broker/CA
|
||||||
|
outage degrades to today's bare-key behaviour, not a boot/sshd failure.
|
||||||
|
- **Key reuse (cf. F-06):** the TPM identity authenticates the mTLS request; the
|
||||||
|
SSH host key is a separate ephemeral key.
|
||||||
|
- **Short-lived certs** + hourly renewal bound the blast radius of a leaked cert
|
||||||
|
(the key is ephemeral anyway).
|
||||||
|
|
||||||
|
## Bench outcome (2026-07-11)
|
||||||
|
|
||||||
|
Proven end-to-end on `comet-beacon-54` (poam-hardening image, real TPM): the full
|
||||||
|
chain — per-boot host key → TPM-mTLS to the broker → step-ca SSH signing → cert +
|
||||||
|
`60-hostcert.conf` install → sshd reload — works; an admin with one
|
||||||
|
`@cert-authority *.fleet.lab <ca>` line logs in **by FQDN with no TOFU prompt**;
|
||||||
|
and after a reboot `ssh-hostcert.timer` re-signs the fresh per-boot key
|
||||||
|
automatically. **SELinux confinement is clean** (no AVCs writing `/etc/ssh` +
|
||||||
|
reloading sshd → **no local TE module needed**), and STIG crypto policy accepts the
|
||||||
|
`ecdsa-sha2-nistp256-cert-v01` host cert.
|
||||||
|
|
||||||
|
**Re-verified on the rebuilt `28d31e4` image (2026-07-11), not hand-patched:** the
|
||||||
|
auto-derived FQDN principal (`<host>.<domain>`) is baked, so admin login by FQDN
|
||||||
|
with no TOFU **survives a reboot with zero manual steps** — the exact case the
|
||||||
|
hand-fixed host regressed on (its manual `SSH_HOSTCERT_PRINCIPALS` edit lived on the
|
||||||
|
RAM overlay and evaporated). F-02/P-17 is closed on a real build, not just a
|
||||||
|
hand-fixed host.
|
||||||
|
|
||||||
|
Getting there surfaced defects, all fixed on `poam-hardening` (`28d31e4`):
|
||||||
|
|
||||||
|
| # | Defect (as seen on the bench) | Fix |
|
||||||
|
|---|---|---|
|
||||||
|
| 3 | Broker dialed step-ca by the fleet dnsmasq name `ca.fleet.lab`, unresolvable from its host netns → `no such host` at sign time (502) | `STEP_CA_URL=https://localhost:8443` (co-located; `localhost` is in the CA cert SAN) |
|
||||||
|
| 4 | `ssh-ca-bootstrap.sh` **swallowed** a failed provisioner SSH-enable → the CA 502s every sign | Fail loudly + verify `enableSSHCA=true` |
|
||||||
|
| 6 | `ssh_host_ca.pub` exported in PEM/PKCS8 → the `@cert-authority` line was unparseable → silent TOFU fallback | Export in OpenSSH wire format (`ssh-keygen -i -m PKCS8`) |
|
||||||
|
| 7 | Cert principal was the **short** hostname; admins connect by FQDN → *"name is not a listed principal"* | Derive `<hostname>.<domain>` from the DHCP domain at runtime |
|
||||||
|
| 1 | A second, mock-signer broker in `serving/` collided on 9090/9443 and issued untrusted certs | Removed; the real broker (dot1x-bench, co-located with step-ca) is the only one |
|
||||||
|
| 2 | Broker HTML admin console = a weaker, client-cert-only parallel human-approve path | Retired; the fleet-manager passkey SPA drives the mTLS JSON API |
|
||||||
|
|
||||||
|
**Retracted finding:** an early read suggested the cert had *empty* principals
|
||||||
|
(valid for any host). That was a `grep` artifact hiding `ssh-keygen -L`'s indented
|
||||||
|
principal lines — the step-ca sign log confirmed the requested principal was
|
||||||
|
present. No over-scoping; #7 is the real (name-alignment) issue.
|
||||||
|
|
||||||
|
**Operational note:** there is a brief post-boot window where sshd serves the bare
|
||||||
|
per-boot key before `ssh-hostcert.timer` (OnBootSec) fires; an admin connecting in
|
||||||
|
that window still sees TOFU. Tune `OnBootSec`/ordering to shrink it. (A client-side
|
||||||
|
PQ-KEX advisory also appears on connect — an OpenSSH warning, not an F-02 issue.)
|
||||||
|
|
||||||
|
**Deploy caveat:** #7 is baked into the `fleet-ssh-hostcert` binary, so it only
|
||||||
|
takes effect on a **new image build** from poam-hardening — until then the reboot
|
||||||
|
no-TOFU test regresses (a short-name-only cert can't match an FQDN login). The
|
||||||
|
serving-side fixes (#1–#4, #6) apply on `docker compose up -d --build` + a re-run of
|
||||||
|
`ssh-ca-bootstrap.sh`.
|
||||||
|
|
||||||
|
## Client rollout
|
||||||
|
|
||||||
|
One line per admin (or a system `known_hosts`), from `ssh-ca-bootstrap.sh` output
|
||||||
|
(now emitted in OpenSSH wire format — see fix #6 above):
|
||||||
|
|
||||||
|
```
|
||||||
|
@cert-authority *.fleet.lab ecdsa-sha2-nistp256 AAAA... # fleet SSH host CA
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bench checklist
|
||||||
|
|
||||||
|
Items 1–6 were **verified 2026-07-11** (see Bench outcome); item 7 (broker-side
|
||||||
|
principal validation) is the remaining defence-in-depth TODO.
|
||||||
|
|
||||||
|
1. Serving: run `ssh-ca-bootstrap.sh`; confirm `ca.json` has the `ssh` block, the
|
||||||
|
`enrol` provisioner is SSH-enabled, and `pki/broker/{host-server.crt,
|
||||||
|
fleet-client-ca.crt}` + `pki/ssh_host_ca.pub` exist. Bring up the broker with
|
||||||
|
`BROKER_HOST_ADDR` set; `journalctl` shows "host plane up".
|
||||||
|
2. Broker: from an enrolled host's identity, `curl --cert/--key … https://
|
||||||
|
boot.fleet.lab:9444/ssh/hostcert` returns a `{cert}`; an un-enrolled client is
|
||||||
|
rejected at the TLS handshake (`RequireAndVerifyClientCert`).
|
||||||
|
3. Host: `systemctl start ssh-hostcert.service`; `journalctl -u ssh-hostcert`
|
||||||
|
shows "installed SSH host certificate"; `/etc/ssh/ssh_host_ecdsa_key-cert.pub`
|
||||||
|
present; `sshd -T | grep hostcertificate`.
|
||||||
|
4. SELinux: watch for AVCs on the root service writing `/etc/ssh/*` and running
|
||||||
|
`systemctl reload sshd` under confinement — add a local TE module if needed
|
||||||
|
(like `fleet_agent_chronylog`). Reproducibility: a new `semodule` would rebuild
|
||||||
|
the policy store → re-check verity-hash stability across two builds.
|
||||||
|
5. Client: with the `@cert-authority` line set, `ssh <host>` shows **no** host-key
|
||||||
|
prompt/warning across reboots.
|
||||||
|
6. Confirm STIG crypto policy accepts the `ecdsa-sha2-nistp256-cert-v01` host cert
|
||||||
|
(same family already accepted for admin keys).
|
||||||
|
7. Principal policy: confirm the broker/step-ca only issue the principals a host is
|
||||||
|
entitled to (tighten `handleSSHHostCert` to validate against the peer cert).
|
||||||
|
|
||||||
|
## Deferred (Design B follow-on)
|
||||||
|
|
||||||
|
Stable, CA-independent hardware host key: a dedicated TPM key at a new persistent
|
||||||
|
handle (e.g. `0x81000101`, avoiding OEM/reserved NV like the identity key) served
|
||||||
|
to sshd via a small TPM `HostKeyAgent`, cert issued at enrolment. Only pursue if a
|
||||||
|
host identity that survives a CA outage is required.
|
||||||
@@ -9,7 +9,12 @@
|
|||||||
> **A&A framework:** ASD *Information Security Manual* (ISM) — engagement target
|
> **A&A framework:** ASD *Information Security Manual* (ISM) — engagement target
|
||||||
> **May 2026 release**.
|
> **May 2026 release**.
|
||||||
>
|
>
|
||||||
> **Annex version:** 0.3 (updated 2026-07-10). v0.3 adds the **fleet-management
|
> **Annex version:** 0.4 (updated 2026-07-11). v0.4 adds two host-identity/time
|
||||||
|
> hardening features (both implemented 2026-07-11; SSH host certs **bench-verified 2026-07-11**, NTS **bench-verified 2026-07-12**):
|
||||||
|
> broker-mediated **SSH host certificates** (per-boot host key signed by the fleet
|
||||||
|
> SSH CA via the enrol-broker host plane; closes F-02/P-17 — new **R-16**) and
|
||||||
|
> **NTS authenticated time** (RFC 8915 CA-signed NTP; closes F-03/P-18 — new
|
||||||
|
> **R-17**) — see §2.4, §2.9 and the register. v0.3 added the **fleet-management
|
||||||
> plane**: the in-image **`fleet-agent`** (health telemetry over TPM-mTLS —
|
> plane**: the in-image **`fleet-agent`** (health telemetry over TPM-mTLS —
|
||||||
> bench-verified on hardware 2026-07-10; new host interface (g)), the server-side
|
> bench-verified on hardware 2026-07-10; new host interface (g)), the server-side
|
||||||
> manager with **WebAuthn-passkey admin MFA** (first human-admin MFA — updates the
|
> manager with **WebAuthn-passkey admin MFA** (first human-admin MFA — updates the
|
||||||
@@ -110,6 +115,7 @@ ISM/PSPF style). Ratings are **pre-treatment** unless stated; the AO accepts the
|
|||||||
| Strong machine authentication | ISM-1546*, ISM-0974* | Mutual, certificate-based auth. | **802.1X EAP-TLS**, client key non-exportable in **TPM** (`0x81000100`), cert in TPM NV. The **management-plane agent reuses the same TPM identity** for mTLS (`RequireAndVerifyClientCert` to the fleet CA; server keys inventory off the verified cert serial). One non-exportable key = 802.1X + NVMe hostnqn + agent stream. | Partial | R-01 (not bench-verified; disabled in test builds), R-08 (placeholder CA), R-14 (mgmt plane = connected system) |
|
| Strong machine authentication | ISM-1546*, ISM-0974* | Mutual, certificate-based auth. | **802.1X EAP-TLS**, client key non-exportable in **TPM** (`0x81000100`), cert in TPM NV. The **management-plane agent reuses the same TPM identity** for mTLS (`RequireAndVerifyClientCert` to the fleet CA; server keys inventory off the verified cert serial). One non-exportable key = 802.1X + NVMe hostnqn + agent stream. | Partial | R-01 (not bench-verified; disabled in test builds), R-08 (placeholder CA), R-14 (mgmt plane = connected system) |
|
||||||
| Privileged access restricted | ISM-1507*, ISM-0445* | Limit & control admin access. | Single key-only `wheel` admin; root login disabled; admin **confined to `staff_u`** with a sudo `sysadm_r`/`sysadm_t` role/type transition (SELinux context elevates on privilege use). | Partial | R-05 |
|
| Privileged access restricted | ISM-1507*, ISM-0445* | Limit & control admin access. | Single key-only `wheel` admin; root login disabled; admin **confined to `staff_u`** with a sudo `sysadm_r`/`sysadm_t` role/type transition (SELinux context elevates on privilege use). | Partial | R-05 |
|
||||||
| Secure remote administration | ISM-1506*, ISM-1232* | Hardened admin channel. | SSH **key-only**, password & keyboard-interactive off, hardened ciphers/MACs/KEX (STIG crypto policy; ECDSA/RSA keys — ed25519 excluded by policy). | Implemented | — |
|
| Secure remote administration | ISM-1506*, ISM-1232* | Hardened admin channel. | SSH **key-only**, password & keyboard-interactive off, hardened ciphers/MACs/KEX (STIG crypto policy; ECDSA/RSA keys — ed25519 excluded by policy). | Implemented | — |
|
||||||
|
| Administrative host-key trust (SSH host certs) | ISM-1506*, ISM-1091* | Admins can verify host identity (no persistent TOFU). | Ephemeral overlay ⇒ new sshd host keys each boot; each host's per-boot key is **signed into an OpenSSH host certificate by the fleet SSH CA** at boot, brokered via the **enrol-broker host plane** over **TPM-identity mTLS** (`fleet-ssh-hostcert`). Admins trust one **`@cert-authority`** line; step-ca stays behind the broker; client **fails open** to a bare key if unreachable (no admin lock-out). **Implemented + bench-verified 2026-07-11 on clean build `28d31e4`** (comet-beacon-54: FQDN login/no-TOFU, **survives reboot unattended**, SELinux clean). | Implemented | **R-16** |
|
||||||
| No default/shared credentials | ISM-1304* | No baked credentials. | No per-host secrets in any artifact; identity from TPM at runtime; `machine-id` empty; host keys regenerated at first boot. | Implemented | — |
|
| No default/shared credentials | ISM-1304* | No baked credentials. | No per-host secrets in any artifact; identity from TPM at runtime; `machine-id` empty; host keys regenerated at first boot. | Implemented | — |
|
||||||
| MFA for privileged users | ISM-1173*, ISM-1504* | MFA on privileged access. | Machine auth is cert+TPM (strong). **Human admin MFA on the management-plane UI now enforced via WebAuthn passkeys** (phishing-resistant, origin-bound, hardware-backed — the manager's admin plane, v0.3). Host **SSH** admin remains key-only (no separate human MFA). | Partial (host SSH) / Implemented (mgmt UI) | R-05 |
|
| MFA for privileged users | ISM-1173*, ISM-1504* | MFA on privileged access. | Machine auth is cert+TPM (strong). **Human admin MFA on the management-plane UI now enforced via WebAuthn passkeys** (phishing-resistant, origin-bound, hardware-backed — the manager's admin plane, v0.3). Host **SSH** admin remains key-only (no separate human MFA). | Partial (host SSH) / Implemented (mgmt UI) | R-05 |
|
||||||
| Passwordless appliance accounts | — (STIG #10) | Account lockout/aging. | `kiosk`/`fleetadmin` are key-only; password aging/faillock **exempted for these two only**, full policy system-wide otherwise. | Implemented | R-05 (folds into rotatable-access roadmap) |
|
| Passwordless appliance accounts | — (STIG #10) | Account lockout/aging. | `kiosk`/`fleetadmin` are key-only; password aging/faillock **exempted for these two only**, full policy system-wide otherwise. | Implemented | R-05 (folds into rotatable-access roadmap) |
|
||||||
@@ -122,7 +128,7 @@ ISM/PSPF style). Ratings are **pre-treatment** unless stated; the AO accepts the
|
|||||||
| Secure build & change control | ISM-1483* | Controlled, repeatable builds. | Reproducible Packer build in CI; digest-pinned deployment; one commit/release; rollback = revert commit. | Implemented | — |
|
| Secure build & change control | ISM-1483* | Controlled, repeatable builds. | Reproducible Packer build in CI; digest-pinned deployment; one commit/release; rollback = revert commit. | Implemented | — |
|
||||||
| Protect build/signing key material | ISM-0501*, ISM-1233* | Protect keys. | Signing keys CI/HSM-held; **never** in repo or build VM; never logged. | Implemented | — |
|
| Protect build/signing key material | ISM-0501*, ISM-1233* | Protect keys. | Signing keys CI/HSM-held; **never** in repo or build VM; never logged. | Implemented | — |
|
||||||
| Privileged actions logged | ISM-1509* | Admin actions auditable. | auditd rules capture privileged/admin events. | Implemented | R-07 (offload protection) |
|
| Privileged actions logged | ISM-1509* | Admin actions auditable. | auditd rules capture privileged/admin events. | Implemented | R-07 (offload protection) |
|
||||||
| Disable debug/back-door access | ISM-1491* | No undocumented access paths. | `debug_shell=true` currently bakes a **passwordless root tty** (lab debugging). | Partial | **R-02 (must remove before prod)** |
|
| Disable debug/back-door access | ISM-1491* | No undocumented access paths. | The `debug_shell` passwordless-root-tty option was **removed from the template 2026-07-11** (`poam-hardening`); no build can bake it. | Implemented | R-02 (remediated); F-07 (runtime drift telemetry) |
|
||||||
| Device enrolment & identity lifecycle | ISM-1195*, ISM-0475* | Controlled device onboarding. | **TPM enrolment broker** (v0.3): the host generates a key in-TPM, submits a CSR to the one *unauthenticated pre-auth* endpoint; an admin verifies an **OOB fingerprint** and **approves over mTLS**, then **step-ca** signs the leaf into TPM NV and the manager births the inventory record (cert-serial = key). No auto-approval; identity is human-anchored. Server-side = connected system. | Partial (connected system) | R-14 |
|
| Device enrolment & identity lifecycle | ISM-1195*, ISM-0475* | Controlled device onboarding. | **TPM enrolment broker** (v0.3): the host generates a key in-TPM, submits a CSR to the one *unauthenticated pre-auth* endpoint; an admin verifies an **OOB fingerprint** and **approves over mTLS**, then **step-ca** signs the leaf into TPM NV and the manager births the inventory record (cert-serial = key). No auto-approval; identity is human-anchored. Server-side = connected system. | Partial (connected system) | R-14 |
|
||||||
| Server-authoritative management plane | ISM-1483*, ISM-1509* | Controlled remote management. | `fleet-agent` streams telemetry + receives desired-state over mTLS; **v1 logs commands but does not act** (no host mutation). Inventory/desired-state is server-authoritative. Reconcile actuators deferred. | Partial | R-14, **R-15 (actuators = future remote-mutation channel)** |
|
| Server-authoritative management plane | ISM-1483*, ISM-1509* | Controlled remote management. | `fleet-agent` streams telemetry + receives desired-state over mTLS; **v1 logs commands but does not act** (no host mutation). Inventory/desired-state is server-authoritative. Reconcile actuators deferred. | Partial | R-14, **R-15 (actuators = future remote-mutation channel)** |
|
||||||
|
|
||||||
@@ -158,8 +164,9 @@ ISM/PSPF style). Ratings are **pre-treatment** unless stated; the AO accepts the
|
|||||||
|---|---|---|---|---|---|
|
|---|---|---|---|---|---|
|
||||||
| ASD-Approved Cryptographic Algorithms (AACA) | ISM-0471*, ISM-1446* | Use approved algorithms. | STIG crypto policy (modern ciphers/MACs/KEX); EAP-TLS, MACsec-GCM. **Conformance to ISM AACA not formally asserted; FIPS off.** | Partial | **R-06** |
|
| ASD-Approved Cryptographic Algorithms (AACA) | ISM-0471*, ISM-1446* | Use approved algorithms. | STIG crypto policy (modern ciphers/MACs/KEX); EAP-TLS, MACsec-GCM. **Conformance to ISM AACA not formally asserted; FIPS off.** | Partial | **R-06** |
|
||||||
| ASD-Approved Cryptographic Protocols (AACP) | ISM-1139*, ISM-1453* | Use approved protocols (TLS 1.2+/1.3). | EAP-TLS; **management-plane agent stream is mTLS 1.2+** (fleet CA) — encrypted, unlike NVMe/TCP & log transport which remain plaintext on the LAN. | Partial | R-04, R-06, R-07 |
|
| ASD-Approved Cryptographic Protocols (AACP) | ISM-1139*, ISM-1453* | Use approved protocols (TLS 1.2+/1.3). | EAP-TLS; **management-plane agent stream is mTLS 1.2+** (fleet CA) — encrypted, unlike NVMe/TCP & log transport which remain plaintext on the LAN. | Partial | R-04, R-06, R-07 |
|
||||||
|
| Authenticated time source | ISM-1453*, ISM-0472* | Trusted, authenticated time. | **NTS (RFC 8915)** — chrony syncs only from a source whose NTS-KE TLS cert is **fleet-CA-signed** (verified against `radius-ca.pem`); server name derived from the DHCP domain at runtime (no baked FQDN); the unauthenticated DHCP source is not mixed in. A DHCP/NTP/DNS spoofer can only **DoS**, never shift the clock (time feeds every cert-validity decision). `nocerttimecheck 1` bootstraps an RTC-less cold boot. **Implemented 2026-07-11; bench-verified 2026-07-12 on clean build `5294860`** (comet-beacon-54: `chronyc authdata` NTS, cookies > 0, unattended). | Implemented | **R-17** |
|
||||||
| Key protection / non-exportability | ISM-0501*, ISM-1091* | Protect keys in hardware where feasible. | EAP-TLS **and management-plane** private key **non-exportable in TPM** (one key, three uses); UKI signing keys in HSM/CI. The **fleet CA (step-ca)** that signs host/agent certs is a **connected-system** trust anchor — its key protection is assessed there. | Implemented (host) / R-14 (CA) | R-14 |
|
| Key protection / non-exportability | ISM-0501*, ISM-1091* | Protect keys in hardware where feasible. | EAP-TLS **and management-plane** private key **non-exportable in TPM** (one key, three uses); UKI signing keys in HSM/CI. The **fleet CA (step-ca)** that signs host/agent certs is a **connected-system** trust anchor — its key protection is assessed there. | Implemented (host) / R-14 (CA) | R-14 |
|
||||||
| Certificate lifecycle | ISM-1091*, ISM-0501* | Managed cert issuance/renewal. | Host identity cert minted at enrolment (step-ca, into TPM NV). The manager's **agent-plane server cert auto-renews** from the co-located CA (`step ca renew --daemon` + in-process hot-reload) — preserves SANs, no 90-day lapse. Server-side = connected system. | Partial (connected system) | R-14 |
|
| Certificate lifecycle | ISM-1091*, ISM-0501* | Managed cert issuance/renewal. | Host identity cert minted at enrolment (step-ca, into TPM NV). The manager's **agent-plane server cert auto-renews** from the co-located CA (`step ca renew --daemon` + in-process hot-reload) — preserves SANs, no 90-day lapse. The **SSH host certificate** is (re)issued **every boot** by the fleet SSH CA via the broker (short-lived, per-boot lifecycle). Server-side CA = connected system. | Partial (connected system) | R-14 |
|
||||||
| Cryptographic equipment / module evaluation | ISM-0467*, ISM-1277* | Use evaluated crypto modules. | TPM 2.0 hardware; module-evaluation status to confirm; FIPS module not enabled. | Partial | R-06 |
|
| Cryptographic equipment / module evaluation | ISM-0467*, ISM-1277* | Use evaluated crypto modules. | TPM 2.0 hardware; module-evaluation status to confirm; FIPS module not enabled. | Partial | R-06 |
|
||||||
|
|
||||||
### 2.10 Guidelines for ICT Equipment & Media
|
### 2.10 Guidelines for ICT Equipment & Media
|
||||||
@@ -187,8 +194,8 @@ asked to accept. Treatments map to [docs/TODO.md](TODO.md) (working POA&M).
|
|||||||
| ID | Residual risk | ISM linkage | Likelihood × Consequence → **Rating** | Existing compensating controls | Treatment (POA&M) | Post-treat. residual | Owner / target |
|
| ID | Residual risk | ISM linkage | Likelihood × Consequence → **Rating** | Existing compensating controls | Treatment (POA&M) | Post-treat. residual | Owner / target |
|
||||||
|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|
|
||||||
| **R-01** | Secure path (802.1X EAP-TLS + MACsec) **implemented but not bench-verified end-to-end**; disabled in test builds (`disable_dot1x=true`). Until verified, devices may attach to an unauthenticated/unencrypted link. | 2.4, 2.7, 2.8 (ISM-1546*, 0520*, 1187*) | Likely × Major → **High** | Test path on an isolated lab LAN; full auth stack present (dormant); design follows ASD intent. | Full boot-chain bench test (swtpm + hostapd + FreeRADIUS): port `Authorized`, macsec0 addressed, MKA survives switch-root, NVMe root mounts. Re-enable in CI. | **Low** (once verified) | Platform team — *before production ATO* |
|
| **R-01** | Secure path (802.1X EAP-TLS + MACsec) **implemented but not bench-verified end-to-end**; disabled in test builds (`disable_dot1x=true`). Until verified, devices may attach to an unauthenticated/unencrypted link. | 2.4, 2.7, 2.8 (ISM-1546*, 0520*, 1187*) | Likely × Major → **High** | Test path on an isolated lab LAN; full auth stack present (dormant); design follows ASD intent. | Full boot-chain bench test (swtpm + hostapd + FreeRADIUS): port `Authorized`, macsec0 addressed, MKA survives switch-root, NVMe root mounts. Re-enable in CI. | **Low** (once verified) | Platform team — *before production ATO* |
|
||||||
| **R-02** | `debug_shell=true` bakes a **passwordless root shell** on a tty — an undocumented privileged access path. | 2.5 (ISM-1491*) | Possible × Severe → **High** | Lab-only; physical access required; isolated environment. | Set `debug_shell=false` for all non-lab builds; assert absent in CI. | **Low** | Platform team — *before production* |
|
| **R-02** | ~~`debug_shell=true` bakes a **passwordless root shell** on a tty~~ — **REMOVED 2026-07-11** (`poam-hardening`): the `debug_shell` variable and its `debug-shell.service` provisioner were deleted from the template; no build path can bake a debug shell. | 2.5 (ISM-1491*) | ~~Possible × Severe → **High**~~ → **eliminated at source** | Lab-only; physical access required; isolated environment (historical). | **Done** — removed from the template. Assessor verifies absence on the assessed digest (POA&M P-02 M2). | **Low** (residual = drift only, F-07) | Platform team — **remediated** |
|
||||||
| **R-03** | STIG hardening **applied + bench-verified** (2026-06-20, all REVIEW rows triaged); residual is ensuring **CI ships `enable_stig=true`** for production builds (it was toggled off during bring-up). | 2.3 (ISM-1407*/1408*) | Possible × Moderate → **Medium** | Tailoring + register + per-build report; SELinux/fapolicyd/USBGuard on; verified on a real hardened build. | Confirm `ENABLE_STIG="true"` in CI; keep the 0-eval guard; re-run the deviations report on the assessed build. | **Low** | Platform team — *before assessment* |
|
| **R-03** | STIG hardening **applied + bench-verified** (2026-06-20, all REVIEW rows triaged); `enable_stig=true` is now **enforced by Packer variable validation** (a false build fails `packer validate`) and CI fails closed on unsigned/placeholder-CA builds (2026-07-11). Residual: confirm on the *assessed* digest. | 2.3 (ISM-1407*/1408*) | Possible × Moderate → **Medium** | Tailoring + register + per-build report; SELinux/fapolicyd/USBGuard on; verified on a real hardened build; enforced-true toggle + 0-eval guard. | Re-run the deviations report on the assessed (signed) build; keep the 0-eval guard. | **Low** | Platform team — *before assessment* |
|
||||||
| **R-04** | NVMe/TCP **root traffic plaintext** on the LAN — exposure of OS content / injection if the segment is compromised. | 2.9 (ISM-1139*/1453*) | Unlikely × Major → **Medium** | Isolated boot LAN; MACsec on the access link (once R-01 done) protects the first hop; dm-verity makes injected root blocks fail verification (integrity preserved even if confidentiality isn't). | Production: TLS/IPsec for the storage transport, or rely on MACsec end-to-end across the fabric; document the decision. | **Low–Medium** | Platform/network — *production* |
|
| **R-04** | NVMe/TCP **root traffic plaintext** on the LAN — exposure of OS content / injection if the segment is compromised. | 2.9 (ISM-1139*/1453*) | Unlikely × Major → **Medium** | Isolated boot LAN; MACsec on the access link (once R-01 done) protects the first hop; dm-verity makes injected root blocks fail verification (integrity preserved even if confidentiality isn't). | Production: TLS/IPsec for the storage transport, or rely on MACsec end-to-end across the fabric; document the decision. | **Low–Medium** | Platform/network — *production* |
|
||||||
| **R-05** | **NOPASSWD key-only host admin**; no per-host or rotatable access model. (Management-plane admin MFA is now **enforced** via WebAuthn passkeys — v0.3; residual is the *host SSH* access model.) | 2.4 (ISM-1507*/1173*) | Possible × Moderate → **Medium** | Single fleet-generic admin, key-only, root disabled, ECDSA/RSA key, **SELinux-confined to `staff_u`** with sudo role/type elevation, fully audited; no interactive password to attack. Mgmt-plane admin actions now behind a phishing-resistant passkey. | Per-host/rotatable **host** access (SSH CA / config-agent); the mgmt-plane MFA clause is now met. | **Low–Medium** | Platform team — *roadmap* |
|
| **R-05** | **NOPASSWD key-only host admin**; no per-host or rotatable access model. (Management-plane admin MFA is now **enforced** via WebAuthn passkeys — v0.3; residual is the *host SSH* access model.) | 2.4 (ISM-1507*/1173*) | Possible × Moderate → **Medium** | Single fleet-generic admin, key-only, root disabled, ECDSA/RSA key, **SELinux-confined to `staff_u`** with sudo role/type elevation, fully audited; no interactive password to attack. Mgmt-plane admin actions now behind a phishing-resistant passkey. | Per-host/rotatable **host** access (SSH CA / config-agent); the mgmt-plane MFA clause is now met. | **Low–Medium** | Platform team — *roadmap* |
|
||||||
| **R-06** | **FIPS off**; ISM **AACA/AACP conformance not formally asserted**; crypto-module evaluation status unconfirmed. | 2.9 (ISM-0471*/1139*/1277*) | Possible × Major → **Medium** | Modern STIG crypto policy; non-exportable TPM key; no deliberately weak algorithms. Acceptable at OFFICIAL; **mandatory to resolve for PROTECTED+.** | Assert AACA/AACP conformance; decide on FIPS module / evaluated crypto alongside Secure Boot signing. | **Low** (at appropriate classification) | Security + Platform — *before PROTECTED+* |
|
| **R-06** | **FIPS off**; ISM **AACA/AACP conformance not formally asserted**; crypto-module evaluation status unconfirmed. | 2.9 (ISM-0471*/1139*/1277*) | Possible × Major → **Medium** | Modern STIG crypto policy; non-exportable TPM key; no deliberately weak algorithms. Acceptable at OFFICIAL; **mandatory to resolve for PROTECTED+.** | Assert AACA/AACP conformance; decide on FIPS module / evaluated crypto alongside Secure Boot signing. | **Low** (at appropriate classification) | Security + Platform — *before PROTECTED+* |
|
||||||
@@ -201,6 +208,8 @@ asked to accept. Treatments map to [docs/TODO.md](TODO.md) (working POA&M).
|
|||||||
| **R-12** | **No formal Incident Response Plan**; volatile RAM overlay complicates live forensics. | 2.1, 2.6 (ISM-0043*/0109*) | Possible × Moderate → **Medium** | Inherent recovery (reboot → verified state; revert digest); centralised logs survive host wipe. | Author IRP + log-retention + break-glass/forensics procedure suited to stateless hosts. | **Low** | SecOps — *before production* |
|
| **R-12** | **No formal Incident Response Plan**; volatile RAM overlay complicates live forensics. | 2.1, 2.6 (ISM-0043*/0109*) | Possible × Moderate → **Medium** | Inherent recovery (reboot → verified state; revert digest); centralised logs survive host wipe. | Author IRP + log-retention + break-glass/forensics procedure suited to stateless hosts. | **Low** | SecOps — *before production* |
|
||||||
| **R-14** | **Management/enrolment plane is a connected system, built but not yet separately authorised.** The host now trusts + streams to the fleet-manager and enrols via the broker + **step-ca fleet CA** (a new trust anchor + key material); the broker exposes **one unauthenticated pre-auth CSR endpoint**; InfluxDB holds telemetry + a bearer token. These server-side controls (CA key protection, broker hardening, admin-plane exposure, data-at-rest) are outside this SSP's boundary. | 2.4, 2.5, 2.6, 2.9 (ISM-1546*/1483*/0501*) | Possible × Moderate → **Medium** | Host-side exposure is limited: **outbound-only** mTLS, TPM-bound identity, **read-only** telemetry (no user data), and **commands logged-not-applied in v1**. Broker enrolment is human-anchored (OOB fingerprint + admin mTLS approve; no auto-approval). Mgmt-plane admin UI behind passkey MFA. | Author a **separate SSP/authorisation** for the management plane (CA key protection, broker endpoint hardening + rate-limiting, InfluxDB token/data protection, admin-plane network exposure); TLS everywhere is already in place on this plane. | **Low–Medium** | Platform/SecOps — *before the mgmt plane is production* |
|
| **R-14** | **Management/enrolment plane is a connected system, built but not yet separately authorised.** The host now trusts + streams to the fleet-manager and enrols via the broker + **step-ca fleet CA** (a new trust anchor + key material); the broker exposes **one unauthenticated pre-auth CSR endpoint**; InfluxDB holds telemetry + a bearer token. These server-side controls (CA key protection, broker hardening, admin-plane exposure, data-at-rest) are outside this SSP's boundary. | 2.4, 2.5, 2.6, 2.9 (ISM-1546*/1483*/0501*) | Possible × Moderate → **Medium** | Host-side exposure is limited: **outbound-only** mTLS, TPM-bound identity, **read-only** telemetry (no user data), and **commands logged-not-applied in v1**. Broker enrolment is human-anchored (OOB fingerprint + admin mTLS approve; no auto-approval). Mgmt-plane admin UI behind passkey MFA. | Author a **separate SSP/authorisation** for the management plane (CA key protection, broker endpoint hardening + rate-limiting, InfluxDB token/data protection, admin-plane network exposure); TLS everywhere is already in place on this plane. | **Low–Medium** | Platform/SecOps — *before the mgmt plane is production* |
|
||||||
| **R-15** | **Deferred agent reconcile actuators.** v1 receives `DesiredState`/`Command` but **only logs** them; when actuation (apply `kiosk_url` / `reboot` / drain) is built, `fleet-agent` becomes a **remote host-mutation channel**. | 2.5 (ISM-1483*/1509*) | Rare × Moderate → **Low** (v1) | v1 performs **no host mutation** — the receive path is inert; the command authZ path (server-authoritative desired-state, verified over mTLS) is designed but not exercised. | At build time, assess the command/authZ path (who can set desired-state, replay/authZ, blast radius of `reboot`/drain) before enabling actuators; keep it behind an explicit flag. | **Low** | Platform team — *at actuator build* |
|
| **R-15** | **Deferred agent reconcile actuators.** v1 receives `DesiredState`/`Command` but **only logs** them; when actuation (apply `kiosk_url` / `reboot` / drain) is built, `fleet-agent` becomes a **remote host-mutation channel**. | 2.5 (ISM-1483*/1509*) | Rare × Moderate → **Low** (v1) | v1 performs **no host mutation** — the receive path is inert; the command authZ path (server-authoritative desired-state, verified over mTLS) is designed but not exercised. | At build time, assess the command/authZ path (who can set desired-state, replay/authZ, blast radius of `reboot`/drain) before enabling actuators; keep it behind an explicit flag. | **Low** | Platform team — *at actuator build* |
|
||||||
|
| **R-16** | Per-boot SSH host keys made admin SSH **perpetual TOFU** — a host key can never be pinned, so a LAN MITM of admin SSH is client-undetectable. **Remediated 2026-07-11** (`poam-hardening`): broker-mediated **SSH host certificates**. | 2.4, 2.9 (ISM-1506*/1091*) | Possible × Moderate → **Medium** (pre-fix) | Each per-boot key is **signed by the fleet SSH CA** via the enrol-broker host plane (TPM-mTLS); admins trust one **`@cert-authority`** line; step-ca behind the single front door; client **fails open** to a bare key (no lock-out) if broker/CA unreachable. | **Bench-verified 2026-07-11 on clean build `28d31e4`** (comet-beacon-54: FQDN login/no-TOFU, **survives reboot unattended** — auto-derived FQDN principal, not a manual edit; SELinux clean). Remaining: broker-side principal validation (defence-in-depth) + roll the `@cert-authority` anchor to admins. Design B deferred, not required. | **Low** | Platform team — *done; roll anchor* |
|
||||||
|
| **R-17** | **Time source was unauthenticated** (DHCP-handed NTP, no NTS) — an attacker spoofing DHCP/NTP could shift the clock to make expired/revoked certs validate (time feeds EAP-TLS, agent mTLS, future log TLS). **Remediated 2026-07-11** (`secure-ntp`): **NTS (RFC 8915)**. | 2.9 (ISM-1453*/1139*) | Possible × Moderate → **Low–Medium** (pre-fix) | Serving NTP presents a **fleet-CA-signed** NTS-KE cert; host (`secure_ntp=true`) verifies against `radius-ca.pem` and **no longer mixes in** the unauthenticated DHCP source; server name derived from the DHCP domain (no baked FQDN). A spoofer can only **DoS**, not shift time. Compensating meanwhile: isolated LAN + cert-serial inventory gate. | **Bench-verified 2026-07-12 on clean build `5294860`** (comet-beacon-54: `chronyc authdata` NTS, cookies > 0, unattended clean boot, sources read under enforcing SELinux). Remaining: issue the per-environment NTS cert; cold-clock `nocerttimecheck` + active-spoof rejection as defence-in-depth (F-03/P-18 M3). | **Low** | Platform team — *issue per-env cert* |
|
||||||
|
|
||||||
### 3.1 Risk heatmap (post-treatment targets)
|
### 3.1 Risk heatmap (post-treatment targets)
|
||||||
|
|
||||||
@@ -211,6 +220,7 @@ Likelihood ↓
|
|||||||
Likely R-10 R-01*
|
Likely R-10 R-01*
|
||||||
Possible R-05 R-07 R-03 R-02*
|
Possible R-05 R-07 R-03 R-02*
|
||||||
R-12 R-14 R-06 R-08
|
R-12 R-14 R-06 R-08
|
||||||
|
R-16 R-17
|
||||||
Unlikely R-04
|
Unlikely R-04
|
||||||
R-11 R-09
|
R-11 R-09
|
||||||
Rare R-15
|
Rare R-15
|
||||||
@@ -227,7 +237,11 @@ connected system requiring its own authorisation) and **R-15** (deferred agent
|
|||||||
actuators — a future remote-mutation channel, inert in v1). Newly *strengthened*
|
actuators — a future remote-mutation channel, inert in v1). Newly *strengthened*
|
||||||
in v0.3: **human-admin MFA on the management plane** via WebAuthn passkeys
|
in v0.3: **human-admin MFA on the management plane** via WebAuthn passkeys
|
||||||
(improves R-05's MFA clause), and **application control made effective on the
|
(improves R-05's MFA clause), and **application control made effective on the
|
||||||
overlay root** (the 2026-07-04 fapolicyd `watch_fs` fix). Carried from v0.2:
|
overlay root** (the 2026-07-04 fapolicyd `watch_fs` fix). Newly recorded in **v0.4**: **R-16**
|
||||||
|
(per-boot SSH host keys were perpetual TOFU — now **broker-signed SSH host
|
||||||
|
certificates**) and **R-17** (time was unauthenticated — now **NTS**), both
|
||||||
|
remediated 2026-07-11 and rated **Low** post-fix — R-16 **bench-verified 2026-07-11**, R-17 (NTS) **bench-verified 2026-07-12**.
|
||||||
|
Carried from v0.2:
|
||||||
USBGuard device control, admin SELinux confinement, STIG applied+verified, and
|
USBGuard device control, admin SELinux confinement, STIG applied+verified, and
|
||||||
Secure Boot signing verified enforcing on real hardware (R-13 Medium→Low).*
|
Secure Boot signing verified enforcing on real hardware (R-13 Medium→Low).*
|
||||||
|
|
||||||
@@ -247,8 +261,13 @@ Secure Boot signing verified enforcing on real hardware (R-13 Medium→Low).*
|
|||||||
5. **Review the STIG generated report** for the build under assessment — every
|
5. **Review the STIG generated report** for the build under assessment — every
|
||||||
non-pass rule should map to the [deviation register](stig-deviations.md);
|
non-pass rule should map to the [deviation register](stig-deviations.md);
|
||||||
anything flagged **REVIEW** is an unjustified finding.
|
anything flagged **REVIEW** is an unjustified finding.
|
||||||
6. **Confirm lab-only flags are off** in the assessed build: `debug_shell=false`,
|
6. **Confirm lab-only flags are off** in the assessed build. As of 2026-07-11
|
||||||
`enable_stig=true`, `disable_dot1x=false`, real RADIUS CA in place.
|
(`poam-hardening`) most are now enforced fail-closed rather than convention:
|
||||||
|
`debug_shell` no longer exists (removed from the template); `enable_stig=true`
|
||||||
|
is enforced by Packer variable validation; the CI Production build gate fails
|
||||||
|
closed unless `SB_KEY`/`SB_CERT`/`FLEET_CA` are set (signed UKI + real RADIUS
|
||||||
|
CA, no placeholder). **Still verify by hand:** `disable_dot1x=false` (not yet
|
||||||
|
enforced — blocked on the P-01 secure-path bench).
|
||||||
7. **Confirm boot-chain signing is the default** (R-13): signing + Secure Boot
|
7. **Confirm boot-chain signing is the default** (R-13): signing + Secure Boot
|
||||||
enforcement are **verified on hardware** (VM HTTP + 820 PXE, 2026-06-22) —
|
enforcement are **verified on hardware** (VM HTTP + 820 PXE, 2026-06-22) —
|
||||||
confirm the *assessed* build is signed and the *assessed* hosts have `db`
|
confirm the *assessed* build is signed and the *assessed* hosts have `db`
|
||||||
@@ -268,6 +287,15 @@ Secure Boot signing verified enforcing on real hardware (R-13 Medium→Low).*
|
|||||||
did not guard the overlay root).
|
did not guard the overlay root).
|
||||||
11. **Confirm actuators are inert (R-15):** the assessed build's `fleet-agent`
|
11. **Confirm actuators are inert (R-15):** the assessed build's `fleet-agent`
|
||||||
**logs but does not apply** desired-state/commands (telemetry-only v1).
|
**logs but does not apply** desired-state/commands (telemetry-only v1).
|
||||||
|
12. **Witness SSH host certificates (R-16):** on a booted + enrolled host, `sshd`
|
||||||
|
presents a **fleet-SSH-CA-signed host certificate** (`ssh-keygen -L -f` the
|
||||||
|
presented cert; principals/validity per policy); an admin with only the
|
||||||
|
`@cert-authority` line connects **without a TOFU prompt**; confirm the client
|
||||||
|
**fails open** to a bare key (no admin lock-out) if the broker/CA is down.
|
||||||
|
13. **Witness NTS authenticated time (R-17):** `chronyc -N authdata` shows the
|
||||||
|
source in **NTS** mode with cookies > 0; a rogue NTP/DHCP/DNS source is **not**
|
||||||
|
selected (NTS handshake fails ⇒ DoS only, no clock shift); a cold-clock boot
|
||||||
|
bootstraps via `nocerttimecheck 1`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+40
-6
@@ -15,10 +15,10 @@
|
|||||||
|---|---|
|
|---|---|
|
||||||
| System name | Diskless Fleet Boot System ("the fleet") |
|
| System name | Diskless Fleet Boot System ("the fleet") |
|
||||||
| Document type | System Security Plan (SSP) |
|
| Document type | System Security Plan (SSP) |
|
||||||
| Version | 0.3 (DRAFT — pre-assessment) |
|
| Version | 0.4 (DRAFT — pre-assessment) |
|
||||||
| Date | 2026-07-10 |
|
| Date | 2026-07-11 |
|
||||||
| Author / maintainer | System Owner (delegate) |
|
| Author / maintainer | System Owner (delegate) |
|
||||||
| Status | Lab / pre-production. STIG hardening applied + bench-verified; **UEFI Secure Boot signing verified enforcing on real hardware** (VM HTTP + EliteBook 820 PXE); secure path (802.1X/MACsec) not yet bench-verified. **v0.3 adds the fleet-management plane** (on-host `fleet-agent` telemetry over TPM-mTLS — bench-verified on hardware 2026-07-10; server-side manager with **WebAuthn-passkey** admin MFA + TPM enrolment broker) and July hardening changes (three local SELinux modules; fapolicyd overlay exec-guard fix). |
|
| Status | Lab / pre-production. STIG hardening applied + bench-verified; **UEFI Secure Boot signing verified enforcing on real hardware** (VM HTTP + EliteBook 820 PXE); secure path (802.1X/MACsec) not yet bench-verified. **v0.3 adds the fleet-management plane** (on-host `fleet-agent` telemetry over TPM-mTLS — bench-verified on hardware 2026-07-10; server-side manager with **WebAuthn-passkey** admin MFA + TPM enrolment broker) and July hardening changes (three local SELinux modules; fapolicyd overlay exec-guard fix). **v0.4** adds two host-identity/time hardening features (both implemented 2026-07-11): broker-mediated **SSH host certificates** (F-02/P-17 — **bench-verified 2026-07-11** on `comet-beacon-54`, clean build `28d31e4`) and **NTS authenticated time** (F-03/P-18 — **bench-verified 2026-07-12** on `comet-beacon-54`, clean build `5294860`). |
|
||||||
| Companion documents | [Annex A — ISM control cross-reference & residual risk](ssp-annex-a-ism-residual-risk.md); [Architecture design v2](diskless-fleet-design-v2.pdf); [Design errata](design-v2-errata.md); [Fleet-management design](fleet-management-design.md); [TPM enrolment design](tpm-enrolment-design.md); [STIG deviation register](stig-deviations.md); [Backlog / POA&M source](TODO.md) |
|
| Companion documents | [Annex A — ISM control cross-reference & residual risk](ssp-annex-a-ism-residual-risk.md); [Architecture design v2](diskless-fleet-design-v2.pdf); [Design errata](design-v2-errata.md); [Fleet-management design](fleet-management-design.md); [TPM enrolment design](tpm-enrolment-design.md); [STIG deviation register](stig-deviations.md); [Backlog / POA&M source](TODO.md) |
|
||||||
|
|
||||||
> **Currency caveat (read before relying on control numbers).** This SSP and its
|
> **Currency caveat (read before relying on control numbers).** This SSP and its
|
||||||
@@ -110,6 +110,13 @@ manifest. One commit = one release = one digest = one manifest entry.
|
|||||||
7. `switch-root` into the real system; NetworkManager renews the DHCP lease so
|
7. `switch-root` into the real system; NetworkManager renews the DHCP lease so
|
||||||
the network-mounted root stays alive; the host derives its NVMe `hostnqn` from
|
the network-mounted root stays alive; the host derives its NVMe `hostnqn` from
|
||||||
the TPM certificate serial and (kiosk builds) launches the locked-down kiosk.
|
the TPM certificate serial and (kiosk builds) launches the locked-down kiosk.
|
||||||
|
Early in the running system the host also (a) syncs **CA-authenticated time**
|
||||||
|
(NTS, RFC 8915 — the NTP source's TLS certificate is verified against the fleet
|
||||||
|
CA, so a DHCP/NTP/DNS spoofer can only DoS, never shift the clock) and (b) gets
|
||||||
|
its **per-boot SSH host key signed into an OpenSSH host certificate** by the
|
||||||
|
fleet SSH CA — brokered through the enrol-broker host plane over TPM-identity
|
||||||
|
mTLS — so administrators trust the fleet through a single `@cert-authority` line
|
||||||
|
instead of per-boot trust-on-first-use.
|
||||||
8. **Post-boot management (v0.3):** the hardened **`fleet-agent`** service dials
|
8. **Post-boot management (v0.3):** the hardened **`fleet-agent`** service dials
|
||||||
the management plane and identifies with the **same TPM key/cert** used for
|
the management plane and identifies with the **same TPM key/cert** used for
|
||||||
802.1X and the NVMe `hostnqn` ("one identity, three stages"), streaming host
|
802.1X and the NVMe `hostnqn` ("one identity, three stages"), streaming host
|
||||||
@@ -145,7 +152,11 @@ control point addressed in Annex A.
|
|||||||
audit-log offload currently **plaintext on an isolated lab LAN** (see residual
|
audit-log offload currently **plaintext on an isolated lab LAN** (see residual
|
||||||
risk R-04, R-07 in Annex A). The **management-plane** telemetry stream is
|
risk R-04, R-07 in Annex A). The **management-plane** telemetry stream is
|
||||||
**mTLS-encrypted** (fleet CA) end-to-end — unlike the NVMe/audit hops, it is not
|
**mTLS-encrypted** (fleet CA) end-to-end — unlike the NVMe/audit hops, it is not
|
||||||
plaintext.
|
plaintext. **NTP is now NTS-authenticated** (RFC 8915) — the time source's TLS
|
||||||
|
certificate is verified against the fleet CA, so the host clock cannot be spoofed
|
||||||
|
(F-03). Administrative **SSH host identity** is asserted by a **fleet-SSH-CA
|
||||||
|
certificate** (broker-signed at boot), replacing per-boot trust-on-first-use
|
||||||
|
(F-02).
|
||||||
- **Management telemetry (v0.3):** the agent sends host **health** (load/cpu/temp/
|
- **Management telemetry (v0.3):** the agent sends host **health** (load/cpu/temp/
|
||||||
memory/tmpfs, uptime, NVMe-root state, 802.1X/MACsec + SELinux state, chrony
|
memory/tmpfs, uptime, NVMe-root state, 802.1X/MACsec + SELinux state, chrony
|
||||||
clock health, kiosk state) and **inventory facts** (image version, kernel,
|
clock health, kiosk state) and **inventory facts** (image version, kernel,
|
||||||
@@ -258,6 +269,15 @@ summarises the implementation posture.
|
|||||||
long-standing "no admin MFA" gap for management operations; host SSH admin
|
long-standing "no admin MFA" gap for management operations; host SSH admin
|
||||||
remains key-only — R-05).
|
remains key-only — R-05).
|
||||||
- **Link protection:** **MACsec** on the access link.
|
- **Link protection:** **MACsec** on the access link.
|
||||||
|
- **Authenticated time (NTS — v0.4):** chrony syncs only from an **NTS** (RFC 8915)
|
||||||
|
source whose NTS-KE TLS certificate is **fleet-CA-signed** (verified against the
|
||||||
|
same `radius-ca.pem` the host already trusts). The server name is derived at
|
||||||
|
runtime from the DHCP domain (`ntp.<domain>` — no baked FQDN, so a site-domain
|
||||||
|
change needs no rebuild), and the unauthenticated DHCP option-42 source is **not**
|
||||||
|
mixed in. A DHCP/NTP/DNS spoofer can therefore only cause **no-sync (DoS)**, never
|
||||||
|
shift the clock — closing the "unauthenticated time feeds every certificate-
|
||||||
|
validity decision" gap (F-03/P-18). `nocerttimecheck 1` lets an RTC-less cold boot
|
||||||
|
bootstrap the first measurement. Toggle `secure_ntp` (default true).
|
||||||
- **Boot & OS integrity:** **Secure Boot** + **signed UKI** with an **immutable
|
- **Boot & OS integrity:** **Secure Boot** + **signed UKI** with an **immutable
|
||||||
cmdline**; **dm-verity** read-only root (continuous cryptographic integrity);
|
cmdline**; **dm-verity** read-only root (continuous cryptographic integrity);
|
||||||
**erofs** immutable filesystem; **reproducible** build (verity hash is a pure
|
**erofs** immutable filesystem; **reproducible** build (verity hash is a pure
|
||||||
@@ -296,6 +316,18 @@ summarises the implementation posture.
|
|||||||
transition (`sysadm_r`/`sysadm_t`) so privilege elevation re-labels the SELinux
|
transition (`sysadm_r`/`sysadm_t`) so privilege elevation re-labels the SELinux
|
||||||
context (STIG `selinux_context_elevation_for_sudo`). Still **NOPASSWD** (the
|
context (STIG `selinux_context_elevation_for_sudo`). Still **NOPASSWD** (the
|
||||||
account is passwordless/key-only) and fleet-generic — see R-05.
|
account is passwordless/key-only) and fleet-generic — see R-05.
|
||||||
|
- **Administrative host-key trust (SSH host certificates — v0.4):** because the
|
||||||
|
overlay is ephemeral, sshd host keys are new every boot; rather than perpetual
|
||||||
|
trust-on-first-use, each host at boot gets its **per-boot host key signed into an
|
||||||
|
OpenSSH host certificate by the fleet SSH CA**, brokered through the
|
||||||
|
**enrol-broker host plane** over **TPM-identity mTLS** (the same non-exportable
|
||||||
|
TPM key that backs 802.1X and the NVMe `hostnqn`). Admins add **one
|
||||||
|
`@cert-authority` line** and thereafter verify every host against the fleet SSH
|
||||||
|
CA — no per-boot key pinning, and a LAN MITM of admin SSH becomes
|
||||||
|
client-detectable. step-ca stays behind the broker's single front door; the
|
||||||
|
client **fails open** to a bare (TOFU) key if the broker/CA is unreachable, so it
|
||||||
|
cannot brick admin access. Closes F-02/P-17. Toggle `enable_ssh_hostcert`
|
||||||
|
(default true).
|
||||||
- **No persistent attack surface:** diskless; overlay discarded each boot; no
|
- **No persistent attack surface:** diskless; overlay discarded each boot; no
|
||||||
per-host secrets in the image.
|
per-host secrets in the image.
|
||||||
- *Gaps / decisions:* **FIPS OFF** (lab phase); **host firewall disabled** (the
|
- *Gaps / decisions:* **FIPS OFF** (lab phase); **host firewall disabled** (the
|
||||||
@@ -367,8 +399,8 @@ production ATO:
|
|||||||
| ID | Residual risk | Rating (pre-treatment) | Disposition |
|
| ID | Residual risk | Rating (pre-treatment) | Disposition |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| R-01 | Secure path (802.1X/MACsec) not yet bench-verified; currently disabled in test builds. | **High** | Treat — verify before production authorisation. |
|
| R-01 | Secure path (802.1X/MACsec) not yet bench-verified; currently disabled in test builds. | **High** | Treat — verify before production authorisation. |
|
||||||
| R-02 | `debug_shell=true` bakes a **passwordless root shell** on a tty. | **High** | Treat — must be removed before production (lab-only). |
|
| R-02 | ~~`debug_shell=true` bakes a **passwordless root shell** on a tty.~~ **Removed 2026-07-11** (`poam-hardening`): the `debug_shell` variable and its provisioner were deleted from the template — no build can bake a debug shell. | **High → residual Low** | **Remediated** — assessor to verify absence on the assessed digest (POA&M P-02 M2). |
|
||||||
| R-03 | STIG hardening **applied + bench-verified** (2026-06-20); residual is that CI ships **unsigned, Secure Boot OFF** and `enable_stig` must be confirmed on for production builds. | **Medium** | Treat — confirm `enable_stig=true` in CI + enforce signing/SB before assessment. |
|
| R-03 | STIG hardening **applied + bench-verified** (2026-06-20); `enable_stig=true` is now **enforced by Packer variable validation** and CI fails closed on unsigned/placeholder-CA builds (2026-07-11). Residual: signing/SB must be confirmed on the *assessed* digest. | **Medium** | Treat — confirm signed + `enable_stig=true` on the assessed build (P-03/P-04/P-05). |
|
||||||
| R-04 | NVMe/TCP root traffic **plaintext** on the lab LAN. | **Medium** | Accept (isolated LAN) / treat for production. |
|
| R-04 | NVMe/TCP root traffic **plaintext** on the lab LAN. | **Medium** | Accept (isolated LAN) / treat for production. |
|
||||||
| R-05 | NOPASSWD key-only **host** admin (now `staff_u`-confined); no per-host/rotatable access model. (Management-plane admin MFA now enforced via WebAuthn passkeys — v0.3.) | **Medium** | Accept with compensating controls / roadmap. |
|
| R-05 | NOPASSWD key-only **host** admin (now `staff_u`-confined); no per-host/rotatable access model. (Management-plane admin MFA now enforced via WebAuthn passkeys — v0.3.) | **Medium** | Accept with compensating controls / roadmap. |
|
||||||
| R-06 | **FIPS off**; ASD-approved-cryptography conformance not yet asserted. | **Medium** | Treat — required for PROTECTED+; revisit with signing. |
|
| R-06 | **FIPS off**; ASD-approved-cryptography conformance not yet asserted. | **Medium** | Treat — required for PROTECTED+; revisit with signing. |
|
||||||
@@ -378,6 +410,8 @@ production ATO:
|
|||||||
| R-13 | **Boot-chain signing not yet the standing default.** Signing + Secure Boot enforcement **verified end-to-end on hardware** (VM HTTP UKI 2026-06-20; 820 PXE signed iPXE→UKI 2026-06-22), but unsigned/SB-OFF lab builds are still possible (CI signs only when `SB_KEY`/`SB_CERT` are set) and not all hosts have `db` enrolled. | **Low** | Treat — make signed + Secure-Boot-on the default for all production builds/hosts (set CI secrets; enrol `db` fleet-wide). |
|
| R-13 | **Boot-chain signing not yet the standing default.** Signing + Secure Boot enforcement **verified end-to-end on hardware** (VM HTTP UKI 2026-06-20; 820 PXE signed iPXE→UKI 2026-06-22), but unsigned/SB-OFF lab builds are still possible (CI signs only when `SB_KEY`/`SB_CERT` are set) and not all hosts have `db` enrolled. | **Low** | Treat — make signed + Secure-Boot-on the default for all production builds/hosts (set CI secrets; enrol `db` fleet-wide). |
|
||||||
| R-14 | **Management/enrolment plane is a connected system, built but not yet separately authorised.** The host now trusts + streams to the fleet-manager and enrols via the broker/**step-ca fleet CA** (a new trust anchor + key material) whose one **unauthenticated pre-auth CSR endpoint** is a new server-side surface. Host-side exposure is limited (outbound-only mTLS, read-only telemetry, commands logged-not-applied in v1). | **Medium** | Treat — separate SSP/authorisation for the management plane (CA key protection, broker endpoint hardening, InfluxDB token/data, admin-plane exposure). |
|
| R-14 | **Management/enrolment plane is a connected system, built but not yet separately authorised.** The host now trusts + streams to the fleet-manager and enrols via the broker/**step-ca fleet CA** (a new trust anchor + key material) whose one **unauthenticated pre-auth CSR endpoint** is a new server-side surface. Host-side exposure is limited (outbound-only mTLS, read-only telemetry, commands logged-not-applied in v1). | **Medium** | Treat — separate SSP/authorisation for the management plane (CA key protection, broker endpoint hardening, InfluxDB token/data, admin-plane exposure). |
|
||||||
| R-15 | **Deferred agent reconcile actuators.** v1 receives `DesiredState`/`Command` but only logs them; when actuation (apply kiosk_url / reboot / drain) is built, the agent becomes a **remote host-mutation channel** needing re-assessment. | **Low** (v1) | Treat at build time — assess the command/authZ path before enabling actuators. |
|
| R-15 | **Deferred agent reconcile actuators.** v1 receives `DesiredState`/`Command` but only logs them; when actuation (apply kiosk_url / reboot / drain) is built, the agent becomes a **remote host-mutation channel** needing re-assessment. | **Low** (v1) | Treat at build time — assess the command/authZ path before enabling actuators. |
|
||||||
|
| R-16 | Per-boot SSH host keys made admin SSH perpetual **TOFU** (LAN MITM client-undetectable). **Remediated 2026-07-11**: broker-mediated **SSH host certificates** (`enable_ssh_hostcert=true`) — per-boot key signed by the fleet SSH CA via the enrol-broker host plane (TPM-mTLS); admins trust one `@cert-authority` line. **Bench-verified 2026-07-11 on clean build `28d31e4`** (comet-beacon-54: FQDN login by name, no TOFU, **survives reboot unattended**; SELinux clean). | **Low** | Treat — roll the `@cert-authority` anchor to admins; broker-side principal validation is a defence-in-depth TODO (F-02/P-17). |
|
||||||
|
| R-17 | Time source was **unauthenticated** (DHCP-handed NTP) — the clock feeds every certificate-validity decision. **Remediated 2026-07-11**: **NTS** (RFC 8915, `secure_ntp=true`) — fleet-CA-signed NTS-KE cert verified against `radius-ca.pem`; server name derived from the DHCP domain (no baked FQDN); a spoofer can only DoS. **Bench-verified 2026-07-12 on clean build `5294860`** (comet-beacon-54: `chronyc authdata` NTS, cookies > 0, unattended on a clean boot, sources read under enforcing SELinux). | **Low** | Treat — issue the per-env NTS cert; cold-clock (`nocerttimecheck`) + active-spoof rejection remain as defence-in-depth bench items (F-03/P-18). |
|
||||||
|
|
||||||
Each is detailed, ISM-cross-referenced, rated and assigned a treatment in Annex A.
|
Each is detailed, ISM-cross-referenced, rated and assigned a treatment in Annex A.
|
||||||
|
|
||||||
|
|||||||
+48
-7
@@ -11,10 +11,11 @@ control. The per-build **generated** report
|
|||||||
post-remediation scan) cross-references this file: any non-`pass` rule **not**
|
post-remediation scan) cross-references this file: any non-`pass` rule **not**
|
||||||
listed here is flagged **REVIEW**.
|
listed here is flagged **REVIEW**.
|
||||||
|
|
||||||
Posture decisions (2026-06-18, lab phase):
|
Posture decisions (2026-06-18, lab phase; offload updated 2026-07-12):
|
||||||
- **FIPS: OFF** — revisit alongside UKI signing / Secure Boot.
|
- **FIPS: OFF** — revisit alongside UKI signing / Secure Boot.
|
||||||
- **Audit offload: LOCAL-only** — auditd runs and logs to the tmpfs overlay; no
|
- **Audit offload: REMOTE over syslog-TLS** — audisp-syslog + rsyslog `omfwd`
|
||||||
remote collector yet.
|
(gtls :6514, fleet-CA x509/name) to `serving/audit-collector` (see row 6;
|
||||||
|
was LOCAL-only until 2026-06-19, plaintext until 2026-07-12).
|
||||||
- **CI: report-only** — `oscap eval` publishes ARF + HTML; never fails the build.
|
- **CI: report-only** — `oscap eval` publishes ARF + HTML; never fails the build.
|
||||||
|
|
||||||
## Intentional deviations
|
## Intentional deviations
|
||||||
@@ -109,8 +110,9 @@ SELinux user (`semanage login`) so the `staff_r`→`sysadm_r` sudo transition is
|
|||||||
authorized at runtime. `staff_u` (not `sysadm_u`) is used so SSH login isn't gated
|
authorized at runtime. `staff_u` (not `sysadm_u`) is used so SSH login isn't gated
|
||||||
by the `ssh_sysadm_login` boolean. **⚠ Two bench gates before trusting:** (a)
|
by the `ssh_sysadm_login` boolean. **⚠ Two bench gates before trusting:** (a)
|
||||||
admin SSH login + `sudo` + every privileged op must work under `staff_u`
|
admin SSH login + `sudo` + every privileged op must work under `staff_u`
|
||||||
confinement — if it locks out, `enable_stig=false` is the escape hatch and this
|
confinement — if it locks out, waive the offending rule in the tailoring as a
|
||||||
reverts to a justified deviation; (b) reproducibility — `semanage` rebuilds the
|
justified deviation (STIG is now enforced-true — there is no `enable_stig=false`
|
||||||
|
escape hatch, POA&M P-03); (b) reproducibility — `semanage` rebuilds the
|
||||||
SELinux policy store, so confirm the verity hash is stable across two builds (and
|
SELinux policy store, so confirm the verity hash is stable across two builds (and
|
||||||
normalize in 2d hygiene if it drifts).
|
normalize in 2d hygiene if it drifts).
|
||||||
|
|
||||||
@@ -135,7 +137,8 @@ reboot** — it cannot exfiltrate data, alter the read-only root, or bypass auth
|
|||||||
a shell. Authenticated reboot paths (`systemctl reboot` over the key-only admin
|
a shell. Authenticated reboot paths (`systemctl reboot` over the key-only admin
|
||||||
SSH session) are unaffected either way. The console itself is otherwise locked
|
SSH session) are unaffected either way. The console itself is otherwise locked
|
||||||
down (kiosk autologin to a single locked browser; no getty shell on tty1;
|
down (kiosk autologin to a single locked browser; no getty shell on tty1;
|
||||||
`debug_shell` is lab-only and never shipped). If a future deployment wants the
|
the `debug_shell` back-door was removed from the template entirely, 2026-07-11).
|
||||||
|
If a future deployment wants the
|
||||||
hotkey disabled (e.g. a physically exposed terminal), re-select both rule IDs.
|
hotkey disabled (e.g. a physically exposed terminal), re-select both rule IDs.
|
||||||
|
|
||||||
## 14. Local SELinux policy module `chrony_dhcp_dispatcher` (added 2026-07-03)
|
## 14. Local SELinux policy module `chrony_dhcp_dispatcher` (added 2026-07-03)
|
||||||
@@ -214,7 +217,9 @@ turns app-allowlisting ON for the whole root at runtime for the first time**, so
|
|||||||
**BENCH-VERIFY HARD:** boot + kiosk render + admin SSH + sudo + TPM enrolment all
|
**BENCH-VERIFY HARD:** boot + kiosk render + admin SSH + sudo + TPM enrolment all
|
||||||
still work (nothing legit is untrusted — the 4 custom binaries + rpmdb are trusted),
|
still work (nothing legit is untrusted — the 4 custom binaries + rpmdb are trusted),
|
||||||
a novel `/tmp` binary is now **blocked** (EPERM), and no new AVCs (extend the `.te`
|
a novel `/tmp` binary is now **blocked** (EPERM), and no new AVCs (extend the `.te`
|
||||||
if any). `enable_stig=false` is the escape hatch if it regresses the boot/secure path.
|
if any). If it regresses the boot/secure path, waive the offending rule in the
|
||||||
|
tailoring as a justified deviation — STIG is enforced-true (no `enable_stig=false`
|
||||||
|
escape hatch, POA&M P-03).
|
||||||
|
|
||||||
**VERIFIED 2026-07-04 (build `fapolicyd-tmpfs-12043d3`, host `comet-beacon-54`) —
|
**VERIFIED 2026-07-04 (build `fapolicyd-tmpfs-12043d3`, host `comet-beacon-54`) —
|
||||||
enforcing, no false positives.** `fapolicyd active (running)`, `permissive = 0`,
|
enforcing, no false positives.** `fapolicyd active (running)`, `permissive = 0`,
|
||||||
@@ -289,6 +294,42 @@ boot; (b) no other chrony-log AVC (extend here if one appears); (c)
|
|||||||
**reproducibility** — `semodule -i` rebuilds the policy store, re-check verity hash
|
**reproducibility** — `semodule -i` rebuilds the policy store, re-check verity hash
|
||||||
stability (same concern as #14/#15). Only shipped when `enable_stig=true`.
|
stability (same concern as #14/#15). Only shipped when `enable_stig=true`.
|
||||||
|
|
||||||
|
## 17. STIG NTP-server rules unselected under `secure_ntp` (added 2026-07-11)
|
||||||
|
|
||||||
|
**Rules waived:** `chronyd_specify_remote_server`, `chronyd_or_ntpd_set_maxpoll`
|
||||||
|
(unselected in `diskless-tailoring.xml`; plus a belt-and-suspenders re-strip in
|
||||||
|
`2c-STIG-postfix`).
|
||||||
|
|
||||||
|
**Why:** these rules remediate by writing a **static, unauthenticated**
|
||||||
|
`server <pool> maxpoll 16` into `/etc/chrony.conf`. The fleet instead configures
|
||||||
|
chrony's time source **dynamically at runtime** — a **fleet-CA-authenticated NTS**
|
||||||
|
server derived from the DHCP domain (`secure_ntp=true`, RFC 8915) or DHCP option-42
|
||||||
|
(`false`) — via the NM dispatcher writing `sourcedir /run/chrony-dhcp`, with
|
||||||
|
`ntstrustedcerts`/`nocerttimecheck` baked. The STIG *intent* (time is synchronised,
|
||||||
|
and under NTS **authenticated**) is fully met; the rules only fail because they
|
||||||
|
look for a literal baked `server` line the fleet deliberately doesn't have.
|
||||||
|
|
||||||
|
**The security reason this is not just cosmetic (F-03/P-18):** the ordering is the
|
||||||
|
trap — `2c-STIG` runs **after** the `secure_ntp` chrony step, so an un-waived
|
||||||
|
`chronyd_specify_remote_server` **re-injects** a plain `server 0.us.pool.ntp.mil
|
||||||
|
maxpoll 16` that the earlier step already commented out. That plain server is a
|
||||||
|
**silent unauthenticated fallback**: with it present, chrony will happily sync
|
||||||
|
non-NTS time, so a DHCP/DNS/NTP spoofer could shift the clock — defeating the whole
|
||||||
|
point of NTS. Leaving it in means the host is **not fail-closed**. Observed on
|
||||||
|
`comet-beacon-54` 2026-07-11: NTS working (`chronyc authdata` cookies > 0) *and* the
|
||||||
|
STIG `0.us.pool.ntp.mil` line still in `chrony.conf` and syncing in parallel.
|
||||||
|
|
||||||
|
**Fix:** unselect the two rules (so remediation injects nothing) **and**, when
|
||||||
|
`secure_ntp=true`, re-comment any `server`/`pool` in `2c-STIG-postfix` (in case a
|
||||||
|
future rule adds one) so **only** the NTS `sourcedir` + `ntstrustedcerts` remain.
|
||||||
|
Under `secure_ntp=false` the re-strip does not run (the DHCP option-42 dispatcher
|
||||||
|
path needs the handed servers).
|
||||||
|
|
||||||
|
**BENCH-VERIFY:** on a clean secure_ntp build, `grep -E '^(server|pool)'
|
||||||
|
/etc/chrony.conf` returns nothing (only `sourcedir`/`ntstrustedcerts`), and
|
||||||
|
`chronyc -N authdata` shows the NTS source with cookies > 0 as the **only** selected
|
||||||
|
source (no `0.us.pool.ntp.mil` fallback). Only applies when `enable_stig=true`.
|
||||||
|
|
||||||
## Things that are NOT deviations (kept / enforced)
|
## Things that are NOT deviations (kept / enforced)
|
||||||
|
|
||||||
- **SELinux enforcing** (`selinux --enforcing`; labels preserved into the erofs).
|
- **SELinux enforcing** (`selinux --enforcing`; labels preserved into the erofs).
|
||||||
|
|||||||
@@ -1,16 +1,40 @@
|
|||||||
#!/bin/bash
|
#!/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
|
# nts (secure_ntp=true, default): AUTHENTICATED time. Derive the server NAME
|
||||||
# DHCP now (the network-manager dracut port), and NM exports the lease's NTP
|
# from the DHCP-provided domain as <PREFIX>.<domain> and write
|
||||||
# servers to dispatcher scripts as $DHCP4_NTP_SERVERS. chrony.conf carries
|
# `server NAME iburst nts`. chrony verifies the NTS-KE TLS cert against the
|
||||||
# `sourcedir /run/chrony-dhcp`, so writing <iface>.sources there and reloading
|
# fleet CA (chrony.conf: ntstrustedcerts). Because the domain comes from
|
||||||
# makes chronyd use the DHCP-provided server. The image's baked public pool is
|
# DHCP, a domain change needs only a new DHCP domain + reissued NTS cert —
|
||||||
# stripped (see the provisioner), so the host syncs ONLY from the lab NTP server
|
# no image rebuild. A DHCP/NTP/DNS spoofer can only DoS (no fleet-CA-signed
|
||||||
# — no time egress from the workstation.
|
# 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
|
# dhcp (secure_ntp=false): LEGACY, UNAUTHENTICATED. Write the DHCP option-42
|
||||||
# purpose: a hiccup here must never break networking.
|
# 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"
|
interface="$1"
|
||||||
action="$2"
|
action="$2"
|
||||||
@@ -20,17 +44,42 @@ case "$action" in
|
|||||||
*) exit 0 ;;
|
*) exit 0 ;;
|
||||||
esac
|
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
|
dir=/run/chrony-dhcp
|
||||||
file="$dir/${interface}.sources"
|
file="$dir/${interface}.sources"
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir"
|
||||||
|
|
||||||
if [ -n "${DHCP4_NTP_SERVERS:-}" ]; then
|
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"
|
: > "$file"
|
||||||
for s in $DHCP4_NTP_SERVERS; do
|
for s in $DHCP4_NTP_SERVERS; do
|
||||||
printf 'server %s iburst\n' "$s" >> "$file"
|
printf 'server %s iburst\n' "$s" >> "$file"
|
||||||
done
|
done
|
||||||
# Pick up the new sources without restarting chronyd.
|
|
||||||
chronyc reload sources >/dev/null 2>&1 || true
|
chronyc reload sources >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0
|
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_reboot" selected="false"/>
|
||||||
<xccdf:select idref="xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction" 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:Profile>
|
||||||
</xccdf:Tailoring>
|
</xccdf:Tailoring>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Lab NTP server (serving stack). Syncs from upstream and serves the boot LAN so
|
# Lab NTP server (serving stack). Syncs from upstream and serves the boot LAN so
|
||||||
# diskless workstations get time INTERNALLY (handed out as DHCP option 42 by
|
# diskless workstations get time INTERNALLY instead of egressing to the public
|
||||||
# dnsmasq) instead of egressing to the public pool. Only this host reaches the
|
# pool. Only this host reaches the internet for time; the workstations never do.
|
||||||
# internet for time; the workstations never do.
|
# With secure_ntp=true the workstations use NTS (below) and authenticate this
|
||||||
|
# server's identity before trusting its time.
|
||||||
|
|
||||||
# Upstream time source (this serving host has internet; the workstations don't).
|
# Upstream time source (this serving host has internet; the workstations don't).
|
||||||
pool pool.ntp.org iburst
|
pool pool.ntp.org iburst
|
||||||
@@ -10,6 +11,20 @@ pool pool.ntp.org iburst
|
|||||||
# to your subnet (e.g. `allow 192.168.50.0/24`) for anything less trusted.
|
# to your subnet (e.g. `allow 192.168.50.0/24`) for anything less trusted.
|
||||||
allow all
|
allow all
|
||||||
|
|
||||||
|
# ---- NTS (RFC 8915): CA-AUTHENTICATED time for the fleet -------------------
|
||||||
|
# The workstations verify THIS server's identity before trusting its time
|
||||||
|
# (secure_ntp=true in the image; closes F-03/P-18). The NTS-KE TLS cert is signed
|
||||||
|
# by the FLEET CA (step-ca), so it chains to the same radius-ca.pem the hosts
|
||||||
|
# already trust; its SAN must be the name the hosts use (ntp_server / NTP_HOST,
|
||||||
|
# default ntp.fleet.lab). Issue it with ./nts-cert.sh (mounted at /etc/chrony/nts).
|
||||||
|
# Without these files the server still answers plain NTP, but NTS clients get an
|
||||||
|
# "NTS-KE handshake" failure. NTS-KE listens on tcp/4460 — open it on the boot-LAN
|
||||||
|
# NIC (see docker-compose.yml), same trap as 123/udp. docs/secure-ntp-design.md.
|
||||||
|
ntsservercert /etc/chrony/nts/nts.crt
|
||||||
|
ntsserverkey /etc/chrony/nts/nts.key
|
||||||
|
# Persist NTS server keys/cookies across restarts (best-effort on the container fs).
|
||||||
|
ntsdumpdir /var/lib/chrony
|
||||||
|
|
||||||
# Serve our own clock at a high stratum if upstream is unreachable, so a diskless
|
# Serve our own clock at a high stratum if upstream is unreachable, so a diskless
|
||||||
# boot never blocks waiting for time.
|
# boot never blocks waiting for time.
|
||||||
local stratum 10
|
local stratum 10
|
||||||
|
|||||||
Executable
+73
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# nts-cert.sh — issue the NTS-KE server certificate for the fleet NTP server,
|
||||||
|
# signed by the fleet step-ca so booted hosts (secure_ntp=true) authenticate it
|
||||||
|
# against the SAME radius-ca.pem they already trust (closes F-03/P-18). Run once
|
||||||
|
# after the CA is up (serving/dot1x-bench bootstrap.sh), from serving/chrony/.
|
||||||
|
# Writes ./nts/{nts.crt,nts.key}, which docker-compose mounts at /etc/chrony/nts.
|
||||||
|
# Pairs with docs/secure-ntp-design.md. BENCH-VERIFY the `step` flags vs your version.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
OUT="nts"
|
||||||
|
# step-ca lives in the dot1x-bench compose project; reach it there (same pattern
|
||||||
|
# as dot1x-bench/step-ca/bootstrap.sh issuing the RADIUS cert).
|
||||||
|
BENCH="../dot1x-bench"
|
||||||
|
[ -d "$BENCH" ] || { echo "ERROR: $BENCH not found (need the step-ca compose project)." >&2; exit 1; }
|
||||||
|
[ -f "$BENCH/.env" ] && . "$BENCH/.env"
|
||||||
|
CA_PORT=${CA_PORT:-8443}
|
||||||
|
CA_CONNECT=${CA_CONNECT:-localhost}
|
||||||
|
CA_URL="https://${CA_CONNECT}:${CA_PORT}"
|
||||||
|
PROVISIONER=${PROVISIONER:-enrol}
|
||||||
|
# The DNS SAN the booted host uses. The image derives it at runtime as
|
||||||
|
# <ntp_prefix>.<DHCP-domain> (default ntp.<domain>), so this MUST equal
|
||||||
|
# ntp.${BOOT_DOMAIN} and resolve via the fleet dnsmasq (NTP_HOST -> SERVER_IP).
|
||||||
|
NTP_SAN=${NTP_HOST:-ntp.fleet.lab}
|
||||||
|
|
||||||
|
PKI="$BENCH/pki"
|
||||||
|
PW="$PKI/stepca/password"
|
||||||
|
[ -e "$PW" ] || { [ -e "$PKI/stepca/secrets/password" ] && PW="$PKI/stepca/secrets/password"; }
|
||||||
|
[ -r "$PW" ] || { echo "ERROR: CA password $PW missing — run dot1x-bench bootstrap.sh first." >&2; exit 1; }
|
||||||
|
CONTAINER_PW="/home/step/${PW#"$PKI/stepca/"}"
|
||||||
|
|
||||||
|
incca() { ( cd "$BENCH" && docker compose exec -T step-ca "$@" ); }
|
||||||
|
|
||||||
|
echo "==> issuing NTS-KE server cert (CN/SAN ${NTP_SAN}) from the fleet CA"
|
||||||
|
incca mkdir -p /export/nts
|
||||||
|
# Leaf with serverAuth (default) + the NTS server name as SAN. --not-after within
|
||||||
|
# the CA's max claim (bootstrap.sh raised it to 2160h).
|
||||||
|
incca step ca certificate "${NTP_SAN}" \
|
||||||
|
/export/nts/nts.crt /export/nts/nts.key \
|
||||||
|
--ca-url "$CA_URL" --root /export/root_ca.crt \
|
||||||
|
--provisioner "${PROVISIONER}" --provisioner-password-file "$CONTAINER_PW" \
|
||||||
|
--not-after 2160h --force
|
||||||
|
|
||||||
|
# The CA wrote into $BENCH/pki/nts via the /export mount; copy to ./nts for the
|
||||||
|
# ntp container's bind mount.
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
cp "$PKI/nts/nts.crt" "$OUT/nts.crt"
|
||||||
|
cp "$PKI/nts/nts.key" "$OUT/nts.key"
|
||||||
|
chmod 0644 "$OUT/nts.crt"
|
||||||
|
|
||||||
|
# The ntp container's chronyd runs as a non-root 'chrony' user (post-PRIVDROP) —
|
||||||
|
# NOT the host user that wrote this key. So `0640` owned by the host user is
|
||||||
|
# UNREADABLE inside the container ("Missing read access ... : Permission denied" ->
|
||||||
|
# "Could not set credentials"), and every NTS-KE TLS handshake then fails with
|
||||||
|
# "The TLS connection was non-properly terminated" (bench 2026-07-11 on
|
||||||
|
# comet-beacon-54). `0644` works but chrony warns "World-readable permissions". The
|
||||||
|
# right answer: chown the key to the container's chrony uid + keep 0640. The ntp
|
||||||
|
# service is in the PARENT compose project (serving/), so query it from there.
|
||||||
|
chmod 0640 "$OUT/nts.key"
|
||||||
|
NTP_DIR="$(cd .. 2>/dev/null && pwd)"
|
||||||
|
CHRONY_UID="$(cd "$NTP_DIR" 2>/dev/null && docker compose exec -T ntp sh -c 'awk -F: "/chrony/{print \$3; exit}" /etc/passwd' 2>/dev/null | tr -d '\r ' || true)"
|
||||||
|
if [ -n "$CHRONY_UID" ]; then
|
||||||
|
chown "$CHRONY_UID" "$OUT/nts.key" 2>/dev/null || sudo chown "$CHRONY_UID" "$OUT/nts.key"
|
||||||
|
echo " NTS key owned by the ntp container's chrony uid ($CHRONY_UID), mode 0640."
|
||||||
|
else
|
||||||
|
chmod 0644 "$OUT/nts.key"
|
||||||
|
echo " WARNING: couldn't determine the ntp container's chrony uid (is the ntp"
|
||||||
|
echo " service running?). Left the key 0644 (world-readable) so chrony"
|
||||||
|
echo " can read it — chown it to the chrony uid + 0640 for production."
|
||||||
|
fi
|
||||||
|
echo "==> wrote $OUT/nts.crt + $OUT/nts.key"
|
||||||
|
echo " Ensure the image's ntp_server = ${NTP_SAN}, dnsmasq resolves ${NTP_SAN},"
|
||||||
|
echo " open tcp/4460 (NTS-KE) on the boot NIC, and restart the ntp service."
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# NTS-KE server cert/key (fleet-signed, per-environment) — issued by ../nts-cert.sh.
|
||||||
|
# Never commit key material or environment CA-signed certs (see CLAUDE.md invariant).
|
||||||
|
nts.crt
|
||||||
|
nts.key
|
||||||
@@ -91,6 +91,11 @@ AUDIT_HOST=audit.fleet.lab
|
|||||||
# *.fleet.lab like the boot/nvme/audit names. Must match the agent server cert SAN
|
# *.fleet.lab like the boot/nvme/audit names. Must match the agent server cert SAN
|
||||||
# (server-pki.sh AGENT_SANS).
|
# (server-pki.sh AGENT_SANS).
|
||||||
MGMT_HOST=manager.fleet.lab
|
MGMT_HOST=manager.fleet.lab
|
||||||
|
# NTS-authenticated NTP server name (secure_ntp). Baked into the image's
|
||||||
|
# chrony.conf (Packer var ntp_server) so every host syncs authenticated time here;
|
||||||
|
# resolved per-environment to this serving host. MUST match the NTS server cert SAN
|
||||||
|
# issued by serving/chrony/nts-cert.sh.
|
||||||
|
NTP_HOST=ntp.fleet.lab
|
||||||
|
|
||||||
# --- NVMe/TCP target -------------------------------------------------------
|
# --- NVMe/TCP target -------------------------------------------------------
|
||||||
# Per-version subsystem NQN base. The long-lived target serves each rootfs
|
# Per-version subsystem NQN base. The long-lived target serves each rootfs
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ services:
|
|||||||
# Fleet-manager AGENT plane (:9600) — baked into the image so enrolled hosts
|
# Fleet-manager AGENT plane (:9600) — baked into the image so enrolled hosts
|
||||||
# dial the control plane by name; resolved here like the other fleet names.
|
# dial the control plane by name; resolved here like the other fleet names.
|
||||||
- --address=/${MGMT_HOST}/${SERVER_IP}
|
- --address=/${MGMT_HOST}/${SERVER_IP}
|
||||||
|
# NTS-authenticated NTP server name (secure_ntp): baked into the image's
|
||||||
|
# chrony.conf; must match the NTS server cert SAN. Resolved to this host.
|
||||||
|
- --address=/${NTP_HOST}/${SERVER_IP}
|
||||||
# Kiosk is LOCAL-ONLY (loads boot.fleet.lab), so sinkhole Chromium's
|
# Kiosk is LOCAL-ONLY (loads boot.fleet.lab), so sinkhole Chromium's
|
||||||
# call-home to 0.0.0.0 — telemetry can't leave the lab even if the browser
|
# call-home to 0.0.0.0 — telemetry can't leave the lab even if the browser
|
||||||
# tries. Defense-in-depth behind the in-image Chromium policy/flags; in
|
# tries. Defense-in-depth behind the in-image Chromium policy/flags; in
|
||||||
@@ -122,9 +125,14 @@ services:
|
|||||||
- --dhcp-range=${DHCP_RANGE_START},${DHCP_RANGE_END},${DHCP_LEASE}
|
- --dhcp-range=${DHCP_RANGE_START},${DHCP_RANGE_END},${DHCP_LEASE}
|
||||||
- --dhcp-option=option:router,${GATEWAY_IP}
|
- --dhcp-option=option:router,${GATEWAY_IP}
|
||||||
- --dhcp-option=option:dns-server,${SERVER_IP}
|
- --dhcp-option=option:dns-server,${SERVER_IP}
|
||||||
# NTP server (option 42): point diskless hosts at the lab 'ntp' service
|
# Domain name (option 15): the image derives its NTS NTP server name at
|
||||||
# below so they sync time internally and never reach the public NTP pool.
|
# runtime as ntp.<this-domain> (secure_ntp — no baked FQDN), so hosts MUST
|
||||||
# The image's NM dispatcher (10-chrony-dhcp-ntp) feeds this into chrony.
|
# receive the domain. NM exports it to the dispatcher as $DHCP4_DOMAIN_NAME.
|
||||||
|
# Explicit (not just --domain) so the option is reliably sent + matches the
|
||||||
|
# NTS cert SAN (ntp.${BOOT_DOMAIN}) and the dnsmasq --address for NTP_HOST.
|
||||||
|
- --dhcp-option=option:domain-name,${BOOT_DOMAIN}
|
||||||
|
# NTP server (option 42): used only in the LEGACY unauthenticated path
|
||||||
|
# (secure_ntp=false). With NTS the image ignores this and dials ntp.<domain>.
|
||||||
- --dhcp-option=option:ntp-server,${SERVER_IP}
|
- --dhcp-option=option:ntp-server,${SERVER_IP}
|
||||||
# ---- Per-host names (creative starship hostnames, server-assigned) ----
|
# ---- Per-host names (creative starship hostnames, server-assigned) ----
|
||||||
# The workstation image is hostname-neutral; naming is done HERE. fleet-namer
|
# The workstation image is hostname-neutral; naming is done HERE. fleet-namer
|
||||||
@@ -220,15 +228,20 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
HASS_URL: ${HASS_URL}
|
HASS_URL: ${HASS_URL}
|
||||||
HASS_TOKEN: ${HASS_TOKEN}
|
HASS_TOKEN: ${HASS_TOKEN}
|
||||||
# --- NTP server (serves time to the boot LAN; handed out via DHCP opt 42) ---
|
# --- NTP server (serves time to the boot LAN; NTS-authenticated for secure_ntp) ---
|
||||||
ntp:
|
ntp:
|
||||||
build: ./chrony
|
build: ./chrony
|
||||||
network_mode: host # serve NTP on udp/123 across the boot LAN
|
network_mode: host # serve NTP on udp/123 (+ NTS-KE tcp/4460) across the boot LAN
|
||||||
cap_add:
|
cap_add:
|
||||||
- SYS_TIME # discipline the (host-shared) clock it serves
|
- SYS_TIME # discipline the (host-shared) clock it serves
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./chrony/chrony.conf:/etc/chrony/chrony.conf:ro
|
- ./chrony/chrony.conf:/etc/chrony/chrony.conf:ro
|
||||||
|
# NTS-KE server cert/key (fleet-signed) so booted hosts CA-AUTHENTICATE the
|
||||||
|
# time source (secure_ntp; F-03/P-18). Issue with ./chrony/nts-cert.sh.
|
||||||
|
# If the dir is empty the server still answers plain NTP; NTS clients then
|
||||||
|
# fail the KE handshake — so run nts-cert.sh before shipping secure_ntp images.
|
||||||
|
- ./chrony/nts:/etc/chrony/nts:ro
|
||||||
# NOTE: with host networking + SYS_TIME this chronyd disciplines the Docker
|
# NOTE: with host networking + SYS_TIME this chronyd disciplines the Docker
|
||||||
# HOST clock (the clock isn't namespaced) — intended: the serving host is the
|
# HOST clock (the clock isn't namespaced) — intended: the serving host is the
|
||||||
# lab time source. Drop SYS_TIME (and add `chronyd -x`) if the host already
|
# lab time source. Drop SYS_TIME (and add `chronyd -x`) if the host already
|
||||||
@@ -236,11 +249,14 @@ services:
|
|||||||
#
|
#
|
||||||
# FIREWALL (easy to miss — same trap as the audit collector's 514/tcp): host
|
# FIREWALL (easy to miss — same trap as the audit collector's 514/tcp): host
|
||||||
# networking means Docker does NOT open the port; the serving host's own
|
# networking means Docker does NOT open the port; the serving host's own
|
||||||
# firewall governs 123/udp. Without a rule, workstations get 192.168.50.10 as
|
# firewall governs the NTP ports. Without a rule, workstations get 192.168.50.10
|
||||||
# their NTP server (DHCP opt 42) but their packets are dropped — chrony logs
|
# as their NTP server but their packets are dropped — chrony logs
|
||||||
# "Added source … / unreachable" and never syncs. Open it on the boot-LAN NIC:
|
# "Added source … / unreachable" and never syncs. Open BOTH on the boot-LAN NIC:
|
||||||
|
# udp/123 = NTP; tcp/4460 = NTS-KE (secure_ntp key establishment)
|
||||||
# ufw: sudo ufw allow in on <boot-nic> to any port 123 proto udp comment 'fleet NTP'
|
# ufw: sudo ufw allow in on <boot-nic> to any port 123 proto udp comment 'fleet NTP'
|
||||||
# firewalld: sudo firewall-cmd --permanent --add-service=ntp && sudo firewall-cmd --reload
|
# sudo ufw allow in on <boot-nic> to any port 4460 proto tcp comment 'fleet NTS-KE'
|
||||||
|
# firewalld: sudo firewall-cmd --permanent --add-service=ntp \
|
||||||
|
# && sudo firewall-cmd --permanent --add-port=4460/tcp && sudo firewall-cmd --reload
|
||||||
|
|
||||||
# --- Remote audit / log collector (RECEIVE side) ---------------------------
|
# --- Remote audit / log collector (RECEIVE side) ---------------------------
|
||||||
# rsyslog receiver: booted diskless hosts (whose RAM-only overlay root loses
|
# rsyslog receiver: booted diskless hosts (whose RAM-only overlay root loses
|
||||||
|
|||||||
Reference in New Issue
Block a user