Authorize agent-service requests in the access-control-service (JWT authn + per-agent authz)
- Dominant language
- Scala
- Stars
- 314
- Forks
- 187
- Avg merge
- 1d 21h
- Merged PRs (30d)
- 214
Description
### Feature Summary
The `agent-service` currently authenticates its own requests, and the check is both weak and incomplete:
- The only token handling is the **optional** `userToken` in `POST /api/agents`. `agent-service/src/api/auth-api.ts` base64-decodes the JWT payload and checks **only the `exp` claim** — the **signature is never verified** (`validateToken = !isTokenExpired`), so a forged-but-unexpired token passes.
- Every other endpoint — `GET /api/agents`, `GET /api/agents/models`, `PATCH …/settings`, and the `/api/agents/:id/react` WebSocket that drives LLM calls (and provider spend) — performs **no auth at all**.
- At the gateway, the ext-authz `SecurityPolicy` (`bin/k8s/templates/gateway-security-policy.yaml`) targets only the `*-dynamic-routes` HTTPRoute (wsapi / executions-stats / pve). The `*-agent-service-route` is **not** targeted, so `/api/agents` bypasses ext-authz entirely. (Single-node nginx has no ext-authz at all.)
### Proposed Solution or Design
**Authorize requests via `access-control-service`**
- Add an Envoy `SecurityPolicy` targeting the `*-agent-service-route` HTTPRoute, mirroring `gateway-security-policy.yaml`, pointing ext-auth at the access-control-service `/api/auth` and injecting `x-user-id` / `x-user-name` / `x-user-email`.
- Add a branch to `AccessControlResource.authorize()` for `/api/agents…` that verifies the JWT (`parseToken`) and requires `REGULAR`/`ADMIN` (`SessionUser.isRoleOf`) — i.e. restore the gate the old LiteLLM proxy had — returning the trusted identity headers on success and 401/403 otherwise. **Any valid REGULAR/ADMIN user is allowed on any agent path** (allow-all per-agent for now). Token extraction already supports header `Authorization: Bearer`, query `access-token` (for the WS), and body `token`.
- Frontend: attach the JWT to **all** agent-service calls. Today `agentHeaders()` only sets `X-Agent-Workflow-Id`, `fetchModelTypes()` sends nothing, and the `/react` WebSocket carries no token. REST → `Authorization: Bearer`; WS → `?access-token=…` (mirror the workflow WebSocket).
- The agent-service **keeps verifying the JWT itself** — now a real HS256 check using the shared secret read from `auth.conf` (env `AUTH_JWT_SECRET`), replacing the old non-cryptographic decode. This runs **in addition** to the gateway ext-authz, as defense-in-depth so direct / bare-metal access is authenticated too.
- Single-node (nginx, no Envoy): add an `auth_request` subrequest to `/api/auth` (Envoy and nginx both forward the original path so the same `authorize()` branch handles them).
```mermaid
sequenceDiagram
participant FE as Frontend
participant GW as Gateway (Envoy / nginx)
participant AC as access-control-service
participant AS as agent-service
FE->>GW: /api/agents/** — Bearer JWT (or ?access-token for the WS)
GW->>AC: ext_authz → /api/auth (forwards original path + token)
alt valid JWT, REGULAR/ADMIN
AC-->>GW: 200 + x-user-id / x-user-name / x-user-email
GW->>AS: forward request (JWT preserved)
Note over AS: re-verifies the JWT itself (HS256, secret from auth.conf)
AS-->>FE: response
else missing / invalid / wrong role
AC-->>GW: 401 / 403
GW-->>FE: blocked (never reaches agent-service)
end
```
Contributor guide
Research direction
Start by reading agent-service/src/api/auth-api.ts, AccessControlResource.authorize(), and bin/k8s/templates/gateway-security-policy.yaml, then trace the frontend agentHeaders(), fetchModelTypes(), and /react WebSocket calls. Done means agent REST and WebSocket requests carry tokens, gateway and single-node routes enforce the access-control checks, and agent-service performs real HS256 verification for direct access.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- kubernetes, nginx, scala, typescript
- Domain
- api, authentication, authorization, devops, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100