Olympus Docs
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 areaEvidence 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.txt

Quarterly 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 HEADER

Send 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 HEADER

If 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.

On this page