Audit & Trust
Don't trust us. Verify everything. kldload is designed so that every byte is traceable back to an official upstream source. No pre-built binaries ship in the repository. Every package is fetched from official distro mirrors at build time, with native package-manager verification at every step. But audit goes far beyond supply chain — a kldload system gives you ZFS snapshots as immutable evidence, eBPF as a kernel-level audit trail, and standard Linux audit frameworks wired into a storage layer that cannot silently lose or corrupt data.
The audit philosophy: Trust is earned through transparency, not authority. Every kldload system has four layers of audit capability built in: the build pipeline is readable bash you can review in an afternoon. The storage layer (ZFS) provides immutable, checksummed, snapshotted evidence that cannot be tampered with without detection. The kernel (eBPF) watches every process, every syscall, every network connection in real time. And the standard Linux audit framework (auditd, journald) logs everything else. No vendor binary. No trust gap. No "just believe us."
Most infrastructure products ask you to trust a binary you cannot read. kldload asks you to read the source, build the ISO, verify the checksums, and then decide. The entire supply chain is auditable. The deployed system is auditable. The ongoing operation is auditable. Every link in the chain is verifiable with standard tools that ship with the operating system.
The trust model is simple. You trust your distro's package mirrors — you already do, every time you run apt update. You can read every line of build script that assembles the ISO. There is no middle layer, no proprietary toolchain, no binary blob you can't inspect. The most common question from security teams is "what did you add?" The answer is bash scripts. Everything else comes from the vendor.
This matters because most infrastructure tools have a trust gap: you trust the vendor's binary because you can't read it. kldload has no vendor binary. The entire build pipeline is deploy.sh → build-iso.sh → vendor packages + bash scripts. If your security team can read bash, they can audit the entire project in an afternoon.
Audit philosophy — trust but verify
The phrase "trust but verify" gets thrown around in security circles as a platitude. On kldload, it is a concrete architecture. Every layer of the system produces verifiable evidence, and every piece of evidence is stored on a filesystem that checksums every block and cannot silently corrupt data. The audit chain has four links:
Build-time provenance
Every package in the ISO came from an official distro mirror. Every build step
is a readable bash script. The entire supply chain is deploy.sh →
official mirrors → ISO. No intermediate binaries. No vendor blobs. You can
rebuild the ISO from source and compare checksums.
Immutable evidence via ZFS
ZFS snapshots are atomic, point-in-time captures of the entire filesystem.
They cannot be modified after creation. With zfs hold, they cannot even
be deleted. Every block is checksummed with SHA-256 or BLAKE3. If a single byte
changes, ZFS detects it. This is not an audit log — it is an audit filesystem.
Kernel-level visibility via eBPF
eBPF programs run inside the kernel. They see every process execution, every file open, every network connection, every privilege escalation — before userspace even knows it happened. An attacker who compromises a process cannot hide from eBPF because eBPF runs at a lower level than any user process.
Standard audit frameworks
journald captures every system log. auditd monitors syscalls and file access. WireGuard logs every peer connection. ZED (ZFS Event Daemon) logs every pool event. All of these are standard Linux tools that auditors already know how to review. kldload does not invent new audit mechanisms — it wires together the ones that already exist and stores them on ZFS.
I have been through SOC2 audits, PCI-DSS assessments, and FedRAMP reviews. The auditor always asks the same question: "show me the evidence." On a traditional system, the evidence is log files on ext4 or XFS — files that root can edit, delete, or truncate. On kldload, the evidence is ZFS snapshots that root cannot modify without leaving a trace in the snapshot history. The auditor asks "can the admin tamper with the logs?" and the answer is "the logs are on a held ZFS snapshot that cannot be destroyed, and every block is checksummed." That answer closes the finding. Every time.
What kldload audits by default
A freshly installed kldload system (desktop or server profile) produces audit data from four sources without any additional configuration. These are not optional add-ons — they are core system services that start at boot.
System logs via journald
systemd-journald captures every log message from every service, the kernel ring buffer, and stdout/stderr of every systemd unit. Logs are structured (key=value pairs), indexed, and queryable. On kldload, journal storage lives on ZFS — compressed, checksummed, and snapshotted automatically by sanoid.
# View all logs since last boot
journalctl -b
# Follow logs in real time
journalctl -f
# Logs from a specific service
journalctl -u sshd --since "1 hour ago"
# Logs from a specific PID
journalctl _PID=1234
# Kernel messages only
journalctl -k
# Export structured JSON for external processing
journalctl -o json --since "2024-01-01" | head -100
# Disk usage of journal storage
journalctl --disk-usage
ZFS events via ZED
The ZFS Event Daemon watches every pool event: scrub completions, checksum errors, device failures, resilver progress, import/export operations. On kldload, ZED is configured to log to journald and optionally send email alerts.
# View all ZFS events
journalctl -t zed
# Check ZED configuration
cat /etc/zfs/zed.d/zed.rc
# List enabled ZED scripts
ls -la /etc/zfs/zed.d/
# Recent pool events
zpool events -v | head -50
# Checksum error history (critical for audit)
zpool events | grep -i checksum
# Scrub history
zpool history rpool | grep scrub
WireGuard connection logs
Every WireGuard peer connection is logged via the kernel's dynamic debug facility. The handshake timestamp, peer public key, and endpoint IP are recorded. On kldload systems with WireGuard enabled, this provides a complete record of every peer that connected, when, and from where.
# Enable WireGuard debug logging
echo module wireguard +p > /sys/kernel/debug/dynamic_debug/control
# View WireGuard handshake events
journalctl -k | grep wireguard
# Show current peer status (latest handshake times)
wg show all
# Show specific interface peer details
wg show wg0 latest-handshakes
# Parse handshake timestamps for audit report
wg show wg0 latest-handshakes | while read key ts; do
echo "$key $(date -d @$ts '+%Y-%m-%d %H:%M:%S')"
done
SSH authentication logs
Every SSH login attempt — successful or failed — is logged to journald with the username, source IP, authentication method, and key fingerprint. kldload disables root SSH login by default, so every legitimate session maps to a named user.
# All SSH authentication events
journalctl -u sshd | grep -E "(Accepted|Failed|Invalid)"
# Failed login attempts (brute force detection)
journalctl -u sshd | grep "Failed password" | awk '{print $11}' | sort | uniq -c | sort -rn
# Successful logins with key fingerprints
journalctl -u sshd | grep "Accepted publickey"
# Active SSH sessions right now
ss -tnp | grep :22
who -u
Most people think audit logging requires installing something. On kldload it does not. journald is already running. ZED is already running. WireGuard debug is one command. SSH logging is already happening. The data is already being generated — you just need to know where to look. The only thing kldload adds is that all of this data lands on ZFS, which means it is compressed (saving disk), checksummed (cannot silently corrupt), and snapshotted (cannot be deleted without a trace).
eBPF audit trail
eBPF is the most powerful audit tool on Linux. It runs programs inside the kernel that can observe every syscall, every process, every network packet — with near-zero overhead. kldload ships with bpftrace and BCC tools on desktop and server profiles. The following eBPF programs provide kernel-level audit trails that userspace cannot evade.
Process execution logging
Track every process that runs on the system. Every exec() call is captured
with the full command line, parent PID, user ID, and timestamp. This is more comprehensive
than auditd because eBPF sees the exec before the process starts.
# Real-time process execution trace (BCC)
execsnoop-bpfcc
# Output includes: PID, PPID, COMM, RET, ARGS
# Example output:
# PID PPID COMM RET ARGS
# 18234 18220 curl 0 curl https://example.com
# 18235 1 crond 0 /usr/sbin/crond -n
# Log to file for audit retention
execsnoop-bpfcc -T > /var/log/audit/exec-$(date +%Y%m%d).log &
# With bpftrace: custom exec tracer with UID
bpftrace -e 'tracepoint:syscalls:sys_enter_execve {
printf("%s uid=%d pid=%d ppid=%d %s\n",
strftime("%H:%M:%S", nsecs),
uid, pid, curtask->parent->pid,
str(args->filename));
}'
# Filter for specific users (e.g., uid 1000)
bpftrace -e 'tracepoint:syscalls:sys_enter_execve /uid == 1000/ {
printf("%s %s %s\n", strftime("%H:%M:%S", nsecs), comm, str(args->filename));
}'
File integrity monitoring
Watch specific files or directories for any open, read, write, or unlink operation. This is real-time file integrity monitoring at the kernel level — no polling, no checksumming delay. Every file access is captured the instant it happens.
# Watch all file opens in /etc (configuration tampering detection)
opensnoop-bpfcc -p 0 | grep /etc/
# Watch writes to critical files
bpftrace -e 'tracepoint:syscalls:sys_enter_openat
/str(args->filename) == "/etc/passwd"/ {
printf("%s uid=%d pid=%d comm=%s OPEN /etc/passwd\n",
strftime("%H:%M:%S", nsecs), uid, pid, comm);
}'
# Watch file deletions system-wide
bpftrace -e 'tracepoint:syscalls:sys_enter_unlinkat {
printf("%s uid=%d pid=%d comm=%s DELETE %s\n",
strftime("%H:%M:%S", nsecs), uid, pid, comm, str(args->pathname));
}'
# Monitor /etc/shadow access (password file)
bpftrace -e 'tracepoint:syscalls:sys_enter_openat
/str(args->filename) == "/etc/shadow"/ {
printf("ALERT: %s (pid=%d uid=%d) accessed /etc/shadow at %s\n",
comm, pid, uid, strftime("%H:%M:%S", nsecs));
}'
# Watch all writes to /var/log (log tampering detection)
opensnoop-bpfcc | grep -E "W.*/var/log/"
Network connection tracking
Every TCP connection established on the system — inbound and outbound — is logged with source IP, destination IP, port, PID, and process name. This creates a complete network audit trail at the kernel level.
# All new TCP connections (outbound)
tcpconnect-bpfcc
# Output: PID, COMM, IP, SADDR, DADDR, DPORT
# Example:
# PID COMM IP SADDR DADDR DPORT
# 4523 curl 4 10.0.0.5 93.184.216.34 443
# 4531 sshd 4 10.0.0.5 10.0.0.10 22
# All new TCP connections (inbound)
tcpaccept-bpfcc
# Combined: all TCP state changes with duration
tcplife-bpfcc
# PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS
# 4523 curl 10.0.0.5 44820 93.184.216 443 1 12 234
# DNS queries (what hostnames is the system resolving?)
bpftrace -e 'tracepoint:net:net_dev_xmit /args->len > 0/ {
printf("%s pid=%d comm=%s len=%d\n",
strftime("%H:%M:%S", nsecs), pid, comm, args->len);
}'
# Log all outbound connections to file for audit
tcpconnect-bpfcc -T > /var/log/audit/netconn-$(date +%Y%m%d).log &
Privilege escalation detection
Detect any process that changes its effective UID (sudo, su, setuid binaries, exploits). Every privilege change is visible to eBPF because the kernel itself performs the credential swap.
# Watch setuid/setgid syscalls
bpftrace -e 'tracepoint:syscalls:sys_enter_setuid {
printf("PRIVESC: %s pid=%d uid=%d -> target_uid=%d\n",
comm, pid, uid, args->uid);
}'
# Watch all capability grants
bpftrace -e 'kprobe:cap_capable {
printf("%s pid=%d uid=%d comm=%s cap=%d\n",
strftime("%H:%M:%S", nsecs), pid, uid, comm, arg2);
}'
# Detect sudo usage
execsnoop-bpfcc | grep -E "^.*sudo"
# Watch for kernel module loading (rootkit detection)
bpftrace -e 'tracepoint:syscalls:sys_enter_finit_module {
printf("ALERT: module load by pid=%d uid=%d comm=%s\n", pid, uid, comm);
}'
# Watch for ptrace (debugger attach, often used in exploits)
bpftrace -e 'tracepoint:syscalls:sys_enter_ptrace {
printf("ALERT: ptrace by pid=%d uid=%d comm=%s request=%d\n",
pid, uid, comm, args->request);
}'
eBPF is the audit tool that changes the game. Traditional audit (auditd) logs syscalls from a userspace daemon that reads kernel audit buffers. If the system is under heavy load, audit events can be dropped. If an attacker gets root, they can stop auditd or truncate the log files. eBPF runs inside the kernel itself. The attacker cannot disable an eBPF program without CAP_BPF privilege, and even then the unload event is visible. The combination of eBPF + ZFS snapshots means: the kernel sees everything, and the evidence is stored on a filesystem that cannot silently lose data. That is a fundamentally stronger audit posture than anything auditd alone can provide.
ZFS as an audit tool
ZFS is not just a filesystem — it is an audit infrastructure. Every block is
checksummed. Every snapshot is atomic and immutable. zfs diff can tell you
exactly which files changed between any two points in time. zfs send can
preserve evidence off-site. And zfs hold can prevent snapshot destruction
even by root. No other filesystem provides this combination.
Snapshots as point-in-time evidence
A ZFS snapshot captures the exact state of a dataset at a specific moment. It is read-only. It cannot be modified. If an attacker modifies a file, the original version still exists in the snapshot. If an attacker deletes a log, the snapshot still has it.
# Create an evidence snapshot (millisecond precision)
zfs snapshot rpool/ROOT/cs@evidence-$(date +%Y%m%d-%H%M%S)
# Create recursive snapshot of entire system
zfs snapshot -r rpool@incident-$(date +%Y%m%d-%H%M%S)
# List all snapshots with creation times
zfs list -t snapshot -o name,creation,used,refer -s creation
# Access snapshot contents (read-only, no mount needed)
ls /rpool/ROOT/cs/.zfs/snapshot/evidence-20240115-143022/etc/passwd
cat /rpool/ROOT/cs/.zfs/snapshot/evidence-20240115-143022/var/log/secure
# Compare current state to snapshot (what changed?)
diff /etc/passwd /rpool/ROOT/cs/.zfs/snapshot/evidence-20240115-143022/etc/passwd
# Automated hourly evidence snapshots via sanoid
# sanoid runs by default on kldload and creates:
# - hourly snapshots (kept for 48 hours)
# - daily snapshots (kept for 30 days)
# - monthly snapshots (kept for 6 months)
sanoid --cron
zfs diff — exactly what changed
zfs diff compares two snapshots (or a snapshot and the live filesystem)
and lists every file that was added, modified, deleted, or renamed. This is the single
most powerful audit command on any filesystem.
# What changed since this morning's snapshot?
zfs diff rpool/ROOT/cs@autosnap_2024-01-15_06:00:00_hourly
# Output format:
# M /etc/passwd (modified)
# + /tmp/suspicious-binary (added)
# - /var/log/secure (deleted)
# R /etc/shadow -> /etc/shadow.bak (renamed)
# Compare two specific snapshots
zfs diff rpool/ROOT/cs@before-change rpool/ROOT/cs@after-change
# Find all changes in the last 24 hours (compare oldest hourly to now)
OLDEST=$(zfs list -t snapshot -o name -s creation rpool/ROOT/cs | grep hourly | head -1)
zfs diff "$OLDEST"
# Pipe to file for evidence
zfs diff rpool/ROOT/cs@evidence-20240115-143022 > /root/incident-changes.txt
# Show only deleted files (potential evidence destruction)
zfs diff rpool/ROOT/cs@autosnap_2024-01-15_06:00:00_hourly | grep "^-"
zfs send/receive for evidence preservation
Preserve the exact state of a system off-site. zfs send creates a
byte-for-byte stream of a snapshot that can be received on any other ZFS system.
The evidence is cryptographically identical to the source.
# Send evidence snapshot to a remote evidence server
zfs send rpool/ROOT/cs@evidence-20240115-143022 | \
ssh evidence-server zfs receive evidence-pool/case-2024-0115
# Send encrypted (raw) for chain of custody
zfs send --raw rpool/ROOT/cs@evidence-20240115-143022 | \
ssh evidence-server zfs receive evidence-pool/case-2024-0115
# Send incremental (only changes between two snapshots)
zfs send -i rpool/ROOT/cs@before rpool/ROOT/cs@after | \
ssh evidence-server zfs receive evidence-pool/case-delta
# Verify the received snapshot matches the source
zfs get -o value written rpool/ROOT/cs@evidence-20240115-143022
ssh evidence-server zfs get -o value written evidence-pool/case-2024-0115
# Save to file for offline evidence storage
zfs send rpool/ROOT/cs@evidence-20240115-143022 | \
gzip > /mnt/usb/evidence-case-2024-0115.zfs.gz
Immutable snapshots with holds
A ZFS hold prevents a snapshot from being destroyed, even by root. This is critical for evidence preservation — you can take a snapshot, place a hold on it, and nobody can delete it until the hold is explicitly released.
# Place a hold on an evidence snapshot (nobody can delete it now)
zfs hold legal-hold rpool/ROOT/cs@evidence-20240115-143022
# Attempt to destroy it (this FAILS)
zfs destroy rpool/ROOT/cs@evidence-20240115-143022
# cannot destroy 'rpool/ROOT/cs@evidence-20240115-143022': dataset is busy
# List all holds
zfs holds rpool/ROOT/cs@evidence-20240115-143022
# NAME TAG TIMESTAMP
# rpool/ROOT/cs@evidence-20240115-143022 legal-hold Mon Jan 15 14:30 2024
# List all held snapshots across the pool
zfs list -t snapshot -o name,defer_destroy | grep -v "-$"
# Release a hold (requires explicit action)
zfs release legal-hold rpool/ROOT/cs@evidence-20240115-143022
# Place holds on all snapshots from an incident
for snap in $(zfs list -t snapshot -o name | grep "incident-20240115"); do
zfs hold forensic-hold "$snap"
done
This is the part that makes auditors smile. In a traditional audit, the evidence is log files on a mutable filesystem. An attacker (or a panicked admin) can delete logs, truncate files, overwrite entries. On ZFS, the evidence is a snapshot — a read-only, checksummed, immutable capture of the entire filesystem state. With a hold, not even root can destroy it. With zfs send, you can move it off-site in minutes. With zfs diff, you can tell the auditor exactly which files changed, when, and what the before/after content was. No other filesystem can do this. It is the strongest evidence preservation mechanism available on any operating system.
Linux audit framework — auditd
The Linux audit system (auditd) is the traditional syscall-level audit framework. It is required by most compliance frameworks (PCI-DSS, HIPAA, FedRAMP, SOC2) and provides detailed logging of file access, syscalls, user authentication, and administrative actions. kldload supports auditd on all distros.
Install and enable auditd
# CentOS / Rocky / RHEL / Fedora (usually pre-installed)
dnf install -y audit
systemctl enable --now auditd
# Debian / Ubuntu
apt install -y auditd audispd-plugins
systemctl enable --now auditd
# Arch
pacman -S audit
systemctl enable --now auditd
# Verify it is running
auditctl -s
# enabled 1
# pid 1234
# ...
# Check audit log location
ls -la /var/log/audit/audit.log
Audit rules for critical files
Watch specific files and directories for read, write, execute, and attribute changes. These rules generate audit events every time the specified file is accessed.
# Add rules to /etc/audit/rules.d/kldload.rules
# Watch password and authentication files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/gshadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Watch SSH configuration
-w /etc/ssh/sshd_config -p wa -k sshd_config
-w /etc/ssh/ssh_config -p wa -k ssh_config
-w /root/.ssh/ -p wa -k root_ssh
# Watch system startup scripts
-w /etc/systemd/ -p wa -k systemd_config
-w /etc/cron.d/ -p wa -k cron
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Watch kernel module loading
-w /sbin/insmod -p x -k kernel_modules
-w /sbin/modprobe -p x -k kernel_modules
-w /sbin/rmmod -p x -k kernel_modules
# Watch ZFS configuration
-w /etc/zfs/ -p wa -k zfs_config
-w /etc/modprobe.d/zfs.conf -p wa -k zfs_config
# Watch WireGuard configuration
-w /etc/wireguard/ -p wa -k wireguard_config
# Load the rules
augenrules --load
auditctl -l # verify rules are active
Audit rules for syscalls
Monitor specific syscalls across the entire system. These rules catch actions that file watches alone cannot — like mounting filesystems, changing the system time, or modifying network configuration.
# Add to /etc/audit/rules.d/kldload-syscalls.rules
# Log all execve calls by root (every command root runs)
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
# Log mount/unmount operations
-a always,exit -F arch=b64 -S mount -S umount2 -k mounts
# Log time changes (critical for log integrity)
-a always,exit -F arch=b64 -S adjtimex -S settimeofday -S clock_settime -k time_change
# Log network configuration changes
-a always,exit -F arch=b64 -S sethostname -S setdomainname -k network_config
# Log user/group modification
-a always,exit -F arch=b64 -S setuid -S setgid -S setreuid -S setregid -k priv_escalation
# Log failed file access attempts (permission denied)
-a always,exit -F arch=b64 -S open -S openat -F exit=-EACCES -k access_denied
-a always,exit -F arch=b64 -S open -S openat -F exit=-EPERM -k access_denied
# Log kernel module operations
-a always,exit -F arch=b64 -S init_module -S finit_module -S delete_module -k kernel_modules
# Make the audit configuration immutable until reboot
-e 2
Searching and reporting with ausearch and aureport
# Search for all events related to /etc/passwd
ausearch -k identity
# Search for events by specific user
ausearch -ua 1000
# Search for failed login attempts
ausearch -m USER_LOGIN --success no
# Search by time range
ausearch --start 01/15/2024 08:00:00 --end 01/15/2024 17:00:00
# Search for root command execution
ausearch -k root_commands -ts today
# Generate summary report
aureport --summary
# Authentication report (logins, failures)
aureport --auth
# File access report
aureport --file --summary
# Executable report (what binaries ran)
aureport --executable --summary
# Failed events report
aureport --failed
# User activity report
aureport --user
# Generate report for specific time period
aureport --start 01/01/2024 --end 01/31/2024 --auth --summary
auditd is not optional for compliance. PCI-DSS Requirement 10 demands it. HIPAA 164.312(b) requires it. FedRAMP mandates it. SOC2 CC6.1 expects it. The audit rules above cover the CIS Benchmark Level 2 requirements for all supported distros. Copy them to /etc/audit/rules.d/, run augenrules --load, and you have a compliance-ready audit configuration. The fact that these logs land on ZFS — checksummed, compressed, snapshotted — is the part that goes beyond what the compliance framework requires. It is defense in depth for your evidence.
Compliance framework mapping
kldload's native capabilities map directly to specific controls in major compliance frameworks. The table below is not aspirational — these are concrete mappings showing which kldload feature satisfies which control requirement.
SOC2 Trust Services Criteria
| Control | Requirement | kldload Capability |
|---|---|---|
| CC6.1 | Logical access security | SSH key auth, WireGuard peer auth, nftables, SELinux/AppArmor |
| CC6.2 | Credentials management | SSH keys only (password auth disabled), WireGuard pre-shared keys |
| CC6.3 | Authorization and access | sudo with NOPASSWD removed post-install, auditd rule logging |
| CC6.6 | Threat and vulnerability management | eBPF security monitors, package verification (rpm -V, debsums) |
| CC6.7 | Restrict data transmission | WireGuard encrypted tunnels, nftables egress filtering |
| CC6.8 | Prevent unauthorized software | eBPF execsnoop, auditd exec rules, signed packages only |
| CC7.1 | Detect and monitor | eBPF process/network/file monitors, journald, ZED alerts |
| CC7.2 | Monitor for anomalies | eBPF privilege escalation detection, failed auth logging |
| CC7.3 | Evaluate detected events | ausearch, aureport, journalctl queries, zfs diff |
| CC8.1 | Change management | ZFS snapshots (before/after), zfs diff, git for config |
PCI-DSS v4.0 Requirements
| Requirement | Description | kldload Capability |
|---|---|---|
| 10.2.1 | Audit log for user access | auditd + journald (SSH, sudo, service access) |
| 10.2.1.1 | Log all individual user access to cardholder data | auditd file watches on data directories |
| 10.2.1.2 | Log administrative actions | auditd root_commands rule, sudo logging |
| 10.2.1.3 | Log access to audit trails | auditd watch on /var/log/audit/ |
| 10.2.1.4 | Log invalid logical access attempts | auditd access_denied rules, SSH failed auth |
| 10.2.1.5 | Log changes to authentication mechanisms | auditd identity rules (/etc/passwd, /etc/shadow) |
| 10.2.2 | Implement automated audit trails | auditd + systemd journal (automatic, persistent) |
| 10.3.1 | Prompt audit log review | aureport, ausearch, Grafana dashboards |
| 10.3.3 | Anomaly and alert monitoring | eBPF monitors + ZED alerts + Grafana alerting |
| 10.5.1 | Retain audit logs 12 months | ZFS snapshots (compressed, no practical storage limit) |
| 10.6.1 | Time synchronization | chrony/systemd-timesyncd, auditd time_change rules |
| 10.7 | Protect audit logs from modification | ZFS snapshots with holds (immutable, checksummed) |
HIPAA 164.312 Technical Safeguards
| Section | Requirement | kldload Capability |
|---|---|---|
| 164.312(a)(1) | Access control | SSH key auth, sudo, nftables, WireGuard peer auth |
| 164.312(a)(2)(i) | Unique user identification | Named user accounts, SSH key fingerprints in logs |
| 164.312(a)(2)(iii) | Automatic logoff | SSH ClientAliveInterval, tmux timeout |
| 164.312(b) | Audit controls | auditd + journald + eBPF + ZFS snapshot evidence |
| 164.312(c)(1) | Integrity controls | ZFS checksumming, rpm -V/debsums, eBPF file monitoring |
| 164.312(c)(2) | Mechanism to authenticate ePHI | ZFS checksums (SHA-256/BLAKE3 on every block) |
| 164.312(d) | Person or entity authentication | SSH public key + WireGuard peer identity |
| 164.312(e)(1) | Transmission security | WireGuard (Curve25519 + ChaCha20-Poly1305) |
| 164.312(e)(2)(ii) | Encryption | ZFS native encryption (AES-256-GCM), WireGuard tunnel encryption |
FedRAMP
FedRAMP audit requirements
FedRAMP inherits NIST 800-53 audit controls. kldload maps to the following control families:
AU-2 (Audit Events) — auditd rules cover authentication, privilege use, file access, system changes. eBPF adds kernel-level process and network visibility.
AU-3 (Content of Audit Records) — journald and auditd capture timestamp, source, user, action, outcome, and object for every event.
AU-4 (Audit Storage Capacity) — ZFS compression reduces log storage by 3-10x. Dynamic pool expansion adds capacity without downtime.
AU-5 (Response to Audit Failures) — auditd -f 2 halts the system if audit cannot write. ZED alerts on pool errors.
AU-6 (Audit Review) — ausearch, aureport, journalctl, Grafana dashboards for systematic review.
AU-9 (Protection of Audit Information) — ZFS snapshots with holds. Checksummed storage. Off-site replication via zfs send.
AU-11 (Audit Record Retention) — ZFS snapshots retained per sanoid policy. Compressed storage makes 12+ month retention practical.
Compliance frameworks are checklists. They tell you what to do, not how to do it. The hard part is not knowing that PCI-DSS 10.7 requires protecting audit logs from modification — the hard part is actually implementing it. On ext4, "protecting audit logs" means setting file permissions and hoping nobody with root access edits them. On ZFS, "protecting audit logs" means a held snapshot that root cannot destroy. That is the difference between a compliance checkbox and actual security. kldload gives you both.
Log aggregation — centralized audit across a fleet
A single kldload server generates audit data in journald, auditd, eBPF traces, ZFS events, and WireGuard logs. A fleet of servers generates all of that times N. Centralized log aggregation brings all of it to a single pane of glass. The standard stack is journald → Promtail → Loki → Grafana.
Promtail — ship logs from every node
Promtail reads journald and ships logs to Loki over HTTP (or WireGuard-encrypted HTTP). Install on every kldload node.
# /etc/promtail/config.yml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://loki.internal:3100/loki/api/v1/push
scrape_configs:
# Scrape systemd journal
- job_name: journal
journal:
max_age: 12h
labels:
job: systemd-journal
host: ${HOSTNAME}
relabel_configs:
- source_labels: ['__journal__systemd_unit']
target_label: 'unit'
- source_labels: ['__journal_priority_keyword']
target_label: 'level'
# Scrape audit logs
- job_name: audit
static_configs:
- targets: [localhost]
labels:
job: auditd
host: ${HOSTNAME}
__path__: /var/log/audit/audit.log
# Scrape eBPF audit logs
- job_name: ebpf-audit
static_configs:
- targets: [localhost]
labels:
job: ebpf
host: ${HOSTNAME}
__path__: /var/log/audit/exec-*.log
__path__: /var/log/audit/netconn-*.log
Loki — log storage on ZFS
Loki stores log chunks and indexes. On kldload, put Loki's data directory on a dedicated ZFS dataset with compression enabled — logs compress 5-10x with LZ4.
# Create a dedicated ZFS dataset for Loki
zfs create -o compression=lz4 -o recordsize=128k rpool/loki
zfs create rpool/loki/chunks
zfs create rpool/loki/index
# /etc/loki/config.yml (relevant storage section)
storage_config:
boltdb_shipper:
active_index_directory: /rpool/loki/index/active
cache_location: /rpool/loki/index/cache
shared_store: filesystem
filesystem:
directory: /rpool/loki/chunks
# Retention: keep 90 days of logs
limits_config:
retention_period: 2160h
# Start Loki
systemctl enable --now loki
# Verify Loki is receiving logs
curl -s http://localhost:3100/ready
curl -s http://localhost:3100/metrics | grep loki_ingester_chunks_stored_total
Grafana — query and alert
Grafana connects to Loki and provides log search, dashboards, and alerting. Query all audit events across every node in the fleet from one interface.
# Grafana Loki queries (LogQL)
# All SSH failures across the fleet
{job="systemd-journal"} |= "Failed password"
# All sudo commands by user "deploy"
{job="systemd-journal", unit="sudo.service"} |= "deploy"
# All auditd events for /etc/passwd changes
{job="auditd"} |= "identity"
# eBPF exec events for curl (exfiltration detection)
{job="ebpf"} |= "curl"
# All events from a specific host
{host="web-prod-01"} | json | level="err"
# Rate of failed SSH logins (alerting)
rate({job="systemd-journal"} |= "Failed password" [5m])
Log retention on ZFS
ZFS makes long-term log retention practical. Logs compress 5-10x with LZ4. Snapshots are incremental (only new data costs space). A 1TB ZFS pool can store years of audit logs for a mid-size fleet.
# Check compression ratio on log storage
zfs get compressratio rpool/loki
# NAME PROPERTY VALUE SOURCE
# rpool/loki compressratio 6.82x -
# Check actual space used vs. logical size
zfs list -o name,used,logicalused,compressratio rpool/loki
# Snapshot log storage for compliance retention
zfs snapshot rpool/loki@eom-$(date +%Y%m)
# Replicate log snapshots off-site
zfs send -i rpool/loki@eom-202312 rpool/loki@eom-202401 | \
ssh log-archive zfs receive archive/loki
# Set quota to prevent log storage from consuming the pool
zfs set quota=500G rpool/loki
The standard enterprise approach to centralized logging is Splunk or Elastic — products that cost tens of thousands of dollars per year and store logs on filesystems that can silently corrupt data. The kldload approach is Promtail + Loki + Grafana on ZFS — free, open source, and stored on a filesystem that checksums every block. Your audit logs are compressed 6x, snapshotted automatically, replicated off-site with one command, and verifiable at the block level. The compliance auditor asks "can you prove your log storage has not been tampered with?" and you show them zpool status — zero checksum errors. Try doing that with Splunk on XFS.
Forensic investigation — incident response on kldload
When an incident occurs, the response workflow on kldload is fundamentally different from
traditional systems. You do not scramble to preserve evidence — the evidence is
already preserved in ZFS snapshots. You do not guess what changed — zfs diff
tells you exactly what changed. You do not wonder what processes ran — eBPF already
logged them. The workflow is: detect → preserve → analyze → recover.
Detect — eBPF and log alerts
Detection comes from eBPF monitors (unexpected process execution, privilege escalation, unusual network connections), auditd alerts (file tampering, unauthorized access), and system logs (failed logins, service crashes).
# Check for suspicious processes RIGHT NOW
execsnoop-bpfcc & # watch new processes in real time
tcpconnect-bpfcc & # watch outbound connections in real time
# Check recent auth failures
journalctl -u sshd --since "1 hour ago" | grep "Failed"
# Check for recent privilege escalation
ausearch -k priv_escalation -ts recent
# Check for file tampering
ausearch -k identity -ts today
ausearch -k sshd_config -ts today
# Check for unusual kernel modules
lsmod | diff - <(cat /root/baseline-modules.txt)
Preserve — ZFS snapshot immediately
The moment an incident is detected, take a recursive snapshot of the entire pool. Place a hold on it. This preserves the exact state of the system at the time of detection — every file, every log, every configuration.
# Snapshot EVERYTHING immediately
zfs snapshot -r rpool@incident-$(date +%Y%m%d-%H%M%S)
# Place a forensic hold (cannot be deleted)
SNAP=$(zfs list -t snapshot -o name -s creation | grep incident | tail -1)
zfs hold forensic "$SNAP"
# Send evidence off-site IMMEDIATELY
zfs send -R "$SNAP" | ssh evidence-server zfs receive evidence/case-$(date +%Y%m%d)
# Record volatile evidence that snapshots don't capture
ss -tnp > /root/incident/active-connections.txt
ps auxf > /root/incident/process-tree.txt
lsof -i > /root/incident/open-files.txt
ip addr > /root/incident/network-config.txt
last -20 > /root/incident/recent-logins.txt
cat /proc/*/maps 2>/dev/null > /root/incident/memory-maps.txt
Analyze — zfs diff, log review, timeline
Now analyze what happened. zfs diff shows every file that changed.
Log queries show the sequence of events. eBPF logs show which processes and
connections were involved.
# What files changed since the last known-good snapshot?
zfs diff rpool/ROOT/cs@autosnap_2024-01-15_06:00:00_hourly \
rpool/ROOT/cs@incident-20240115-143022
# Look at specific changed files
diff /rpool/ROOT/cs/.zfs/snapshot/autosnap_2024-01-15_06:00:00_hourly/etc/passwd \
/rpool/ROOT/cs/.zfs/snapshot/incident-20240115-143022/etc/passwd
# Build a timeline from multiple log sources
journalctl --since "2024-01-15 12:00" --until "2024-01-15 15:00" \
-o short-precise > /root/incident/timeline-journal.txt
ausearch --start 01/15/2024 12:00:00 --end 01/15/2024 15:00:00 \
> /root/incident/timeline-audit.txt
# Check what the attacker accessed
ausearch -k identity --start 01/15/2024 12:00:00
ausearch -k root_commands --start 01/15/2024 12:00:00
# Check network exfiltration
cat /var/log/audit/netconn-20240115.log | \
grep -v "10\.0\.0\." | sort -t: -k5 -rn
# Recover deleted files from the snapshot
cp /rpool/ROOT/cs/.zfs/snapshot/autosnap_2024-01-15_06:00:00_hourly/var/log/secure \
/root/incident/recovered-secure-log.txt
Recover — rollback or rebuild
Once analysis is complete, decide: rollback to the last known-good snapshot, or rebuild from scratch. ZFS makes both options fast and safe.
# Option 1: Rollback to last known-good snapshot
# (WARNING: destroys all changes after the snapshot)
zfs rollback rpool/ROOT/cs@autosnap_2024-01-15_06:00:00_hourly
# Option 2: Clone the known-good snapshot to a new boot environment
zfs clone rpool/ROOT/cs@autosnap_2024-01-15_06:00:00_hourly \
rpool/ROOT/cs-recovered
# Update bootloader to boot from cs-recovered
# Option 3: Selective file recovery from snapshot
cp -a /rpool/ROOT/cs/.zfs/snapshot/autosnap_2024-01-15_06:00:00_hourly/etc/passwd /etc/passwd
cp -a /rpool/ROOT/cs/.zfs/snapshot/autosnap_2024-01-15_06:00:00_hourly/etc/shadow /etc/shadow
# Option 4: Full reinstall from ISO (nuclear option)
# Boot from kldload ISO, install fresh, restore data from zfs send backup
# After recovery: verify the system
rpm -Va 2>/dev/null | head -50 # CentOS/Rocky/Fedora/RHEL
debsums -c 2>/dev/null | head -50 # Debian/Ubuntu
pacman -Qkk 2>/dev/null | grep -v "0 altered" | head -50 # Arch
On a traditional system, incident response is chaos. You scramble to copy log files before they rotate. You try to figure out what changed by comparing config files to backups that may be weeks old. You hope the attacker did not delete the logs. On kldload, the workflow is calm and methodical: take a snapshot (milliseconds), hold it (one command), diff it against known-good (one command), review the timeline (journalctl + ausearch), recover (rollback or clone). The entire investigation can be done from the held snapshots without touching the compromised live system. That is not a theoretical advantage — it is the difference between a two-hour incident response and a two-week one.
Change management — who changed what, when
Change management on kldload operates at three layers: git for configuration (human-readable diffs, commit history, attribution), ZFS snapshots for filesystem state (point-in-time captures of the entire system), and eBPF for real-time changes (who ran what command, when, and what it touched).
Git for configuration tracking
Store /etc in a local git repository. Every configuration change gets
a commit with a timestamp, author, and message. git log and git diff
show exactly who changed what and when.
# Initialize git tracking for /etc
cd /etc
git init
git add -A
git commit -m "baseline configuration after kldload install"
# After making any configuration change:
cd /etc
git add -A
git diff --cached # review what changed
git commit -m "enable auditd, add audit rules for PCI-DSS"
# Who changed sshd_config and when?
git log --oneline -10 -- ssh/sshd_config
# What exactly changed in the last commit?
git show HEAD -- ssh/sshd_config
# Compare current config to the original baseline
git diff HEAD~10 -- ssh/sshd_config
# Blame: who wrote each line of the current config?
git blame ssh/sshd_config
ZFS snapshots for filesystem state
ZFS snapshots capture the entire filesystem state, not just tracked files.
Sanoid creates automatic snapshots on schedule. Use zfs diff to see
what changed between any two snapshots — including files git does not track.
# List recent automatic snapshots
zfs list -t snapshot -o name,creation -s creation rpool/ROOT/cs | tail -20
# Before a maintenance window: manual snapshot
zfs snapshot rpool/ROOT/cs@pre-maintenance-$(date +%Y%m%d)
# After maintenance: what changed?
zfs diff rpool/ROOT/cs@pre-maintenance-20240115
# Save the diff as a change record
zfs diff rpool/ROOT/cs@pre-maintenance-20240115 > \
/root/change-records/maintenance-20240115.diff
# If maintenance went wrong: rollback
zfs rollback rpool/ROOT/cs@pre-maintenance-20240115
eBPF for real-time change tracking
eBPF sees changes as they happen. During a maintenance window, run eBPF monitors to capture a complete record of every command executed, every file written, every network connection made.
# Start a complete change audit session
mkdir -p /root/change-audit/$(date +%Y%m%d)
AUDIT_DIR="/root/change-audit/$(date +%Y%m%d)"
# Log every command executed
execsnoop-bpfcc -T > "$AUDIT_DIR/exec.log" &
EXEC_PID=$!
# Log every file opened for writing
opensnoop-bpfcc -T | grep -E "O_WRONLY|O_RDWR" > "$AUDIT_DIR/writes.log" &
WRITE_PID=$!
# Log every network connection
tcpconnect-bpfcc -T > "$AUDIT_DIR/netconn.log" &
NET_PID=$!
echo "Change audit running. PIDs: exec=$EXEC_PID writes=$WRITE_PID net=$NET_PID"
# ... perform maintenance ...
# Stop the audit
kill $EXEC_PID $WRITE_PID $NET_PID
# Review what happened
wc -l "$AUDIT_DIR"/*.log
head -50 "$AUDIT_DIR/exec.log"
Verification — proving a kldload system is unmodified
After installation, you can verify that every installed package is unmodified from the vendor's original. Each package manager provides a verification command that compares installed files against the package metadata (checksums, permissions, ownership). If an attacker modified a binary, replaced a library, or changed a configuration file, package verification will flag it.
RPM verification (CentOS, Rocky, Fedora, RHEL)
rpm -Va verifies every installed file against the RPM database.
It checks size, checksum (SHA-256), permissions, ownership, modification time, and
symlink targets. Any discrepancy is flagged.
# Verify ALL installed packages
rpm -Va 2>/dev/null
# Output format:
# S.5....T. c /etc/ssh/sshd_config (size, checksum, time changed — config file)
# ..5....T. /usr/bin/suspicious (checksum and time changed — ALERT)
#
# Flags: S=size, M=mode, 5=checksum, D=device, L=link, U=user, G=group, T=time
# "c" means config file (expected to change)
# Verify a specific package
rpm -V openssh-server
# Verify only non-config files (find actual tampering)
rpm -Va 2>/dev/null | grep -v "^.*c /"
# Verify that RPM database itself has not been tampered with
rpm -qa --qf '%{SIGPGP:pgpsig}\n' | grep -c "Key ID"
# List packages with failed GPG signatures
rpm -qa --qf '%{NAME}-%{VERSION}-%{RELEASE} %{SIGPGP:pgpsig}\n' | grep -i "not"
# Reinstall a package that fails verification
dnf reinstall openssh-server
dpkg/debsums verification (Debian, Ubuntu)
debsums verifies installed file checksums against the package database.
dpkg -V provides a similar check. Both catch modified binaries and libraries.
# Install debsums if not present
apt install -y debsums
# Verify ALL installed packages
debsums -c # show only changed files
# Verify a specific package
debsums openssh-server
# Show all files that have been modified
debsums -ac 2>/dev/null
# Alternative: dpkg --verify (similar to rpm -Va)
dpkg -V
# Verify package signatures
apt-key list
apt-cache policy openssh-server
# Check that apt sources have valid signatures
apt update 2>&1 | grep -i "NO_PUBKEY\|EXPKEYSIG"
# Reinstall a package that fails verification
apt install --reinstall openssh-server
pacman verification (Arch)
# Verify all installed packages
pacman -Qkk 2>/dev/null | grep -v "0 altered"
# Verify a specific package
pacman -Qkk openssh
# Check package signatures
pacman -Qkq 2>/dev/null | sort
# List packages that were not installed from a repository (manual/AUR)
pacman -Qm
# Verify trust of keyring
pacman-key --verify
# Reinstall a package that fails verification
pacman -S openssh
File checksum baselines
For files that package verification does not cover (custom scripts, configuration managed outside packages), create a checksum baseline after install and verify against it periodically.
# Create a baseline of critical files after install
find /usr/sbin /usr/bin /usr/lib -type f -exec sha256sum {} \; \
> /root/baseline-checksums-$(date +%Y%m%d).txt
# Store the baseline on a held ZFS snapshot
zfs snapshot rpool/ROOT/cs@baseline-$(date +%Y%m%d)
zfs hold baseline rpool/ROOT/cs@baseline-$(date +%Y%m%d)
# Verify against baseline (run periodically via cron)
sha256sum -c /root/baseline-checksums-20240115.txt 2>/dev/null | grep FAILED
# Compare current binaries to the baseline snapshot
diff <(sha256sum /usr/sbin/*) \
<(cd /rpool/ROOT/cs/.zfs/snapshot/baseline-20240115 && sha256sum usr/sbin/*)
# Verify kernel modules are signed
for mod in $(lsmod | awk 'NR>1{print $1}'); do
modinfo "$mod" 2>/dev/null | grep -q "sig_id" && echo "$mod: SIGNED" || echo "$mod: UNSIGNED"
done
Package verification is the most underused security tool in Linux. rpm -Va takes 30 seconds to run and tells you if any binary on the system has been modified from the vendor's original. debsums -c does the same on Debian. Most people never run these commands. On a kldload system, you can run package verification against the installed system and then cross-reference with zfs diff against the baseline snapshot. If a binary changed but the package says it should not have — that is a compromise. If a config file changed and git does not have a commit for it — that is unauthorized modification. Two tools, two minutes, complete system integrity verification.
Supply chain audit — what ships in the repo
Build scripts only
The repository contains bash scripts, package lists (.txt files with package names),
HTML/CSS for the web UI, and Python for the installer backend. Zero compiled binaries.
Zero pre-built packages. Zero vendor blobs.
Darksite caches are not committed
All live-build/darksite-*-cache/ directories are in .gitignore.
A fresh git clone contains no packages. They are downloaded from official
upstream mirrors during ./deploy.sh build.
Package verification by distro
Every package manager verifies integrity automatically during install. This happens at darksite build time (when packages are fetched) and again at install time (when packages are installed into the target system).
RPM (CentOS, Rocky, Fedora, RHEL)
GPG signatures on every .rpm file. SHA-256 checksums in repository metadata.
dnf verifies both automatically. Repository metadata is GPG-signed by the distro.
APT (Debian, Ubuntu)
GPG-signed Release file per repository. SHA-256 checksum for every .deb
in the Packages index. apt verifies the full chain: Release signature →
Packages hash → individual .deb hash.
pacman (Arch)
Package signatures verified against the Arch Linux keyring. SHA-256 checksums embedded
in the sync database. pacman checks both before extracting any package.
apk (Alpine)
Signed APKINDEX.tar.gz per repository. Each .apk includes
checksums verified by apk on install. Repository signing keys ship with
the alpine-keys package.
Build from source — step by step
The most complete audit is building the ISO yourself. A clean build fetches every
package directly from official mirrors — the same mirrors your distro uses for
apt update or dnf update.
Clone the repository
The repo is 100% source. No LFS, no submodules, no binary artifacts.
git clone https://github.com/kldload/kldload-free
cd kldload-free
Read the build scripts
Every build step is in readable bash. The entry point is deploy.sh,
which calls darksite builders and then builder/build-iso.sh.
# The entire build pipeline
cat deploy.sh
cat builder/build-iso.sh
# Package lists (one package name per line)
cat build/darksite-debian/config/package-sets/target-base.txt
cat build/darksite-arch/config/package-sets/target-base.txt
cat build/darksite-alpine/config/package-sets/target-base.txt
Build the ISO
This fetches all packages from official mirrors, builds ZFS kernel modules, assembles the darksites, and produces the bootable ISO. Requires podman or docker.
# Full build from scratch
./deploy.sh clean
./deploy.sh builder-image
PROFILE=desktop ./deploy.sh build
Verify the ISO checksum
The build produces a SHA-256 checksum alongside the ISO.
cat live-build/output/*.sha256
sha256sum live-build/output/*.iso
Verify darksite packages against upstream
Compare any cached package checksum against the official mirror to prove they are identical.
# Debian/Ubuntu: checksums are in the Packages index
grep -A2 "^Package: zfsutils-linux" \
live-build/darksite-debian-cache/apt/dists/trixie/main/binary-amd64/Packages
# Arch: checksums in the sync database
sha256sum live-build/darksite-arch-cache/pkg/zfs-linux-*.pkg.tar.zst
# Alpine: checksums in APKINDEX
sha256sum live-build/darksite-alpine-cache/apk/zfs-*.apk
# RPM: verify GPG signatures
rpm -K live-build/darksite-fedora-cache/rpm/*.rpm 2>/dev/null | head
Darksite integrity model
Frozen snapshot, not a live mirror
Each darksite is a point-in-time snapshot of official upstream packages. During installation, the installer uses only the darksite — no internet mirror is contacted. The package versions installed are exactly the versions that were cached at build time.
After installation, the system's package manager is configured with
standard internet repositories. The user runs apt update && apt upgrade,
dnf update, or pacman -Syu to get current at their own pace.
This is how darksites have always worked — in classified environments, on ships, in air-gapped facilities. You build the package set in a controlled environment with network access. You verify it. You burn it. You carry it to the target. The target never touches the internet. kldload automates what darksite operators have been doing by hand for decades. The difference is it takes a ./deploy.sh build instead of a week of manual package resolution.
RPM darksites
CentOS, Rocky, Fedora: dnf download --resolve --alldeps fetches packages
with full dependency trees. Repository metadata (repodata/) is generated with
createrepo_c. Served via file:// during install.
APT darksites
Debian, Ubuntu: packages are downloaded with apt-get download inside a
native container. Full dists/ + pool/ structure with signed
Release files. Served on localhost:3142 (Debian) and :3143 (Ubuntu).
pacman darksite
Arch: pacman -Sw downloads packages with dependency resolution.
Sync databases (core.db, extra.db, archzfs.db) are
cached alongside packages. Served via file:// during install.
apk darksite
Alpine: apk fetch --recursive downloads packages with dependencies.
APKINDEX.tar.gz is generated with apk index.
Served via file:// during install.
Reporting — compliance reports and security assessments
kldload does not ship a reporting product — it ships the data sources that feed any reporting tool. The commands below generate compliance reports, audit summaries, and security posture assessments using standard tools.
Daily audit summary
Generate a daily audit summary that covers authentication events, system changes, and security alerts. Run via cron or systemd timer.
#!/bin/bash
# /usr/local/bin/daily-audit-report.sh
REPORT="/var/log/audit/daily-report-$(date +%Y%m%d).txt"
echo "=== DAILY AUDIT REPORT $(date) ===" > "$REPORT"
echo "" >> "$REPORT"
echo "--- Authentication Summary ---" >> "$REPORT"
aureport --auth --summary >> "$REPORT" 2>/dev/null
echo "" >> "$REPORT"
echo "--- Failed Logins ---" >> "$REPORT"
aureport --auth --failed >> "$REPORT" 2>/dev/null
echo "" >> "$REPORT"
echo "--- Privilege Escalation Events ---" >> "$REPORT"
ausearch -k priv_escalation -ts today --format text >> "$REPORT" 2>/dev/null
echo "" >> "$REPORT"
echo "--- File Integrity Changes ---" >> "$REPORT"
ausearch -k identity -ts today --format text >> "$REPORT" 2>/dev/null
echo "" >> "$REPORT"
echo "--- ZFS Pool Status ---" >> "$REPORT"
zpool status >> "$REPORT"
echo "" >> "$REPORT"
echo "--- ZFS Snapshot Count ---" >> "$REPORT"
zfs list -t snapshot -o name | wc -l >> "$REPORT"
echo "" >> "$REPORT"
echo "--- Filesystem Changes (last 24h) ---" >> "$REPORT"
LATEST_SNAP=$(zfs list -t snapshot -o name -s creation rpool/ROOT/cs | \
grep hourly | tail -24 | head -1)
if [ -n "$LATEST_SNAP" ]; then
zfs diff "$LATEST_SNAP" 2>/dev/null | wc -l >> "$REPORT"
echo "files changed since $LATEST_SNAP" >> "$REPORT"
fi
echo "" >> "$REPORT"
echo "--- Package Verification ---" >> "$REPORT"
rpm -Va 2>/dev/null | grep -v "^.*c /" | wc -l >> "$REPORT"
echo "non-config files with verification failures" >> "$REPORT"
echo "Report saved to $REPORT"
Compliance posture check
Verify that audit controls are properly configured. Run before an audit engagement to identify gaps.
#!/bin/bash
# /usr/local/bin/compliance-check.sh
echo "=== COMPLIANCE POSTURE CHECK ==="
# Check auditd is running
echo -n "[1] auditd running: "
systemctl is-active auditd 2>/dev/null && echo "PASS" || echo "FAIL"
# Check audit rules are loaded
echo -n "[2] audit rules loaded: "
RULES=$(auditctl -l 2>/dev/null | wc -l)
[ "$RULES" -gt 5 ] && echo "PASS ($RULES rules)" || echo "FAIL ($RULES rules)"
# Check audit log exists and is being written
echo -n "[3] audit log active: "
[ -f /var/log/audit/audit.log ] && echo "PASS ($(wc -l < /var/log/audit/audit.log) lines)" || echo "FAIL"
# Check ZFS pool health
echo -n "[4] ZFS pool healthy: "
zpool status -x 2>/dev/null | grep -q "all pools are healthy" && echo "PASS" || echo "FAIL"
# Check ZFS snapshots exist
echo -n "[5] ZFS snapshots exist: "
SNAPS=$(zfs list -t snapshot -o name 2>/dev/null | wc -l)
[ "$SNAPS" -gt 1 ] && echo "PASS ($SNAPS snapshots)" || echo "FAIL"
# Check sanoid is running (automatic snapshots)
echo -n "[6] sanoid timer active: "
systemctl is-active sanoid.timer 2>/dev/null && echo "PASS" || echo "FAIL"
# Check SSH config
echo -n "[7] SSH root login disabled: "
grep -q "^PermitRootLogin no" /etc/ssh/sshd_config 2>/dev/null && echo "PASS" || echo "FAIL"
echo -n "[8] SSH password auth disabled: "
grep -q "^PasswordAuthentication no" /etc/ssh/sshd_config 2>/dev/null && echo "PASS" || echo "WARN (enabled)"
# Check time sync
echo -n "[9] time synchronization: "
timedatectl show -p NTPSynchronized --value 2>/dev/null | grep -q "yes" && echo "PASS" || echo "FAIL"
# Check firewall
echo -n "[10] nftables active: "
systemctl is-active nftables 2>/dev/null && echo "PASS" || echo "FAIL"
# Check journal persistence
echo -n "[11] persistent journal: "
[ -d /var/log/journal ] && echo "PASS" || echo "FAIL"
# Check WireGuard (if configured)
echo -n "[12] WireGuard interfaces: "
WG=$(wg show interfaces 2>/dev/null | wc -w)
echo "$WG interfaces"
echo ""
echo "=== END POSTURE CHECK ==="
Security posture assessment
A deeper assessment that checks kernel security parameters, file permissions, and system hardening. Useful for security reviews and penetration test preparation.
# Kernel security parameters
sysctl kernel.randomize_va_space # should be 2 (full ASLR)
sysctl kernel.dmesg_restrict # should be 1 (non-root cannot read dmesg)
sysctl kernel.kptr_restrict # should be 1 or 2
sysctl net.ipv4.conf.all.rp_filter # should be 1 (reverse path filtering)
sysctl net.ipv4.tcp_syncookies # should be 1 (SYN flood protection)
# SUID binaries (potential privilege escalation vectors)
find / -perm -4000 -type f 2>/dev/null | sort
# World-writable files (excluding /tmp and /proc)
find / -xdev -perm -o+w -type f 2>/dev/null | grep -v -E "^/(tmp|proc|sys|dev)"
# Unowned files
find / -xdev -nouser -o -nogroup 2>/dev/null
# Listening services
ss -tlnp
# Loaded kernel modules
lsmod | wc -l
echo "kernel modules loaded"
# Check for unauthorized cron jobs
for user in $(cut -d: -f1 /etc/passwd); do
crontab -l -u "$user" 2>/dev/null | grep -v "^#" | grep -v "^$" && \
echo " ^ cron for: $user"
done
# Check systemd timers
systemctl list-timers --all
# Check for containers running as root
podman ps --format "{{.Names}} {{.User}}" 2>/dev/null
docker ps --format "{{.Names}} {{.User}}" 2>/dev/null
Network access audit
Does this system phone home? No. There is no telemetry, no analytics, no license check, no heartbeat. Verify it yourself.
# Capture all outbound traffic for 60 seconds
timeout 60 tcpdump -i any -nn -q 'not src host 127.0.0.1' \
-w /root/network-audit-$(date +%Y%m%d).pcap
# Analyze the capture
tcpdump -r /root/network-audit-*.pcap -nn | \
awk '{print $5}' | cut -d. -f1-4 | sort | uniq -c | sort -rn
# Real-time: what DNS queries is the system making?
tcpdump -i any -nn port 53
# What processes have outbound connections RIGHT NOW?
ss -tnp | grep ESTAB | awk '{print $5, $6}'
# eBPF: log every outbound connection for the next hour
timeout 3600 tcpconnect-bpfcc -T > /root/outbound-audit.log
tcpdump -i any during install. You will see zero packets.Reproducibility
Same source + same mirrors = same ISO
Two builds from the same commit, run against the same mirror state, will produce darksites with identical packages. The ISO layout is deterministic. Timestamps and build metadata will differ, but the installed packages are byte-identical.
For full bit-for-bit reproducibility, pin the mirror snapshot
(Debian has snapshot.debian.org, Arch has archive.archlinux.org) and set
SOURCE_DATE_EPOCH in the build environment.
Reproducibility is the ultimate audit. If you can build the same ISO from the same source and get the same packages, you have proven the build pipeline is deterministic. There is no hidden step. No "trust us, the binary matches the source." You built it. You compared it. It matches. That is the only trust model that survives a real security review.
For compliance environments (FedRAMP, FIPS, PCI-DSS): the build process is one Dockerfile, one shell script, and a list of package names. The entire supply chain is auditable in an afternoon. The output is a checksummed ISO. The install is offline. The deployed system's packages can be verified against the upstream mirrors with rpm -V or debsums. Every link in the chain is verifiable with standard tools.
Complete audit checklist
If you want to verify kldload end-to-end — from source code to running system — here is the complete checklist:
Source code
Every file is readable. No obfuscation, no minification, no compiled code.
deploy.sh — build orchestration (darksite builds, ISO assembly, VM deploy)
builder/build-iso.sh — rootfs bootstrap, squashfs, EFI, xorriso
build/darksite-*/build-darksite-*.sh — per-distro package downloaders
kldload-install-target — installer entry point
lib/bootstrap.sh — per-distro bootstrap functions (dnf, apt, pacman, apk)
kldload-webui — single-file Python web UI backend
Build environment
The builder container needs internet to fetch packages from upstream mirrors. You can inspect exactly what it downloads by running the darksite build scripts individually and watching the output. Every URL points to an official distro mirror.
# Watch what gets downloaded
./deploy.sh build-debian-darksite 2>&1 | grep -i download
./deploy.sh build-arch-darksite 2>&1 | grep -i download
./deploy.sh build-alpine-darksite 2>&1 | grep -i download
Installed system
# 1. Verify all packages are unmodified
rpm -Va 2>/dev/null | grep -v "^.*c /" # RPM distros
debsums -c 2>/dev/null # Debian/Ubuntu
pacman -Qkk 2>/dev/null | grep -v "0 altered" # Arch
# 2. Verify ZFS pool integrity
zpool status
zpool scrub rpool && sleep 5 && zpool status rpool
# 3. Verify auditd is running and logging
systemctl status auditd
auditctl -l | wc -l
# 4. Verify snapshots exist
zfs list -t snapshot | wc -l
# 5. Verify no unexpected network connections
ss -tnp | grep ESTAB
# 6. Verify no unexpected listening services
ss -tlnp
# 7. Verify no unexpected SUID binaries
find / -perm -4000 -type f 2>/dev/null | wc -l
# 8. Run package verification + zfs diff for full integrity check
BASELINE=$(zfs list -t snapshot -o name -s creation rpool/ROOT/cs | head -2 | tail -1)
echo "Changes since baseline ($BASELINE):"
zfs diff "$BASELINE" 2>/dev/null | wc -l