34 lines
1.9 KiB
Markdown
34 lines
1.9 KiB
Markdown
# 3.5 — Capability authorization (RED)
|
|||
|
|
|
||
|
|
Phase: 3 — Authentication
|
||
|
|
Stage: RED
|
||
|
|
Depends on: [3.2](3.2-bearer-validation.md), [3.4](3.4-flag-gated-rollout.md)
|
||
|
|
|
||
|
|
- [ ] Beyond proving who the caller is, the gateway checks the token is permitted to invoke the capability being called
|
||
|
|
- [ ] Each capability prefix — `/v1/*` for the model surface, and the future `/sqs/*`, `/workflow/*`, `/cluster/*`, `/db/*` — maps to a required grant, and the mapping is configuration
|
||
|
|
- [ ] A token carrying a queue grant but no model grant is rejected on `POST /v1/chat/completions` with 403, not 401 — it authenticated fine, it is not permitted
|
||
|
|
- [ ] A token with no recognised grant at all is rejected on every protected route
|
||
|
|
- [ ] Rejections are RFC 9457 `application/problem+json` and state which capability was denied, without echoing the token or its claims verbatim
|
||
|
|
- [ ] Denials are logged with the caller identity and the capability, and counted as a distinct rejection reason
|
||
|
|
- [ ] A route with no required grant configured while auth is on fails startup rather than defaulting to allow-all
|
||
|
|
|
||
|
|
Write the failing tests first: a queue token must not invoke a GPU. The default on an
|
||
|
|
unconfigured route is deny, because a fail-open authorization layer is worse than none
|
||
|
|
— it reads as protection while granting everything.
|
||
|
|
|
||
|
|
## Verify
|
||
|
|
|
||
|
|
```bash
|
||
|
|
curl -s -i localhost:8080/v1/chat/completions -H "authorization: Bearer $QUEUE_ONLY_TOKEN" \
|
||
|
|
-H 'content-type: application/json' -d '{"model":"reasoning","messages":[]}'
|
||
|
|
# expected: 403, application/problem+json naming the denied capability, no upstream stub hit
|
||
|
|
|
||
|
|
curl -s -o /dev/null -w '%{http_code}\n' localhost:8080/v1/chat/completions \
|
||
|
|
-H "authorization: Bearer $MODEL_TOKEN" -H 'content-type: application/json' \
|
||
|
|
-d '{"model":"reasoning","messages":[]}'
|
||
|
|
# expected: 200
|
||
|
|
|
||
|
|
./gateway --config testdata/auth-on-route-without-grant.yaml; echo "exit=$?"
|
||
|
|
# expected: non-zero exit, stderr names the route missing a required grant
|
||
|
|
```
|