Skip to content

Authorize agent-service requests in the access-control-service (JWT authn + per-agent authz) #5561

@bobbai00

Description

@bobbai00

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).
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
Loading

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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