14 KiB
STIG deviation register — diskless fleet image
DISA STIG (SSG xccdf_org.ssgproject.content_profile_stig for RHEL 9) is applied
to the image via oscap --remediate with a tailoring file
(../files/stig/diskless-tailoring.xml).
This register documents every intentional deviation — the rules unselected in
that tailoring, plus posture decisions — with the rationale and compensating
control. The per-build generated report
(stig-deviations.md / stig-report.html, produced by
../scripts/stig-deviations.sh from the
post-remediation scan) cross-references this file: any non-pass rule not
listed here is flagged REVIEW.
Posture decisions (2026-06-18, lab phase):
- FIPS: OFF — revisit alongside UKI signing / Secure Boot.
- Audit offload: LOCAL-only — auditd runs and logs to the tmpfs overlay; no remote collector yet.
- CI: report-only —
oscap evalpublishes ARF + HTML; never fails the build.
Intentional deviations
| # | STIG/SSG area (rules) | Why it doesn't apply / can't be met | Compensating control |
|---|---|---|---|
| 1 | Separate partitions + their mount options for /var, /var/log, /var/log/audit, /tmp, /home, /var/tmp, /dev/shm (partition_for_*, mount_option_*) |
Root is a single immutable erofs image on NVMe/TCP with a tmpfs volatile overlay — there is no local disk to partition, and these dirs are not separate mounts. The related mount_option_* rules will therefore show as findings; they are covered by this entry. |
The entire root is read-only (erofs+dm-verity) and the writable layer is tmpfs (RAM), which structurally bounds the tampering / space-exhaustion risks the partition+option rules target. noexec on /tmp is intentionally NOT forced — the browser kiosk uses /tmp for its profile/cache and can break under noexec. |
| 2 | Bootloader / GRUB (grub2_*: password, admin user, and the *_argument kernel-param rules) |
Boot is a UKI (systemd-stub) over UEFI HTTP boot — there is no GRUB. | The STIG kernel params (audit=1, audit_backlog_limit, slub_debug=P, page_poison=1, vsyscall=none, init_on_alloc/free) are baked into the signed UKI cmdline (../scripts/extract-artifacts.sh); the cmdline is immutable once signed (Secure Boot covers it), which is stronger than an editable GRUB config. |
| 3 | AIDE / file integrity (package_aide_installed, aide_build_database, aide_periodic_cron_checking, aide_scan_notification, aide_verify_*) |
The root is read-only and cryptographically verified; an on-disk AIDE db would also be non-deterministic and break the reproducible verity hash. | dm-verity verifies the whole root filesystem against a signed root hash on every read — continuous, hardware-backed integrity that supersedes AIDE's periodic scan. |
| 4 | Firewall (service_firewalld_enabled, package_firewalld_installed, set_firewalld_default_zone) |
Currently firewall --disabled; auto-enabling firewalld/nftables risks the NVMe/TCP (:4420) and NetworkManager paths during bring-up. |
Isolated boot LAN; egress controlled at the serving-stack / network firewall layer. Future hardening — re-enable with explicit allow rules once the secure path is verified. |
| 5 | FIPS (enable_fips_mode, grub2_enable_fips_mode, package_dracut-fips_installed) |
Decision: off for the lab phase (interacts with the still-unsigned UKI / Secure Boot story). | Revisit with signing. No weakened crypto is configured in the meantime. |
| 6 | Remote audit offload (auditd_audispd_configure_remote_server, ..._encrypt_sent_records, ..._network_failure_action, rsyslog_remote_loghost) + rsyslog TLS offload (rsyslog_encrypt_offload_defaultnetstreamdriver, ..._actionsendstreamdrivermode, ..._actionsendstreamdriverauthmode) |
Offload is done via rsyslog over TCP (simpler, container-friendly receiver); the audisp-remote native-protocol rules stay waived. The TLS-offload rules are waived because the isolated lab boot LAN is plaintext (mirrors HTTP boot / no MACsec) and applying them forces gtls + x509 peer auth + a CA file, which breaks the plaintext 2f forward (no CA, plaintext :514). |
Wired both ends, plaintext. Sender (image, provisioner 2f, audit_host): audisp-syslog → LOCAL6 + rsyslog omfwd (disk-assisted queue) → host-generic audit.fleet.lab. Receiver: serving/audit-collector/ (rsyslog TCP/514, per-source-host files). No per-host state; isolated LAN. Production (tracked in docs/TODO.md #2): issue the collector a TLS cert, bundle the CA in the image, switch 2f + the collector to gtls/6514, then re-select the three rsyslog_encrypt_offload_* rules (drop the waivers) so they pass. |
| 7 | Smart card login (require_smart_card_login, sssd_enable_smartcard, install_smartcard_packages) |
Diskless hosts have no smartcard reader; admin access is SSH key-based. Enforcing smartcard login would lock the host out. | Admin auth is an SSH key-only account; root is locked; password auth disabled. |
| 8 | NOPASSWD sudo (sudo_remove_nopasswd) |
The baked admin is a passwordless, key-only account, so sudo cannot prompt for a password — NOPASSWD is required for it to function at all. |
Single fleet-generic admin, key-only, wheel; no interactive password exists to protect. Tracked for the eventual per-host/rotatable access model (SSH CA / config-agent). |
| 9 | GUI / autologin / GDM (gnome screensaver, GDM banner, automatic-login rules) | No GNOME/GDM is installed; the kiosk is an intentional autologin single-app (weston + Chromium) session on kiosk builds. | Kiosk user is unprivileged and passwordless; the session runs a single locked-down browser (no shell, no nav). Applies to kiosk builds only; non-kiosk builds don't install the GUI stack. |
| 10 | Password aging / faillock on the key-only APPLIANCE ACCOUNTS (accounts_*_age_*, account_disable_post_pw_expiration, pam_faillock account phase) — applied to the system but exempted for the fleetadmin and kiosk users |
These two accounts are passwordless / key-only. The hardened PAM account phase (faillock + password aging/inactivity) treats a passwordless account as expired/lockable, which locked the fleet out: the kiosk autologin session never opened (getty kept tty1) and sshd denied the valid key with Permission denied (publickey) (pubkey auth succeeds, then pam_acct_mgmt fails). Fixed 2026-06-18. |
The exemption is scoped to these two appliance accounts only (post-STIG chage -M -1 -m 0 -I -1 -E -1 + faillock --reset; kiosk uses a dedicated /etc/pam.d/kiosk; sshd gets an account sufficient pam_succeed_if.so user = fleetadmin bypass as its first account rule). Every other account keeps the full STIG password policy + faillock. These accounts have no password to age and (key-only, PasswordAuthentication no) no password-auth path for faillock to protect. Folds into the per-host/rotatable access model (SSH CA / config-agent). |
| 11 | Login banner content (banner_etc_issue, banner_etc_issue_net, banner_etc_motd) | STIG requires the verbatim DoD/USG "you are accessing a U.S. Government Information System" text. This is a fictional starship fleet, not a USG system, so /etc/issue{,.net} + /etc/motd carry the fleet (ISV AURELIAN) notice instead (provisioner step 2e, files/banner/fleet-issue). The content rules therefore fail by design. | The banner keeps its security function: it still states authorized-crew-only, that the terminal is monitored/logged, and that unauthorized access is prohibited — only the wording is themed. sshd_enable_warning_banner (Banner is shown) still passes. |
12. Report triage 2026-06-19 (scan app-9f7d8d1)
Triage of the 21 REVIEW rows from the post-fix hardened scan (398 pass / 12 fail
/ 9 notchecked — a valid scan). All but one are dispositioned below; the rule IDs
are unselected in diskless-tailoring.xml (or made to pass / N/A) so they no
longer surface as REVIEW. Remaining open: selinux_context_elevation_for_sudo
(see end).
Waived — covered by an existing register entry (Bucket A):
encrypt_partitions,auditd_audispd_configure_sufficiently_large_partition— single erofs+tmpfs root, no partitions (#1).grub2_password— UKI, no GRUB (#2).sysctl_crypto_fips_enabled,harden_sshd_macs_openssh_conf_crypto_policy,harden_sshd_macs_opensshserver_conf_crypto_policy— FIPS off (#5).configured_firewalld_default_deny— firewall off for the lab boot LAN (#4).sssd_enable_certmap,sssd_has_trust_anchor— no SSSD/PKI/smartcard; local key-only host, no domain join (#7).
Waived — architecturally absent, N/A (Bucket B): account_temp_expire_date
(no temporary accounts), accounts_user_home_paths_only (appliance accounts,
fixed home layout), libreswan_approved_tunnels (no IPsec/libreswan),
security_patches_up_to_date (rebuild-from-current model — a scanner can't assess
patch currency against a future rebuild), ssh_keys_passphrase_protected (no
private keys stored on the host; host keys regenerate per boot).
Waived — decisions (Bucket C):
installed_OS_is_vendor_supported— Rocky is vendor-supported; the rule fails only because the RHEL datastream's CPE checks for an RHEL subscription.network_configure_name_resolution— single DHCP-provided resolver by design on the boot LAN; the multi-DNS requirement is N/A to the netboot model.sysctl_user_max_user_namespaces_no_remediation— KEEP user namespaces enabled: the Chromium kiosk sandbox requires them. Disabling userns would break the kiosk.accounts_authorized_local_users— the authorized-users check doesn't know the key-only appliance accounts (fleetadmin,kiosk); they are authorized (see #10). Production note: prefer setting the SSG authorized-users regex variable to assert these two explicitly rather than waiving the whole check.package_postfix_installed— postfix removed (provisioner 2c-STIG): the appliance sends no mail and ships audit off-box via rsyslog (2f), so a local MTA is dead attack surface. This also makespostfix_prevent_unrestricted_relayN/A. Audit-failure notification relies on the remote offload, not email (auditd ... action_mail_acctstays configured but is not the alerting path).
Remediated to pass (not waived):
configure_usbguard_auditbackend+usbguard_allow_hid/usbguard_allow_hub/usbguard_allow_hid_and_hub— decision: ENABLE USBGuard with enforcement. usbguard was already installed + enabled, but its policy is generated on the build VM (no real USB), so under the default block target a physical console keyboard/mouse would be blocked. The provisioner now setsAuditBackend=LinuxAuditand writes a hardware-independent HID + hub allowlist (allow with-interface match-all { 03:*:* },{ 09:00:* }, and the composite{ 03:*:* 09:00:* }) so console input survives while mass storage and other device classes are blocked. ⚠ BENCH-VERIFY physical console input on real hardware — if a platform's input device doesn't match the HID/hub classes, fall back to a per-platform allow rule or re-waiveconfigure_usbguard_auditbackend.
Remediated with confinement (decision 2026-06-20) — selinux_context_elevation_for_sudo:
SSG ships no automated fix; the STIG manual remediation
(... TYPE=sysadm_t ROLE=sysadm_r ...) requires the admin to reach the
sysadm_r role, which unconfined_u cannot. So the provisioner (2c-STIG) now
(1) rewrites the admin sudoers rule to <admin> ALL=(ALL) TYPE=sysadm_t ROLE=sysadm_r NOPASSWD:ALL (keeps NOPASSWD — the admin is key-only, #8 — and is
what the OVAL check matches), and (2) maps the admin to the confined staff_u
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
by the ssh_sysadm_login boolean. ⚠ Two bench gates before trusting: (a)
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
reverts to a justified deviation; (b) reproducibility — semanage rebuilds the
SELinux policy store, so confirm the verity hash is stable across two builds (and
normalize in 2d hygiene if it drifts).
Things that are NOT deviations (kept / enforced)
- SELinux enforcing (
selinux --enforcing; labels preserved into the erofs). - fapolicyd kept enabled (NOT waived) — it's the real exec-allowlist guard
for the writable RAM overlay, which dm-verity does not cover. The only
adjustment: the one non-RPM executable,
/usr/local/bin/kiosk-session, is added to fapolicyd's trust DB at build (provisioner 2c-STIG-postfix) so the kiosk runs; everything else stays RPM-trust-only. (fapolicyd blocking that binary was the original kiosk-won't-start cause.) - auditd enabled; STIG audit rules applied; logs locally.
- USBGuard enabled + enforcing (
blocktarget) with a HID/hub allowlist andAuditBackend=LinuxAudit(see #12) — blocks USB mass storage / unknown device classes at the console while keeping keyboard/mouse working. - sshd hardening: root login off, password & keyboard-interactive auth off,
key-only (drop-in
50-fleet-hardening.conf); STIG ciphers/MACs/KEX applied. - No per-host state: SSH host keys stripped at build, regenerated at first
boot into the overlay;
machine-idempty. - Password-policy / faillock rules remain selected and applied system-wide (kept honest in the report); they are exempted only for the two key-only appliance accounts — see deviation #10.
How to read the generated report
scripts/stig-deviations.sh stig-arf.xml emits a table of every rule whose
result is not pass. Each row is tagged:
- intentional — matches an unselected rule / area in this register.
- REVIEW — a real finding to triage (fix it, or add a justified entry here).