GDPR endpoints¶
AKKO v2026.04 ships two GDPR-focused admin endpoints to answer Data Subject
Access Requests (DSAR) programmatically. Both are gated on the akko-admin
realm role and produce an immutable audit record.
See compliance-matrix.md for the full regulation mapping.
Architecture¶
The endpoints live on the akko-catalog-manager Deployment (sharing its
FastAPI app for Keycloak JWKS auth + structured audit + Prometheus
counters). They are exposed via the cockpit nginx proxy:
Cockpit / CLI
│ Authorization: Bearer <admin-JWT>
▼
cockpit nginx → /api/cockpit/catalog-manager/api/admin/users/...
▼
akko-catalog-manager (routes_gdpr.py)
├─ Keycloak admin-cli (DELETE /users/{id})
├─ Trino (DELETE / SELECT on PII tables)
└─ Audit JSON → logs layer (S3 WORM 365 d)
Configuration¶
Two env vars on the Deployment drive the behaviour:
| Env var | Purpose | Default |
|---|---|---|
AKKO_KC_ADMIN_USER |
Keycloak master realm admin username | required |
AKKO_KC_ADMIN_PASSWORD |
corresponding password (Secret) | required |
AKKO_GDPR_PII_TABLES |
Comma-separated list iceberg.<schema>.<table> |
auto-discover iceberg.*.users |
To pin a specific set of tables:
akko-catalog-manager:
extraEnv:
- name: AKKO_GDPR_PII_TABLES
value: "iceberg.banking.customers,iceberg.healthcare.patients,iceberg.retail.customers"
POST /api/admin/users/{user_id}/erasure¶
GDPR Art. 17 — Right to erasure ("right to be forgotten").
curl -X POST https://cockpit.customer.example/api/cockpit/catalog-manager/api/admin/users/42/erasure \
-H "Authorization: Bearer $ADMIN_TOKEN"
Response 202 Accepted:
{
"user_id": "42",
"keycloak_deleted": true,
"tables_processed": [
"iceberg.banking.customers",
"iceberg.healthcare.patients"
],
"tables_failed": [],
"timestamp": 1713542400.0,
"actor": "alice"
}
Side effects:
- The user is removed from Keycloak and all active sessions are revoked.
- Rows matching
user_id = <id>are deleted from every table in the PII table list. - An immutable audit event
USER_ERASEDis written to logs layer and mirrored to the S3 WORM cold bucket (365 d Object Lock COMPLIANCE).
Partial failures are captured in tables_failed[] without aborting the
overall erasure — the receipt is the authoritative record of what was
scrubbed.
GET /api/admin/users/{user_id}/export¶
GDPR Art. 20 — Right to data portability.
curl -X GET https://cockpit.customer.example/api/cockpit/catalog-manager/api/admin/users/42/export \
-H "Authorization: Bearer $ADMIN_TOKEN" \
--output akko-gdpr-export-42.zip
Response : application/zip with
iceberg_banking_customers.csviceberg_healthcare_patients.csviceberg_retail_customers.csvMANIFEST.json(user_id, exported_at, exported_by, schema_version, format)
Observability¶
Prometheus counters exposed by akko-catalog-manager:
akko_gdpr_operations_total{operation="erasure|export", result="success|partial|failure"}
Audit logs (logs layer LogsQL):
Response-time targets¶
| Endpoint | Target p95 | Test data |
|---|---|---|
| erasure (50 PII tables) | < 5 s | 100 k rows / table |
| export (50 PII tables) | < 20 s | 100 k rows / table |
Beyond these thresholds, the endpoint returns 202 and the full work runs asynchronously — polling endpoint in the Sprint 44 roadmap.
Operational runbook¶
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 Unauthorized | Missing / expired admin JWT | Re-authenticate via the cockpit login |
| 500 "Keycloak admin credentials not configured" | AKKO_KC_ADMIN_USER/PASSWORD not set | Create the Secret + restart deployment |
tables_failed[].error = "NOT NULL constraint" |
PII table has FK with cascade off | Split the erasure into multiple sub-deletes, document sequence |
| Audit missing from logs layer | Pod logs not scraped | kubectl -n akko logs deploy/akko-akko-catalog-manager --tail=20 |
Legal notes¶
- The receipt JSON is signed (HMAC-SHA256) with the cluster-wide audit key — proof-of-erasure for the DPO without needing to query the log store.
Right-to-be-forgottendoes not override legal retention obligations (DORA, AMF audit requirements). Consult your DPO before enabling the endpoint in production.