journald forwards to the syslog daemon that is actually running
If a syslog daemon is running, journald must forward to it (ForwardToSyslog=yes). This pavois-native control asserts the other half of the CIS requirement: the norms demand that a syslog daemon be installed and enabled, but nothing checks that it still receives anything. A host where rsyslog is active (running) and /var/log/syslog has not gained a line since boot is compliant on paper and blind in practice.
Checked against the resolved running state (e.g. sshd -T, sysctl, systemctl show), catches drop-ins and Includes a file read would miss. Caveat: runtime ≠ persistence; a value correct now may not survive a reboot.
What Pavois checks
Two layers. An only_if scopes the control to the transport that actually depends on forwarding: it runs when rsyslog or syslog-ng is running and rsyslog does not load the imjournal module (the Debian and Ubuntu default is imuxsock alone, so without forwarding rsyslog receives nothing). Where rsyslog reads the journal directly through imjournal (the RHEL and Fedora default), forwarding is redundant and the control is skipped, not failed. The check then reads the effective value with systemd-analyze cat-config systemd/journald.conf, keeping the last ForwardToSyslog= line: that merges the vendor file, /etc/systemd/journald.conf and every drop-in, so a no reintroduced downstream is caught. Grepping the main file alone would miss exactly that.
only_if { (service('rsyslog').running? || service('syslog-ng').running?) && !command('grep -rqsE \'^[[:space:]]*(module\\(load="imjournal"|\\$ModLoad[[:space:]]+imjournal)\' /etc/rsyslog.conf /etc/rsyslog.d/ 2>/dev/null').exit_status.zero? }
describe command('systemd-analyze cat-config systemd/journald.conf 2>/dev/null | grep -iE "^[[:space:]]*ForwardToSyslog[[:space:]]*=" | tail -1 | grep -qiE "=[[:space:]]*(no|false|0)" && echo ko || echo ok') do
its('stdout.strip') { should eq 'ok' }
endHow to verify it is applied
Read the resolved value, then prove the pipe end to end:
systemd-analyze cat-config systemd/journald.conf | grep -i '^ForwardToSyslog'
logger -t pavois-test 'forwarding check'
tail -n 5 /var/log/syslog # /var/log/messages on RHEL
The test line must appear in the file within seconds. That end-to-end check, not the config value, is what the incident behind this control would have caught.
Inspect & investigate
The tell-tale is the freshness of the file-based logs: compare stat -c %y /var/log/syslog (or /var/log/messages) with the machine's uptime. An mtime frozen at boot time, on a host whose systemctl status rsyslog is green, is the exact fingerprint of broken forwarding. journalctl -u systemd-journald shows the daemon restarting once the drop-in is applied.
Remediation
Pavois writes a dedicated drop-in /etc/systemd/journald.conf.d/99-pavois-forward.conf containing only:
[Journal]
ForwardToSyslog=yes
It then removes the legacy aggregated drop-in 99-pavois.conf that used to pin ForwardToSyslog=no, and restarts systemd-journald and rsyslog. The one-setting-per-drop-in shape is the whole point: it is what stops another journald control from silently clobbering this one on the next confkit harden apply.
Pavois applies this with its own harden engine, the plan below, not a shell script:
| command | mkdir -p /etc/systemd/journald.conf.d && printf '[Journal]\nForwardToSyslog=yes\n' > /etc/systemd/journald.conf.d/99-pavois-forward.conf && rm -f /etc/systemd/journald.conf.d/99-pavois.conf && systemctl restart systemd-journald 2>/dev/null; systemctl restart rsyslog 2>/dev/null; true |
|---|---|
| name | journald-forward-to-running-syslog |
| not_if | ! test -e /etc/systemd/journald.conf.d/99-pavois.conf && ! systemd-analyze cat-config systemd/journald.conf 2>/dev/null | grep -iE "^[[:space:]]*ForwardToSyslog[[:space:]]*=" | tail -1 | grep -qiE "=[[:space:]]*(no|false|0)" |
| resource | exec |
pavois harden plan localwhere the target is local, a user@host SSH alias, or a container , Docs
Impact & precautions
This control was born from a pavois bug: three journald controls wrote the same aggregated file, whose content pinned ForwardToSyslog=no. rsyslog stayed active (running), /var/log/syslog had not received a single line since boot, and every control was green, while the entire chain that reads log files (SIEM shipping, logrotate, post-incident forensics) was dead. Enabling forwarding stores each message twice (in the journal and in the syslog files), so it roughly doubles log disk usage: pair it with growth-journal-bounded and growth-logrotate-active so neither store can grow without a bound. On RHEL and Fedora with imjournal, the control is skipped and nothing changes.