security: version the CIS Tier-A hardening pass #11

Merged
l.kirchner merged 1 commits from security/cis-tierA-hardening into main 2026-08-19 11:08:14 +02:00
Owner

Versions the CIS Tier-A hardening pass that has been running on twelve containers since 2026-07-24.

Why

The script existed only as /root/cis-tierA.sh — a root-owned copy on each machine it hardens. Nothing referenced it, nothing reviewed it, and a change would have had to be made twelve times. It was found only by searching for the string that caused a support case (below).

What it does

SSH drop-in, login.defs aging + YESCRYPT, pwquality/faillock, PAM (pwquality, pwhistory, faillock, nullok removed) and auditd rules. Idempotent, backs everything up to /root/cis-hardening-backup-<ts>/, gates the SSH restart on sshd -t, rolls PAM back when a referenced module is missing, and probes whether auditd works at all in an unprivileged LXC (it does not — the host owns the audit subsystem — so the package is removed again).

Two substantive changes over the deployed copy

1. Forwarding is denied per option, not via DisableForwarding.

Same effect, different diagnosability. DisableForwarding overrides every other forwarding option and does not appear in sshd -T. The symptom this produced:

$ ssh -L 9749:127.0.0.1:9749 lutz@luki-dev
channel 3: open failed: administratively prohibited: open failed

$ sudo sshd -T | grep -i forwarding
allowtcpforwarding yes          <- and yet every forward is refused

A configuration that reports itself as permissive while refusing the operation is expensive to diagnose. The four explicit options report honestly.

2. MODDIR command substitution quoted (SC2046, the only shellcheck finding in the file).

Host-specific exceptions

Section 1 rewrites 99-cis-hardening.conf wholesale on every run, so exceptions must not live in it. They belong in a drop-in that sorts after it — 99-zz-local-forward.conf on luki-dev, granting one user a local forward to one loopback port. Verified by simulating a rerun: the patched heredoc was written into a copy of the configuration and checked with sshd -t -f / sshd -T -C user=... -f; the exception survives. Header and README both record that a Match block extends until the next Match, across Include file boundaries, so such a drop-in has to stay alphabetically last.

Checks

bash -n clean, tests/check_ct_source.sh and tests/test_validation.sh pass (50/50), shellcheck -S warning -e SC1090,SC1091 install/cis-tierA.sh clean. Note that the existing scripts do report shellcheck findings — the CI step is currently skipped because shellcheck is not on the runner (K-114). Not addressed here.

Not in scope

The eleven other containers still carry the deployed copy with DisableForwarding. That is correct as long as only luki-dev needs a tunnel; rolling the new version out is a separate, deliberate step. The PVE hosts and the Wazuh manager VM stay unhardened by design — they are the break-glass foundation.

Versions the CIS Tier-A hardening pass that has been running on twelve containers since 2026-07-24. ## Why The script existed only as `/root/cis-tierA.sh` — a root-owned copy on each machine it hardens. Nothing referenced it, nothing reviewed it, and a change would have had to be made twelve times. It was found only by searching for the string that caused a support case (below). ## What it does SSH drop-in, `login.defs` aging + YESCRYPT, pwquality/faillock, PAM (pwquality, pwhistory, faillock, `nullok` removed) and auditd rules. Idempotent, backs everything up to `/root/cis-hardening-backup-<ts>/`, gates the SSH restart on `sshd -t`, rolls PAM back when a referenced module is missing, and probes whether auditd works at all in an unprivileged LXC (it does not — the host owns the audit subsystem — so the package is removed again). ## Two substantive changes over the deployed copy **1. Forwarding is denied per option, not via `DisableForwarding`.** Same effect, different diagnosability. `DisableForwarding` overrides every other forwarding option *and* does not appear in `sshd -T`. The symptom this produced: ``` $ ssh -L 9749:127.0.0.1:9749 lutz@luki-dev channel 3: open failed: administratively prohibited: open failed $ sudo sshd -T | grep -i forwarding allowtcpforwarding yes <- and yet every forward is refused ``` A configuration that reports itself as permissive while refusing the operation is expensive to diagnose. The four explicit options report honestly. **2. `MODDIR` command substitution quoted** (SC2046, the only shellcheck finding in the file). ## Host-specific exceptions Section 1 rewrites `99-cis-hardening.conf` wholesale on every run, so exceptions must not live in it. They belong in a drop-in that sorts *after* it — `99-zz-local-forward.conf` on luki-dev, granting one user a local forward to one loopback port. Verified by simulating a rerun: the patched heredoc was written into a copy of the configuration and checked with `sshd -t -f` / `sshd -T -C user=... -f`; the exception survives. Header and README both record that a `Match` block extends until the next `Match`, across `Include` file boundaries, so such a drop-in has to stay alphabetically last. ## Checks `bash -n` clean, `tests/check_ct_source.sh` and `tests/test_validation.sh` pass (50/50), `shellcheck -S warning -e SC1090,SC1091 install/cis-tierA.sh` clean. Note that the existing scripts do report shellcheck findings — the CI step is currently skipped because shellcheck is not on the runner (K-114). Not addressed here. ## Not in scope The eleven other containers still carry the deployed copy with `DisableForwarding`. That is correct as long as only luki-dev needs a tunnel; rolling the new version out is a separate, deliberate step. The PVE hosts and the Wazuh manager VM stay unhardened by design — they are the break-glass foundation.
l.kirchner added 1 commit 2026-08-19 11:02:42 +02:00
security: version the CIS Tier-A hardening pass
CI / Shell-Lint (bash -n, source-check, Validierungs-Tests) (pull_request) Successful in 2s
5130b82639
The script that sets the SSH, PAM, pwquality and auditd baseline on twelve
containers existed only as a root-owned copy on the machines it hardens.
Bring it into the repo so a change reaches one place instead of twelve.

Two substantive changes over the copy that shipped on 2026-07-24:

- Deny forwarding per option instead of via DisableForwarding. Same effect,
  but DisableForwarding overrides every other forwarding option and is
  invisible in sshd -T, which makes a rejected port-forward read as a
  configuration that should work.
- Quote the command substitution in MODDIR (SC2046).

The header and README now record the rollout command, the containers left
unhardened as break-glass foundation, and why host-specific exceptions must
live in a drop-in that sorts after 99-cis-hardening.conf.
l.kirchner merged commit 0ec6738217 into main 2026-08-19 11:08:14 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: luki-net/proxmox-scripts#11