Ubuntu · Logging & auditing

Audit changes to scheduled tasks and to the systemd journal

When it fails: Changes to scheduled tasks and journal files are not recorded. A scheduled task is a common persistence mechanism, and journal files can be removed to erase traces.

CB-UBU-AUDIT-028lowactiveautomatedautomated fixprofile server-hardenedprofile desktop-hardened

As released in v0.1.1 (beta). Current state.

Description

auditd rules in /etc/audit/rules.d/60-cirsec-cron.rules:

  • writes to /etc/crontab, /etc/cron.d/, /etc/cron.hourly/, /etc/cron.daily/, /etc/cron.weekly/, /etc/cron.monthly/ and the user crontabs in /var/spool/cron/ (key cron);
  • writes and deletions in the persistent journal /var/log/journal/ (key journal).

These rules come from the ANSSI and STIG baselines; CIS has no equivalent. Watches on paths that do not exist yet (cron is not installed by default) are accepted.

Rationale

Adding a cron job is one of the simplest ways to keep code running on a system. Deleting or truncating journal files is a way to erase what happened. Both should leave an audit record.

Impact

journald itself opens, rotates and removes journal files, which produces journal records from the systemd-journald process.

Setting

Location/etc/audit/rules.d/60-cirsec-cron.rules
DefaultNo audit rules
RecommendedRules loaded (auditctl -l) and present in /etc/audit/rules.d/60-cirsec-cron.rules

Audit

  1. Run auditctl -l as root. It must list the rules shown below (keys cron, journal).
  2. Run grep -h . /etc/audit/rules.d/*.rules. The same rules must be present, so they load at boot.

Expected rules, as auditctl -l prints them:

-w /etc/crontab -p wa -k cron
-w /etc/cron.d -p wa -k cron
-w /etc/cron.hourly -p wa -k cron
-w /etc/cron.daily -p wa -k cron
-w /etc/cron.weekly -p wa -k cron
-w /etc/cron.monthly -p wa -k cron
-w /var/spool/cron -p wa -k cron
-w /var/log/journal -p wa -k journal

Automated: every check must pass. Scanners report each check by its id.

rules

Rules loaded in the kernel and present in /etc/audit/rules.d.

script: |
  command -v auditctl >/dev/null 2>&1 || { echo "auditctl not found: auditd is not installed"; exit 1; }
  want() {
  cat <<'RULES'
  -w /etc/crontab -p wa -k cron
  -w /etc/cron.d -p wa -k cron
  -w /etc/cron.hourly -p wa -k cron
  -w /etc/cron.daily -p wa -k cron
  -w /etc/cron.weekly -p wa -k cron
  -w /etc/cron.monthly -p wa -k cron
  -w /var/spool/cron -p wa -k cron
  -w /var/log/journal -p wa -k journal
  RULES
  }
  loaded=$(auditctl -l 2>&1) || { echo "$loaded"; exit 3; }
  disk=$(cat /etc/audit/rules.d/*.rules 2>/dev/null)
  n=0 rc=0
  while IFS= read -r r; do
    [ -n "$r" ] || continue
    n=$((n + 1))
    printf '%s\n' "$loaded" | grep -Fxq -- "$r" || { echo "not loaded: $r"; rc=1; }
    printf '%s\n' "$disk" | grep -Fxq -- "$r" || { echo "not in /etc/audit/rules.d: $r"; rc=1; }
  done <<EOF
  $(want)
  EOF
  [ "$rc" = 1 ] || echo "$n rules loaded and present in /etc/audit/rules.d"
  exit "$rc"

Remediation

  1. Write the expected rules to /etc/audit/rules.d/60-cirsec-cron.rules (mode 0640).
  2. Run augenrules --load. If auditctl -s shows enabled 2, the configuration is locked: reboot to load the new rules.
Restartnone
Disruptionnone

Rollback

Delete /etc/audit/rules.d/60-cirsec-cron.rules and run augenrules --load (or reboot if the configuration is locked).

rules-file

command -v augenrules >/dev/null 2>&1 || { echo "auditd is not installed" >&2; exit 1; }
want() {
cat <<'RULES'
-w /etc/crontab -p wa -k cron
-w /etc/cron.d -p wa -k cron
-w /etc/cron.hourly -p wa -k cron
-w /etc/cron.daily -p wa -k cron
-w /etc/cron.weekly -p wa -k cron
-w /etc/cron.monthly -p wa -k cron
-w /var/spool/cron -p wa -k cron
-w /var/log/journal -p wa -k journal
RULES
}
f=/etc/audit/rules.d/60-cirsec-cron.rules
t=$(mktemp)
{ echo '# Written by CIRSEC Baselines CB-UBU-AUDIT-028'; want; } >"$t"
if ! cmp -s "$t" "$f"; then
  cb_backup "$f"
  cat "$t" >"$f"
fi
rm -f "$t"
chmod 640 "$f"
if auditctl -s | grep -q '^enabled 2'; then
  echo "audit rules are locked (-e 2): reboot to load $f"
else
  augenrules --load >/dev/null
fi

Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.

References

Mappings

How this control relates to the other framework's: equivalent, stricter, looser, partial or related.

Threats mitigated

People and lifecycle

Authors
Verified2026-10-19 by Sn0wAlice on 20.04, Proxmox VM, Ubuntu 20.04 cloud image, kernel 5.4.0-216-generic. Fails after auditd install; rules written and loaded (augenrules), all present in auditctl -l with auditd 2.8.5; passes after reboot with enabled 2.
Verified2026-10-19 by Sn0wAlice on 22.04, Proxmox VM, Ubuntu 22.04 cloud image, kernel 5.15.0-198-generic. Fails after auditd install; rules written and loaded (augenrules), all present in auditctl -l with auditd 3.0.7; passes after reboot with enabled 2.
Verified2026-10-19 by Sn0wAlice on 24.04, Proxmox VM, Ubuntu 24.04 cloud image, kernel 6.8.0-147-generic. Fails after auditd install; rules written and loaded (augenrules), all present in auditctl -l with auditd 3.1.2; passes after reboot with enabled 2.
Verified2026-10-19 by Sn0wAlice on 26.04, Proxmox VM, Ubuntu 26.04 cloud image, kernel 7.0.0-38-generic. Fails after auditd install; rules written and loaded (augenrules), all present in auditctl -l with auditd 4.1.2; passes after reboot with enabled 2.
Introduced inv0.1.0
Created, updated2026-10-19, 2026-10-19
Translationsfr