ironflow-auth-proxy 0.1.24

Auth proxy for ironflow agent pods: swaps an opaque per-step token for the real Claude credential
Documentation

ironflow-auth-proxy

Auth proxy for ironflow agent pods. With K8sEphemeralProvider::auth_proxy, the agent pod never receives the Claude credential: the worker issues an opaque token bound to one run and one step, the pod sends it to this proxy as Authorization: Bearer, and the proxy swaps it for the real credential before relaying the request to https://api.anthropic.com.

  • Anthropic relay: GET and POST under /v1/ only. An unknown, expired or revoked token gets a 401, a path outside /v1/ or a request for another host a 403, any other method a 405. Responses (SSE included) are streamed, rate-limit headers kept.
  • Admin API (/admin/v1/tokens, /admin/v1/tokens/{id}, /admin/v1/runs/{run_id}/tokens): protected by the admin key.
  • Proxied secrets: /r/<host>/<path> relays to https://<host>/<path> with a GitHub, GitLab or other secret the pod never sees (see below).
  • Metrics: GET /metrics (Prometheus text format, same port).
  • Logs (JSON) never contain a token, a credential, a secret, a header or a query string, only the first 12 characters of the token id.

Proxied secrets

A grant can hold a secret instead of the Claude credential (K8sEphemeralProvider::proxied_secret / AgentConfig::proxied_secret). The pod gets an opaque token in <env> and the relay base <proxy>/r in <env>_URL, and calls <env>_URL/<host>/<path> presenting the token as Authorization: Bearer, x-api-key, Private-Token or the password of Authorization: Basic. The proxy checks the host against the grant's allowlist, strips the token and injects the real secret:

Injection Header sent upstream
bearer Authorization: Bearer <secret>
private_token Private-Token: <secret> (GitLab)
x_api_key x-api-key: <secret>
header <name>: <secret>
basic Authorization: Basic base64(<username>:<secret>) (git over https)
  • Allowlist: exact host names (api.github.com) or a leading wildcard (*.example.com, which matches api.example.com but not example.com). No regex, no port, no IP address, https only. A host outside it gets a 403 forbidden_host, so does a Claude token on /r/ and a secret token on the Anthropic API.
  • Path: .., // and percent-encoding are refused (403 forbidden_path); the query string is relayed untouched. Methods: GET, HEAD, POST, PUT, PATCH and DELETE (405 forbidden_method otherwise).
  • Redirects are returned to the pod with their Location, never followed: a redirect never carries the secret to another host.
  • Unknown, expired and revoked tokens get a 401. A revoked grant is dropped with its secret at once; its id is kept until its expiry so logs tell revoked from unknown_token.
  • Each /r/ request logs one secret relay event with host, secret (the name), run_id, step, token (short id), result and upstream_status, and increments ironflow_auth_proxy_requests_total{secret, result}. Results: relayed, forbidden_host, forbidden_path, forbidden_method, unknown_token, expired, revoked, upstream_error, unavailable.
  • The proxy pods need egress to every allowlisted host: see the commented toFQDNs rule of examples/k8s/sandbox/cilium-egress-auth-proxy.yaml.

Environment

Variable Default Description
IRONFLOW_AUTH_PROXY_ADMIN_KEY - (required) Admin key, at least 32 characters, shared with the worker.
IRONFLOW_AUTH_PROXY_LISTEN 0.0.0.0:8080 Listen address.
IRONFLOW_AUTH_PROXY_DATABASE_URL unset (in memory) PostgreSQL URL of the shared token registry. Never logged.
IRONFLOW_SECRET_KEYS - (required with a database) Key ring encrypting the credentials at rest, version:hex entries (64 hex characters each), comma-separated.
IRONFLOW_SECRET_ACTIVE_KEY_VERSION highest version Key version new grants are encrypted with.
IRONFLOW_SECRET_KEY - Legacy single key (version 1), used when IRONFLOW_SECRET_KEYS is unset.
RUST_LOG info Log filter.

Registry backends

In memory (default). Run exactly one replica with the Recreate strategy: a second one would refuse the tokens issued by the first, and a restart drops the tokens of the steps in flight.

PostgreSQL, when IRONFLOW_AUTH_PROXY_DATABASE_URL is set. Several replicas share the registry, with the RollingUpdate strategy, and tokens survive restarts.

  • Stored per token: its SHA-256 (never the token itself), the run, the step, the expiry and the credential, AES-256-GCM encrypted with the key ring. For a proxied secret, its name, injection and allowlist are stored in clear next to it; only the value is encrypted. Startup fails when the database is set without an encryption key.
  • Expired rows are purged every 60 s by every replica (idempotent deletes).
  • A database outage answers 503 (retryable), never 401: a valid token does not look revoked.
  • The network policy must allow egress from the proxy to PostgreSQL: under Cilium, uncomment the 5432 rule of examples/k8s/sandbox/cilium-egress-auth-proxy.yaml.
  • Least privilege: give the proxy a dedicated database or role. Its migrations create the ironflow schema in that database.

Deployment

Image: registry.gitlab.com/thomastartrau/ironflow/ironflow-auth-proxy:<version>, where <version> is the version of this crate. CI builds it from docker/auth-proxy/Dockerfile and publishes it once the version is released; a published tag is never rebuilt, and there is no latest. Manifests and network policies: examples/k8s/sandbox/ (auth-proxy.yaml, cilium-egress-auth-proxy.yaml, networkpolicy-auth-proxy.yaml).