Skip to content

Stage 2: ship credentials-service worker (arch.md §15.1) — Lambda + mTLS to signer #91

@hanwencheng

Description

@hanwencheng

Deferred from: stage 1 (#89). The bucket + role separation (arch.md §17 per-data-class layer) is shipped in this PR; the worker itself is deferred to stage 2.

Background

Per arch.md §15.1 credentials-service:

  • Worker takes a cap-token + (service, plaintext_or_ciphertext)
  • Derives the per-actor KEK via mTLS to the signer (derive_cred_kek(operator_omni, k3_epoch))
  • AES-256-GCM encrypt/decrypt under that KEK
  • PUTs / GETs the blob at s3://$VAULT_BUCKET/bots/<actor_omni>/credentials/<service>.enc
  • Independent re-verify of the cap against the on-chain ScopeContract before any S3 / signer touch

What's already in place (no work needed)

Piece State Source
Per-actor KEK derivation shipped (client-side) crates/agentkeys-core/src/s3_backend.rs:222 — derives via signer's /dev/sign-message
AES-256-GCM envelope (v1 + v2 shapes) shipped (client-side) same file, store_credential / read_credential
OIDC AssumeRoleWithWebIdentity → STS session w/ agentkeys_actor_omni tag shipped in this PR crates/agentkeys-broker-server/src/handlers/oidc.rs
$VAULT_BUCKET separate from $EMAIL_BUCKET per arch.md §17 shipped in this PR scripts/provision-vault-bucket.sh + scripts/provision-vault-role.sh + scripts/apply-vault-bucket-policy.sh
agentkeys-vault-role with credentials-only inline policy shipped in this PR scripts/provision-vault-role.sh
Bucket policy gated on agentkeys_actor_omni PrincipalTag shipped in this PR scripts/apply-vault-bucket-policy.sh

Today the CLI does the encrypt + direct S3 PUT (acting as the worker). This is functionally correct and produces the same envelope bytes the worker will produce; the architecture violation is purely topological — the encrypt should happen inside the worker, not on the operator's laptop.

What this issue ships

  1. Lambda function (agentkeys-worker-creds per arch.md §28 layout):
    • Endpoint POST /v1/cred/store — takes {cap, service, plaintext}, verifies cap on-chain, derives KEK via signer mTLS, encrypts, PUTs to $VAULT_BUCKET/bots/<actor>/credentials/<service>.enc
    • Endpoint POST /v1/cred/fetch — takes {cap, service}, verifies cap, GETs blob, decrypts via signer-derived KEK, returns plaintext over the TLS-terminated channel
    • Endpoint POST /v1/cred/teardown — takes {cap, target_actor}, verifies cap, wipes bots/<target_actor>/credentials/* prefix
  2. mTLS plumbing: worker presents its attestation-issued cert; signer accepts only attested worker callers (arch.md §11)
  3. CLI flag --credential-backend=sidecar activates the daemon → worker path (today returns "not yet implemented")
  4. Sidecar daemon hop: agentkeys-daemon proxies CLI requests to the worker (cap-token cache + SSE drop event push)

Acceptance

  • agentkeys store openrouter sk-or-... with --credential-backend=sidecar succeeds and the blob lands at s3://$VAULT_BUCKET/bots/<actor>/credentials/openrouter.enc — same path the v2 envelope-v2 CLI writes today
  • agentkeys read openrouter with --credential-backend=sidecar returns the plaintext
  • Cap-mint without the right scope is rejected by the worker (independent re-verify against chain)
  • KEK never leaves the signer enclave; the operator's laptop never sees it

Why this is stage 2

The worker is architecturally required but operationally optional for stage-1 smoke testing. The CLI's direct path produces correct envelope bytes; future migration to the worker is invisible to anyone reading them later (same KEK, same AAD, same nonce shape). Deferring lets stage 1 ship the on-chain registry + sidecar daemon + WebAuthn binding first; stage 2 adds the worker hop.

See arch.md §15.1 + §28 (agentkeys-worker-creds/ layout) for the full design.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions