Skip to content

Governance — Bring Your Own Key (BYOK)

AKKO ships with a sovereign AI stack — every model runs locally via the AI Runtime layer (Ollama + Qwen 2.5 family + Nomic Embed). Some workloads still need a hosted provider (long context, vision, advanced tool-calling). The Bring Your Own Key feature lets a platform admin register an external AI provider key (OpenAI, Anthropic, Mistral, Cohere, or any OpenAI-compatible endpoint) on the AKKO AI Gateway without giving up the sovereign default.

Threat model

  • Plaintext in Git — values files commit-by-accident. Mitigated : the key is uploaded only through the cockpit modal, never typed into a YAML file.
  • Plaintext in container logs — accidental print(key). Mitigated : the Pydantic model wraps api_key in SecretStr, so repr(), str(), and model_dump_json() all return **********.
  • Plaintext at rest — disk-level encryption does not protect against a pg_dump exfil. Mitigated : column-level pgp_sym_encrypt(plaintext, master_key, 'cipher-algo=aes256') ; the master key lives in a separate k8s Secret.
  • Plaintext in HTTP responses — admin operators see only the masked form (sk-…XXXX). The plaintext is never returned by any cockpit-backend endpoint.

Architecture (one paragraph)

The cockpit page #ai-models carries a dedicated "Bring Your Own Key" section, visible only to the admin realm role. Adding a key POSTs to /api/governance/byok/keys. The cockpit-backend (akko-cockpit-backend) wraps the plaintext in SecretStr, opens one short Postgres transaction, sets akko.master_key via SET LOCAL, and INSERTs pgp_sym_encrypt(plain, master, 'cipher-algo=aes256') into akko_governance.byok_keys. The local plaintext variable goes out of scope immediately. The future LiteLLM sync worker (PR-D, separate sprint) decrypts inside Postgres and mints a virtual key bound to the rbac_label.

How to enable the vault (operator)

The feature is OFF by default. Two values + two Secrets to wire :

# 1. Generate a 256-bit master key.
kubectl -n akko create secret generic akko-cockpit-backend-byok \
  --from-literal=master-key="$(openssl rand -hex 32)"

# 2. Wire the Postgres DSN to the data instance.
kubectl -n akko create secret generic akko-cockpit-backend-byok-pg \
  --from-literal=dsn="postgresql://akko:$AKKO_PG_PASSWORD@akko-postgresql-data:5432/akko"

# 3. Patch the Helm values.
cat >> my-values.yaml <<EOF
akko-cockpit-backend:
  byok:
    encryptionKey:
      secretName: akko-cockpit-backend-byok
    dsnSecretRef:
      name: akko-cockpit-backend-byok-pg
EOF

# 4. helm upgrade — the akko-init job creates the table idempotently.
helm upgrade akko helm/akko -n akko -f my-values.yaml

How to register a key (admin)

  1. Sign in to the cockpit as alice (akko-admin).
  2. Open the sidebar entry AI Models.
  3. Scroll to the "Bring Your Own Key" section. Click Add provider key.
  4. Pick the provider (OpenAI / Anthropic / Mistral / Cohere / Custom).
  5. Fill a unique Label (e.g. corp-prod-key).
  6. Paste the API key. Submit.

The row appears in the table with the mask sk-…XXXX. The plaintext is never echoed again ; if you lose it, regenerate one on the provider side and re-upload.

How to disable a key without deleting

Toggle the Enable / Disable button in the row's Actions column. The ciphertext stays in the vault — re-enabling restores routing without re-uploading. This is the supported kill-switch during a provider outage.

How to permanently delete a key

Click Delete in the row's Actions column and confirm. The row is removed. There is no recycle bin.

Audit trail

Every BYOK action (add / toggle / delete / list) emits an OCSF class_uid 3008 event (LLM Access Mgmt). Sample :

{
  "class_uid": 3008,
  "type_uid": 300801,
  "activity_id": 1,
  "activity_name": "byok.key.add",
  "actor": { "user": { "uid": "...alice...", "name": "alice", "email_addr": "alice@akko-ai.com", "type_id": 1 } },
  "resources": [ { "type_name": "byok-key", "name": "corp-prod-key" } ],
  "status": "Success"
}

The plaintext api_key field is NEVER serialised in OCSF events.

Key rotation

Annual rotation procedure :

ALTER SYSTEM SET akko.master_key = '<new-key>';
SELECT pg_reload_conf();
UPDATE akko_governance.byok_keys
   SET encrypted_key = akko.reencrypt(encrypted_key, '<old-key>', '<new-key>');

Same helpers as the PII column encryption — see Admin / Encryption.

References

  • ADR-068 — BYOK vault design rationale
  • feedback_layer_first_abstraction — "AI Gateway" / "AI Provider Key" naming
  • feedback_lego_zero_hardcoded — keys via Secret, never inline
  • postgres/init/12-pgcrypto.sqlakko.encrypt_pii / akko.decrypt_pii / akko.reencrypt