Agent-Hellboy / Agent-Hellboy/mcp-runtime

Per-audience token isolation via gateway-side token exchange

Abierto
#325 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Go
Estrellas
6
Forks
1
Merge medio
11 h 33 min
PR fusionados (30 d)
13

Descripción

## Summary

Today the platform issues a **single user access token whose `aud` covers all three services** (`[platform-api, runtime-api, analytics-api]`, 15m TTL — `services/platform-api/internal/platformstore/auth.go:355` → `pkg/platformauth/audience.go` `RequiredAudiences()`). The CLI/UI store that one bearer and send it to the gateway, which path-routes to whichever backend. Because the token is multi-audience, a token leaked from one service is replayable against the other two for its whole TTL.

Goal: **per-audience isolation** — a token captured from runtime-api must not work against platform-api.

Raised as a design item in the PR #324 review. The naive fix (per-service token map at login) is architecturally unsound: clients hit a single gateway endpoint and don't know which backend a `/api/v1/*` path routes to, so they can't pick the right token. Decision: **gateway-side token exchange**, clients unchanged.

## Design: gateway-side token exchange

The gateway is **Traefik** (`mcp-sentinel-gateway`), with an existing forward-auth precedent.

1. **New platform-api internal exchange endpoint** beside the existing `platforminternal` handlers (`services/platform-api/routes.go:19-22`, gated by `INTERNAL_AUTH_TOKEN`). Verifies the inbound user token (aud=`platform-api`) and mints a fresh short-lived token with the target audience. Mirror the registry-authz handler shape (`services/platform-api/registry/authz.go:35-52`).
2. **Traefik forward-auth on the runtime-api + analytics-api routes only** (currently no middlewares — `k8s/10-gateway.yaml:601-750`; runtime `/api/v1/runtime/...`→`mcp-runtime-api:8084`, analytics `/api/v1/user/analytics`→`mcp-analytics-api:8085`). Mirror the `registry-admin-auth` middleware (`config/ingress/base/dynamic-config.yaml:10-18`) / `sentinel-admin-auth` (`k8s/10-gateway.yaml:31-34`); use `authResponseHeaders: Authorization` so Traefik replaces the upstream header with the exchanged per-audience token.
3. **Narrow the issued login token to `aud=platform-api`** (`CreateAccessToken`, `auth.go:340-356`). `Sign`/`Verify` already take audience args (`pkg/platformauth/sign.go:12`, `verify.go:11`); each service already verifies its own audience (`platform` `auth.go:363`; runtime `main.go:75`; analytics `main.go:71`).
4. **Update expectations**: `docs/security/authz-matrix.json` + the three `authz_matrix_test.go`, route-ownership tests, and `test/e2e` request-flow scripts that assume one token works on all services.

## Direct-access analysis (from wiring map)

- ✅ **Service-to-service calls are SAFE/unaffected** — runtime→platform (`runtime-api/internal/platformclient/client.go`) and analytics→platform (`analytics-api` `identity.HTTPResolver`) use `INTERNAL_AUTH_TOKEN`, not the user JWT.
- ✅ **CLI is SAFE** — `internal/cli/platformapi/client.go` calls the gateway/ingress URL, so forward-auth exchange applies.
- ⚠️ **UI is the gating concern.** The UI logs in directly against platform-api in-cluster (`services/ui/main.go:115`, login/oidc — fine, public), then **stores the token in session and proxies subsequent user API calls with it** as `Bearer `. If a narrowed `aud=platform-api` token is what the UI replays to runtime-api/analytics-api, those calls 401 — **unless** the UI proxy routes through Traefik (where exchange runs). Must confirm the UI→runtime/analytics proxy path.

## Open questions (do first — gate feasibility)

- [ ] **Does the UI proxy reach runtime-api/analytics-api via Traefik (exchange applies) or via direct in-cluster DNS (exchange bypassed)?** This is the gating question.
- [ ] If the UI bypasses Traefik for backend calls, pick a resolution: **(A)** auth response returns both a narrowed `aud=platform-api` token and a proxy token; **(B)** route the UI's backend proxy calls through Traefik; or **(C)** the exchange endpoint accepts the platform token and returns a per-route narrowed token. (A) or (B) preferred.
- [ ] Confirm Traefik `forwardAuth` + `authResponseHeaders` can **replace** (not just append) the upstream `Authorization` header.

## Acceptance criteria

- [ ] A user token (aud=`platform-api`) is rejected by runtime-api/analytics-api when presented **directly**.
- [ ] Through the gateway, CLI + UI flows calling runtime-api/analytics-api keep working (token swapped transparently).
- [ ] Exchanged tokens are short-lived + single-audience; unit tests on the exchange handler + minting.
- [ ] Kind e2e (`test/e2e/kind.sh`) passes with forward-auth enforced on the split routes.

## Risk / rollout

- Touches the request path for **all authenticated control-plane traffic** — a misconfigured middleware breaks auth cluster-wide.
- Traefik forward-auth wiring can only be fully validated on a live Kind cluster (qa-cluster-bringup + qa-e2e), not unit tests alone.
- **Phased rollout:** ship exchange endpoint + per-audience minting (additive, non-breaking) → wire Traefik + resolve the UI path → validate on a cluster → narrow `CreateAccessToken` last.

## Context

Surfaced during the PR #324 review (split of `mcp-sentinel-api`). Related: `pkg/platformauth/{sign,verify,audience,middleware}.go`, `services/platform-api/internal/platforminternal/`, `services/platform-api/internal/platformstore/auth.go`, `services/ui/main.go`, `config/ingress/`, `k8s/10-gateway.yaml`.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.