diff --git a/diskless.pkr.hcl b/diskless.pkr.hcl index f7b1fec..ac8cde7 100644 --- a/diskless.pkr.hcl +++ b/diskless.pkr.hcl @@ -142,6 +142,12 @@ variable "debug_shell" { default = true } +variable "audit_host" { + type = string + description = "Remote rsyslog/audit collector the image forwards auditd (via audisp-syslog) + the journal (rsyslog imjournal) to. Host-generic DNS name resolved per-environment by the serving dnsmasq to the collector (serving/audit-collector); matches serving/.env AUDIT_HOST. The diskless root is a RAM-only overlay, so local logs die on reboot — this ships them off-box. Empty = local-only (no remote offload)." + default = "audit.fleet.lab" +} + variable "sb_key" { type = string description = "Secure Boot signing key (PEM) for the UKI. Empty = unsigned lab build. Production: CI/HSM-held; never enters the build VM." @@ -586,6 +592,35 @@ build { ] } + # --- 2f. Remote log / audit offload (sender) ----------------------------- + # Forward auditd (via the audisp-syslog plugin, on LOCAL6) AND the journal + # (rsyslog's stock imjournal) to the remote collector (serving/audit-collector). + # The diskless root is a RAM-only overlay so local logs vanish on reboot; this + # ships them off-box (and gives remote visibility into boot/login failures). + # Host-generic: targets ${audit_host} (DNS, resolved per environment). Empty + # audit_host => local-only. Implements the remote-offload half of TODO #2 / + # docs/stig-deviations.md #6. Disk-assisted queue so a network blip won't drop + # records (action.resumeRetryCount=-1 retries forever). + provisioner "shell" { + environment_vars = ["AUDIT_HOST=${var.audit_host}"] + inline = [ + "set -eux", + "if [ -z \"$AUDIT_HOST\" ]; then echo 'audit_host empty -> local-only logging, no remote offload.'; exit 0; fi", + "dnf install -y rsyslog", + # auditd -> syslog on LOCAL6 (the collector routes LOCAL6 to a per-host + # audit.log; see serving/audit-collector/rsyslog.conf). + "if [ -f /etc/audit/plugins.d/syslog.conf ]; then sed -i -e 's/^active.*/active = yes/' -e 's/^args.*/args = LOG_INFO LOG_LOCAL6/' /etc/audit/plugins.d/syslog.conf; fi", + # rsyslog -> collector over TCP, disk-assisted queue. + "cat > /etc/rsyslog.d/60-fleet-remote.conf </dev/null || true", + ] + } + # --- 2d. Final image hygiene: strip caches/logs after the LAST dnf step -- # MUST be the last provisioner. The `dnf clean all` in step 2 runs BEFORE the # kiosk (2b) and admin (2c) provisioners, and 2b enables EPEL + installs diff --git a/docs/TODO.md b/docs/TODO.md index a144ca0..358b30e 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -107,10 +107,12 @@ keeps full STIG. Justified as register deviation #10. **Bench-verify on rebuild: kiosk renders + admin SSH works with hardening on.** Remaining (future hardening, currently waived — see the register): FIPS, -firewall, per-host/rotatable admin access. **Remote audit offload — receiver -DONE** (`serving/audit-collector/`, rsyslog over TCP/514, per-source-host files; -DNS name `${AUDIT_HOST}`); the **image-side sender** (audisp-syslog + rsyslog -forward to `audit.fleet.lab`) is still to wire — see that dir's README. +firewall, per-host/rotatable admin access. **Remote audit offload — WIRED both +ends** (2026-06-19): receiver `serving/audit-collector/` (rsyslog TCP/514, +per-source-host files) + sender in the image (provisioner 2f, `audit_host` var: +audisp-syslog→LOCAL6 + rsyslog `omfwd` to `audit.fleet.lab`). Lab is plaintext; +TLS/RELP is the remaining hardening. **Bench-verify:** booted host's auditd AVCs +land in `audit-collector/logs//audit.log` and journal in `messages.log`. ### Original spec (retained for reference) diff --git a/docs/stig-deviations.md b/docs/stig-deviations.md index 1073538..1f3f5ec 100644 --- a/docs/stig-deviations.md +++ b/docs/stig-deviations.md @@ -26,7 +26,7 @@ Posture decisions (2026-06-18, lab phase): | 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`) | Decision: local-only for now; image not yet forwarding. | auditd enabled and recording locally. **Receiver now deployed** — `serving/audit-collector/` (rsyslog over TCP/514, per-source-host files, DNS name `${AUDIT_HOST}`). **Remaining hardening** — wire the image-side sender (audisp-syslog + rsyslog forward to the host-generic `audit.fleet.lab`, no per-host state); then this row's `rsyslog_remote_loghost` should pass. | +| 6 | **Remote audit offload** (`auditd_audispd_configure_remote_server`, `..._encrypt_sent_records`, `..._network_failure_action`, `rsyslog_remote_loghost`) | The SSG `audisp-remote` native-protocol rules stay waived; offload is done via **rsyslog over TCP** instead (simpler, container-friendly receiver). Lab uses **plaintext** (mirrors the HTTP-boot stack); encryption/failure-action rules remain deviated until TLS/RELP. | **Now wired both ends.** Sender (image, provisioner 2f, `audit_host` var): audisp-syslog → LOCAL6 + rsyslog `omfwd` (disk-assisted queue) to the host-generic `audit.fleet.lab`. Receiver: `serving/audit-collector/` (rsyslog TCP/514, per-source-host files). No per-host state. **Future**: TLS (`gtls`) or RELP+TLS + a real collector — then the encrypt/failure-action rules can 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. |