feat(gateway): require a token on every adapter #67

Merged
rock merged 2 commits from feat/gateway-auth-all-adapters into main 2026-10-27 08:12:53 +00:00
Member

workflow, memory, s3 and iam served requests with no Authorization header. Only sqs and the LLM models checked a token.

Set auth.required true on all five adapters. The dispatcher derives one capability per method from the verb: GET/HEAD/OPTIONS -> :read, anything else -> :write.

Those strings now exist as ZITADEL role keys with the colon, and the riotpiao grant carries them.

workflow, memory, s3 and iam served requests with no Authorization header. Only sqs and the LLM models checked a token. Set auth.required true on all five adapters. The dispatcher derives one capability per method from the verb: GET/HEAD/OPTIONS -> <service>:read, anything else -> <service>:write. Those strings now exist as ZITADEL role keys with the colon, and the riotpiao grant carries them.
poimen added 2 commits 2026-10-27 08:11:45 +00:00
Replace the handwritten Authentik JWT checks with github.com/zitadel/zitadel-go/v3.

What the SDK now owns:
- OIDC discovery (client.Discover) to find issuer and JWKS URI
- remote, auto-refreshing JWKS (rp.NewRemoteKeySet)
- signature, expiry and issuer checks (op.NewAccessTokenVerifier,
  op.VerifyAccessToken)
- the verified claim set and role lookup (oauth.IntrospectionContext,
  IsGrantedRole)

What stays local:
- a multi-audience allow-list, because this gateway is a shared resource
  server and the SDK's oauth.WithJWT accepts exactly one client ID
- an adapter from IntrospectionContext to jwt.MapClaims so the rest of the
  gateway keeps one claim representation

Other changes:
- issuer is configuration-driven; the hardcoded authentik.riotpiao.com
  check is gone
- aud may be a string or an array (ZITADEL sends an array)
- CheckPermissions also reads urn:zitadel:iam:org:project:roles, the claim
  ZITADEL uses for project roles
- requiredCapability accepts a comma-separated list, so llm:inference and
  llm-inference both satisfy the LLM gate
- a five-segment token is reported as an encrypted JWE with the fix in the
  message, instead of failing as a bad signature

Config: auth.zitadelDomain turns the SDK path on; jwksUrl stays supported for
the legacy path and for tests.
feat(gateway): require a token on every adapter
CI / CI (pull_request) Successful in 3m0s
CI / CI (push) Successful in 3m26s
29f5c64fab
workflow, memory, s3 and iam served requests without any Authorization header.
Only sqs and the LLM models checked a token, so four upstreams were reachable
from inside the cluster with no authentication at all.

Set auth.required true on all five adapters. The dispatcher then enforces one
capability per method, derived from the verb
(internal/serviceadapter/router.go:142):

  GET/HEAD/OPTIONS -> "<service>:read"
  anything else    -> "<service>:write"

Those strings must exist as ZITADEL role keys with the colon. The project now
holds memory:read, memory:write, workflow:read, workflow:execute, s3:read,
s3:write, iam:read, iam:write, sqs:read and sqs:write, and the riotpiao user
grant carries them, so a browser session keeps working.

Correct the old comment: ZITADEL does store colon role keys. Verified by listing
the project roles, which returned all eleven colon keys.
rock merged commit 29f5c64fab into main 2026-10-27 08:12:52 +00:00
Sign in to join this conversation.