Add app
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# Received logs and rsyslog spool are runtime state, not source.
|
||||
logs/*
|
||||
spool/*
|
||||
!logs/.gitkeep
|
||||
!spool/.gitkeep
|
||||
@@ -0,0 +1,17 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
#
|
||||
# rsyslog as a remote log/audit collector (RECEIVE side only) for the diskless
|
||||
# fleet serving stack. Booted hosts forward auditd + syslog here; records land
|
||||
# under /var/log/remote/<sender-ip>/. See rsyslog.conf + README.md.
|
||||
|
||||
FROM alpine:3.20
|
||||
|
||||
RUN apk add --no-cache rsyslog tzdata \
|
||||
&& mkdir -p /var/log/remote /var/spool/rsyslog
|
||||
|
||||
COPY rsyslog.conf /etc/rsyslog.conf
|
||||
|
||||
EXPOSE 514/tcp
|
||||
# Foreground (-n), our config only (-f). rsyslog logs its own startup to stderr,
|
||||
# captured by `docker compose logs audit`.
|
||||
ENTRYPOINT ["rsyslogd", "-n", "-f", "/etc/rsyslog.conf"]
|
||||
@@ -0,0 +1,82 @@
|
||||
# Audit / log collector (receive side)
|
||||
|
||||
An **rsyslog** container that receives logs forwarded by booted diskless hosts
|
||||
and writes them to durable, per-source-host files. Diskless hosts run on an
|
||||
immutable erofs root with a **RAM-only volatile overlay**, so their local
|
||||
`/var/log/audit/audit.log` is lost on every reboot — this gives auditd somewhere
|
||||
permanent to offload to. It implements the remote-audit-offload that STIG
|
||||
deviation #6 (`docs/stig-deviations.md`) lists as future hardening.
|
||||
|
||||
```
|
||||
booted host serving host
|
||||
┌───────────────────────────┐ ┌──────────────────────────────┐
|
||||
│ auditd → audisp-syslog │ TCP/514 │ audit (this container) │
|
||||
│ → rsyslog (forward) ├──────────►│ rsyslogd → /var/log/remote/ │
|
||||
│ to ${AUDIT_HOST} │ syslog │ <sender-ip>/audit.log │
|
||||
└───────────────────────────┘ │ <sender-ip>/messages.log │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
## What this provides (the receiver)
|
||||
|
||||
- Listens on **TCP/514** (plain syslog). Disk-assisted queue so bursts / brief
|
||||
stalls don't drop records.
|
||||
- Writes per **sender IP** (not the claimed hostname): audit records →
|
||||
`audit.log`, everything else → `messages.log`, under
|
||||
`./logs/<ip>/` (bind-mounted, inspectable on the host).
|
||||
- Host-networked, so hosts reach it at `${AUDIT_HOST}` (resolved to the serving
|
||||
host by the stack's dnsmasq), matching the boot/NVMe DNS pattern.
|
||||
|
||||
Run it with the rest of the stack:
|
||||
|
||||
```bash
|
||||
cd serving
|
||||
docker compose build audit
|
||||
docker compose up -d audit
|
||||
docker compose logs -f audit # startup + connection messages
|
||||
tail -F audit-collector/logs/*/audit.log
|
||||
```
|
||||
|
||||
Quick self-test (send a line as if from a host):
|
||||
|
||||
```bash
|
||||
logger -n 127.0.0.1 -P 514 -T -t audispd "test audit record"
|
||||
ls audit-collector/logs/ # a 127.0.0.1/ dir appears
|
||||
```
|
||||
|
||||
## What's still needed (the sender — in the image)
|
||||
|
||||
This container only **receives**. To make hosts actually send, the hardened
|
||||
image needs (not yet wired — keep `no per-host state`, use the generic
|
||||
`${AUDIT_HOST}` name, never a per-host address):
|
||||
|
||||
1. **auditd → syslog**: enable the `audisp-syslog` plugin
|
||||
(`/etc/audit/plugins.d/syslog.conf`: `active = yes`), so audit events also go
|
||||
to the local syslog socket.
|
||||
2. **rsyslog forward rule**: a drop-in (e.g. `/etc/rsyslog.d/60-fleet-remote.conf`)
|
||||
forwarding to the collector with a disk-assisted action queue, so a network
|
||||
blip doesn't drop records:
|
||||
|
||||
```rsyslog
|
||||
*.* action(type="omfwd" target="audit.fleet.lab" port="514" protocol="tcp"
|
||||
queue.type="LinkedList" queue.fileName="fwdq"
|
||||
queue.maxDiskSpace="256m" queue.saveOnShutdown="on"
|
||||
action.resumeRetryCount="-1")
|
||||
```
|
||||
3. Bake `audit.fleet.lab` (the generic name) — DNS resolves it per environment.
|
||||
|
||||
Once the sender is wired, flip STIG deviation #6 in `docs/stig-deviations.md`
|
||||
from "local-only" to "offloading to ${AUDIT_HOST}" and the
|
||||
`rsyslog_remote_loghost` rule should pass.
|
||||
|
||||
## Production hardening (lab simplifications to revisit)
|
||||
|
||||
The lab stack is **plaintext TCP**, mirroring the HTTP-boot/no-TLS posture. For
|
||||
a secured fleet:
|
||||
|
||||
- **TLS**: rsyslog `gtls` driver (mutual auth with the boot-infra CA), or
|
||||
**RELP + TLS** (`imrelp`/`omrelp`) for guaranteed, acknowledged delivery.
|
||||
- **Restrict senders** by subnet (`$AllowedSender`) or firewall :514 to the
|
||||
boot LAN.
|
||||
- Add log rotation/retention (logrotate sidecar or rsyslog `outchannel`) and
|
||||
ship to long-term storage / SIEM.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Remote audit / syslog collector for the diskless fleet (LAB).
|
||||
#
|
||||
# Receives logs forwarded by booted diskless hosts over plain TCP/514 and writes
|
||||
# them per-source-host. Diskless hosts have an immutable, RAM-only overlay root,
|
||||
# so their local audit log dies on reboot — this gives auditd somewhere durable
|
||||
# to offload to (STIG remote-audit-offload, deviation #6 in docs/stig-deviations.md).
|
||||
#
|
||||
# Lab posture: PLAINTEXT TCP, mirroring the HTTP-boot/no-TLS lab stack. For
|
||||
# production switch to TLS (gtls driver) or RELP+TLS and restrict senders by
|
||||
# subnet — see README.md.
|
||||
|
||||
global(workDirectory="/var/spool/rsyslog")
|
||||
|
||||
# --- intake: plain TCP syslog ---
|
||||
module(load="imtcp" maxSessions="1000")
|
||||
input(type="imtcp" port="514")
|
||||
# Some senders only speak UDP; uncomment to also accept it (lossy, no queue).
|
||||
#module(load="imudp")
|
||||
#input(type="imudp" port="514")
|
||||
|
||||
# Disk-assisted main queue so a brief disk stall or a burst of records doesn't
|
||||
# drop audit events (the records keep flowing to spool, then drain to disk).
|
||||
main_queue(
|
||||
queue.type="LinkedList"
|
||||
queue.filename="mainq"
|
||||
queue.maxdiskspace="1g"
|
||||
queue.saveOnShutdown="on"
|
||||
)
|
||||
|
||||
# --- per-source-host files (keyed by SENDER IP, not the claimed hostname) ---
|
||||
template(name="HostAudit" type="string" string="/var/log/remote/%FROMHOST-IP%/audit.log")
|
||||
template(name="HostMessages" type="string" string="/var/log/remote/%FROMHOST-IP%/messages.log")
|
||||
|
||||
# Audit records (audisp-syslog tags them 'audispd'/'audit', or forwards on the
|
||||
# local6 facility) go to a dedicated per-host audit.log; everything else to
|
||||
# messages.log. Files/dirs are created 0640/0750 (audit logs aren't world-read).
|
||||
if ($programname == 'audispd')
|
||||
or ($programname startswith 'audit')
|
||||
or ($syslogfacility-text == 'local6') then {
|
||||
action(type="omfile" dynaFile="HostAudit"
|
||||
dirCreateMode="0750" fileCreateMode="0640")
|
||||
stop
|
||||
}
|
||||
|
||||
action(type="omfile" dynaFile="HostMessages"
|
||||
dirCreateMode="0750" fileCreateMode="0640")
|
||||
Reference in New Issue
Block a user