Skip to content

Explore Ory Talos API keys as a complement to agent OAuth2 (local + e2e only) #1348

Description

@getlarge

Summary

Evaluate and prototype Ory Talos (Apache-2.0, Go) as a complementary API-key auth layer for MoltNet — not a replacement for the existing agent OAuth2 (Hydra client_credentials) flow. Talos is a standalone API-key server (issue / verify / revoke / derive) that does no authorization itself; it tells you who (an actor_id) and what coarse scopes, and we keep running the Keto check.

⚠️ Constraint: Talos is not reliably available on Ory Network yet (despite "GA" marketing). Only the self-hosted OSS edition is viable. Scope is local + e2e only — no prod deploy until Ory Network support lands. The whole feature is config-gated so it never registers in production.

Why

The current stack only mints credentials via Hydra. That leaves two gaps Talos fills cleanly:

  1. Self-service personal API keys for a management/console area — humans only have Kratos sessions/cookies today (no scriptable credential); agents would also benefit from long-lived, rotatable keys distinct from their OAuth2 client secret.
  2. Macaroon-derived, attenuated delegation tokens — short-lived tokens with narrowed scopes that map onto MoltNet's agent→sub-agent / task-lease model (Task#claimant least-privilege).

Keto needs zero changes. Talos carries coarse scopes + a team binding; Keto keeps authorizing per-resource off identity_id exactly as it does for OAuth2.

Research (signed diary entries)

Captured in the themoltnet diary (6e4d9948-8ec5-4f59-b82a-3acbc4bbc396), signed by legreffier (1671-B080-99BF-4270):

  • Talos product researchf6a0c26e-cdce-4dee-ba53-6077f420d29d: what Talos is, OSS self-hosting (docker-compose.oss.yaml, SQLite, :4420), the /v2alpha1 API surface (issuedApiKeys, apiKeys:verify, apiKeys:derive, selfRevoke, :rotate, importedApiKeys), actor_id + scopes + metadata model, prefixes (ory_ak / mc), and the "no built-in authz" fact.
  • Design decision76ef6f8b-33f3-465d-b382-a4721e451de3 (references → the research entry): adopt Talos as a complement, alternatives considered, integration seam, two-phase plan, trade-offs.

Integration seam

Single chokepoint is TokenValidator.resolveAuthContext (libs/auth/src/token-validator.ts):

  • Add an ory_ak_-prefix branch alongside the Hydra-introspection branch → call Talos apiKeys:verify → map actor_id + metadata.subject_type onto the same Agent/Human AuthContext union.
  • Everything downstream (Keto subjectSet checks, x-moltnet-team-id resolution in libs/auth/src/plugin.ts) is untouched.
  • Identity binding: actor_id = identity_id; stash subject_type + an optional pinned team_id in metadata.
  • Team scoping (two layers): coarse capability scopes on the key drive requireScopes; fine-grained per-resource authz stays in Keto keyed by identity_id + currentTeamId. A key may be pinned to one team (CI least-privilege); unpinned keys fall back to the existing header + Keto-membership check.

Phase 1 — Personal API keys + management surface

  • Add Talos (OSS edition) to docker-compose.e2e.yaml (and a local compose), config-gated behind TALOS_ADMIN_URL.
  • ory_ak_-prefix branch in TokenValidator → Talos apiKeys:verifyAuthContext (cache like Hydra introspection).
  • New apps/rest-api/src/routes/api-keys.ts: POST (create, returns secret once), GET (list), :rotate, DELETE/revoke — requireAuth, self-service over the caller's own keys. No new Keto namespace.
  • Bind actor_id = identity_id, subject_type + optional team_id in metadata; map scopes to the existing vocabulary so requireScopes works identically.
  • e2e coverage: create key → call an authenticated endpoint with Authorization: Bearer ory_ak_… → Keto check passes/denies as expected.
  • Feature gate: when TALOS_ADMIN_URL is unset, the route + verify branch do not register (prod safety).

Phase 2 — Derived macaroon delegation

  • Use apiKeys:derive to mint short-lived attenuated macaroons (narrowed scopes + task_id/team_id caveat).
  • Map onto Task#claimant least-privilege: an agent derives a scoped, time-boxed token to hand to a daemon/sub-agent for a single task lease.
  • Offline verification via /derivedKeys/jwks.json to avoid per-request introspection.

Trade-offs / risks

  • New stateful service to operate.
  • Two credential issuers (Hydra + Talos) — mitigated by prefix routing (ory_at_ vs ory_ak_) and keeping TokenValidator the single chokepoint.
  • Talos revocation is eventual (cache) — document the lag; lean on selfRevoke (proof-of-possession) for leaked keys.
  • Do NOT invent an ApiKey Keto namespace — authz stays identity-keyed.

Open questions

  • Pin keys to a single team by default, or allow multi-team keys with header-based selection?
  • Shadow each key in Postgres for audit/listing/rate-limit bucketing, or rely solely on Talos issuedApiKeys listing?
  • Phase 2 macaroon caveat schema — reuse the scope vocabulary, or a dedicated caveat grammar?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions