CookbookData & compliance
Export evidence for compliance audits
SOC 2 / ISO 27001 / GDPR, what auditors ask for and how to produce it
When an auditor asks for evidence, you want to hand them a clean bundle quickly. This is a recipe for assembling it.
What auditors typically ask
| Control area | Evidence asked |
|---|---|
| User provisioning | "Show me list of all users with admin access" |
| User deprovisioning | "Show me users deactivated in last 90 days" |
| MFA enforcement | "How many admins have MFA enabled?" |
| Password policy | "What's your password requirement? Show the config" |
| Access reviews | "Quarterly review of admin access, show the audit trail" |
| Audit logging | "Show 30d of login events for user X" |
| Incident response | "Show the runbook + last 12 months of incidents" |
| Encryption | "Where is data encrypted? Key management?" |
| Backups | "Last 30 backup successes, last test restore" |
Export script
#!/bin/bash
# audit-evidence.sh
# Run on the Olympus host. Outputs files into ./audit/
set -e
mkdir -p audit
DATE=$(date +%Y%m%d)
# 1. All identities with admin role
psql -d olympus -c "
COPY (
SELECT id, traits->>'email' AS email, traits->>'role' AS role,
created_at, updated_at, state
FROM identities
WHERE traits->>'role' IN ('admin', 'iam:admin')
ORDER BY created_at
) TO STDOUT WITH CSV HEADER
" > audit/admins-${DATE}.csv
# 2. MFA enrollment among admins
psql -d olympus -c "
COPY (
SELECT i.id, i.traits->>'email' AS email,
EXISTS (SELECT 1 FROM identity_credentials c
WHERE c.identity_id = i.id AND c.type IN ('totp', 'webauthn')) AS has_mfa
FROM identities i
WHERE i.traits->>'role' IN ('admin', 'iam:admin')
) TO STDOUT WITH CSV HEADER
" > audit/admin-mfa-${DATE}.csv
# 3. Deprovisioned users (90 days)
psql -d olympus -c "
COPY (
SELECT id, traits->>'email' AS email, state, updated_at
FROM identities
WHERE state = 'inactive'
AND updated_at > NOW() - INTERVAL '90 days'
) TO STDOUT WITH CSV HEADER
" > audit/deprovisioned-${DATE}.csv
# 4. Login events for last 30 days (sampled)
psql -d olympus -c "
COPY (
SELECT identity_id, source_ip, user_agent, created_at,
event_type, outcome
FROM security_audit
WHERE event_type IN ('login', 'mfa_challenge', 'recovery')
AND created_at > NOW() - INTERVAL '30 days'
ORDER BY created_at DESC
LIMIT 10000
) TO STDOUT WITH CSV HEADER
" > audit/login-events-${DATE}.csv
# 5. Configurations
cp /home/deploy/olympus/config/kratos.yml audit/kratos-config-${DATE}.yml
cp /home/deploy/olympus/config/hydra.yml audit/hydra-config-${DATE}.yml
cp /home/deploy/olympus/config/Caddyfile audit/caddyfile-${DATE}
# 6. Backups
ls -la /var/backups/olympus/ > audit/backups-${DATE}.txt
# 7. Container versions
podman ps --format "table {{ '{{' }}.Names{{ '}}' }}\t{{ '{{' }}.Image{{ '}}' }}\t{{ '{{' }}.Status{{ '}}' }}" > audit/containers-${DATE}.txt
# Bundle
tar czf audit-${DATE}.tar.gz audit/
echo "Written audit-${DATE}.tar.gz"Run it the day before the audit meeting.
Mapping to controls
For SOC 2 specifically, label each artifact with its control:
audit-bundle/
├── CC6.1-logical-access/
│ ├── admins-list.csv
│ └── admin-mfa.csv
├── CC6.2-user-provisioning/
│ └── provisioning-changes.csv
├── CC6.3-user-deprovisioning/
│ └── deprovisioned-90d.csv
├── CC6.6-mfa/
│ ├── kratos-config.yml (highlight `mfa.required: true`)
│ └── admin-mfa.csv
├── CC6.7-encryption/
│ ├── encryption-config.yml
│ └── adr-0006-encryption.md
├── CC7.1-monitoring/
│ └── audit-log-sample.csv
└── A1.2-backups/
└── backups-listing.txtQuarterly access review
-- Quarterly admin access review
COPY (
SELECT
id,
traits->>'email' AS email,
traits->>'role' AS role,
created_at,
(SELECT MAX(created_at) FROM security_audit
WHERE identity_id = i.id AND event_type = 'login') AS last_login
FROM identities i
WHERE traits->>'role' IN ('admin', 'iam:admin')
ORDER BY last_login DESC NULLS FIRST
) TO STDOUT WITH CSV HEADERSend to access reviewers (managers). They mark "keep" or "revoke." Track decisions in a ticket.
Incident log
COPY (
SELECT id, severity, summary, opened_at, resolved_at, postmortem_url
FROM incidents
WHERE opened_at > NOW() - INTERVAL '12 months'
ORDER BY opened_at
) TO STDOUT WITH CSV HEADERIf you don't track incidents in a DB, your ticket system (Linear, Jira) is the source of truth, export from there.
Verification: test restore
Auditors love "show me you actually test backups." Test restore:
# In a sandbox VM (NOT prod):
psql -c "CREATE DATABASE olympus_restore_test"
pg_restore -d olympus_restore_test latest-backup.dump
psql -d olympus_restore_test -c "SELECT COUNT(*) FROM identities"
# Record date, count, screenshot, send to auditor.Document this in audit-bundle/A1.2-backups/restore-test-2026-Q1.md.