Bulk audit log export
Stream the audit log for SIEM or analysis
For compliance, security analytics, or just backups, you'll want to export the entire audit log periodically. Streaming approach to avoid memory issues.
Daily export script
#!/bin/bash
# /etc/cron.daily/audit-export
set -e
DATE=$(date +%Y-%m-%d)
TARGET="s3://olympus-audit-archive/$DATE.csv.gz"
psql -d olympus -c "
COPY (
SELECT
id, created_at, identity_id, actor_id, actor_type, event_type, outcome,
source_ip, user_agent, metadata
FROM security_audit
WHERE created_at >= '$DATE' AND created_at < ('$DATE'::date + INTERVAL '1 day')
ORDER BY created_at
) TO STDOUT WITH CSV HEADER
" | gzip | aws s3 cp - "$TARGET"
# Optional: write a manifest
echo "{ \"date\": \"$DATE\", \"size\": $(aws s3 ls $TARGET --summarize | tail -1 | awk '{print $3}'), \"rows\": ... }" > /var/lib/olympus/last-export.json0 5 * * * deploy /etc/cron.daily/audit-export >> /var/log/olympus/audit-export.log 2>&1Daily at 05:00. Old days compressed.
To Object Storage with Object Lock
For tamper-evident archival:
aws s3 cp - "$TARGET" \
--object-lock-mode COMPLIANCE \
--object-lock-retain-until-date $(date -d "+7 years" -I)Once written, S3 won't allow modification for 7 years. Even root can't delete (in compliance mode).
Streaming to SIEM
If you have a SIEM that accepts JSONL:
psql -d olympus -c "
COPY (
SELECT row_to_json(t) FROM (
SELECT id, created_at, identity_id, actor_id, event_type, outcome, source_ip, metadata
FROM security_audit
WHERE created_at >= NOW() - INTERVAL '1 day'
) t
) TO STDOUT
" | curl -X POST $SPLUNK_HEC \
-H "Authorization: Splunk $TOKEN" \
-H "Content-Type: application/x-ndjson" \
--data-binary @-Real-time delivery to SIEM. Each line is an event.
Better: use Vector for continuous streaming, not batched.
Real-time via Debezium
For sub-second latency:
# debezium connector
name: olympus-audit-cdc
config:
connector.class: io.debezium.connector.postgresql.PostgresConnector
database.hostname: ciam-postgres
database.dbname: olympus
table.include.list: public.security_audit
topic.prefix: olympusPostgres logical replication → Kafka → consumers.
Heavier infra; better latency. For most: daily batch is enough.
Privacy considerations
Audit data is PII-heavy (IPs, identity_ids, user agents).
If exporting to external SIEM:
- Encrypt in transit (TLS).
- Encrypt at rest in destination.
- Access controls in SIEM match Olympus's.
- Retention matches your policy.
Document in your privacy policy.
Anonymized export
For analysts / researchers who don't need PII:
COPY (
SELECT
event_type, outcome,
DATE_TRUNC('hour', created_at) AS hour, -- aggregated, not exact
digest(identity_id::text, 'sha256') AS hashed_user,
NULL AS source_ip,
NULL AS user_agent
FROM security_audit
WHERE created_at >= ...
) TO STDOUT WITH CSV;Less granular but usable for stats.
Manifest
For each export, write a manifest:
{
"date": "2026-05-13",
"rows": 12345,
"first_id": "01HQ-...",
"last_id": "01HQ-...",
"size_bytes": 1234567,
"format": "csv.gz",
"fields": ["id", "created_at", "identity_id", ...],
"exported_at": "2026-05-14T05:00:00Z",
"exporter_version": "v1"
}Helps consumers know what they have.
Checksum
For integrity:
sha256sum exported.csv.gz > exported.csv.gz.sha256
aws s3 cp exported.csv.gz.sha256 s3://...Verify before processing.
Restore from archive
If main DB loses data (unlikely but possible), restore audit log from archive:
aws s3 cp s3://olympus-audit-archive/$DATE.csv.gz - | gunzip | psql -c "COPY security_audit FROM STDIN WITH CSV HEADER"Doesn't restore the present state, but provides historical record.
Periodic verification
# Weekly: verify last 7 days of exports exist
for i in $(seq 1 7); do
DATE=$(date -d "-$i days" +%Y-%m-%d)
aws s3 ls s3://olympus-audit-archive/$DATE.csv.gz || alert "Missing: $DATE"
doneDetects silently failing exports.
Compliance value
Auditors love regular exports:
- Demonstrates retention.
- Demonstrates immutability.
- Demonstrates "we have what we say we have."
Make exports a documented practice.