Authentication & gateway identity (internals)
This page describes how identity and subscription context are established at the gateway and how rate limiting uses them.
Subscription selection (AuthPolicy) → rate limits (TokenRateLimitPolicy)
Today’s pipeline does not drive TokenRateLimitPolicy by splitting group membership in CEL the way older drafts did. The flow is:
- AuthPolicy (controller-generated per model) authenticates the caller and calls maas-api at:
- Default tenant:
POST https://maas-api.opendatahub.svc.cluster.local:8443/internal/v1/subscriptions/select - Additional tenants:
POST https://maas-api-{tenantID}.opendatahub.svc.cluster.local:8443/internal/v1/subscriptions/select
with the caller’s groups, username, requested subscription header (when applicable), and model scope.
-
maas-api resolves which MaaSSubscription applies (including validation, auto-selection when only one subscription applies, and error paths). Results are exposed to Authorino as
subscription-infometadata. When a guardrail resolver is configured, a successful model selection also includesselectedGuardrailChecks, a sparse map whose keys are the shared SHA-256 check IDs and whose values aretrue. The map is{}when no guardrails apply; if a selected attachment cannot be resolved, the endpoint returnsguardrail_unavailableand does not return a partial selection. The field carries selection state only; request headers are handled separately. -
AuthPolicy
filters.identitycopies resolved fields ontoauth.identity, including: selected_subscription_key— model-scoped key of the form
{subscriptionNamespace}/{subscriptionName}@{modelNamespace}/{modelName}
Kept for telemetry and debugging, and still the rate-limit identity for subscriptions that existed before the short-ID rollout.-
selected_subscription_id— first 16 hex chars of SHA-256 over that key (from maas-apisubscription-info.rateLimitId). Set only when the selected MaaSSubscription hasstatus.rateLimitIdentity=short(created after upgrade). Also mirrored as response headerX-MaaS-Subscription-Rate-Limit-Idwithout replacing existing headers. -
TokenRateLimitPolicy (aggregated per model by the MaaSSubscription reconciler) defines one limit entry per distinct rate set among the subscriptions that apply to that model, split further when that set mixes legacy and short-ID members (they cannot share counters). Each limit’s
whenpredicate matches: - new subscriptions:
auth.identity.selected_subscription_id - pre-upgrade subscriptions:
auth.identity.selected_subscription_key
Inference paths are distinguished from discovery; /v1/models is exempt from token consumption limits where configured. Counters use the same identity field as the predicate, plus userid. See Reconciliation Flow.
So enforcement is: subscription resolved in AuthPolicy → the same identity the TRLP was built for. Group-based authorization is resolved during subscription selection: maas-api evaluates MaaSAuthPolicy rules for the caller and returns accessAllowed in subscription-info metadata. The gateway authorization rule simply reads that boolean rather than embedding per-model allowlists, keeping the AuthPolicy CR fixed-size at any subscription count.
TRLP predicates vs other identity: TokenRateLimitPolicy when clauses use selected_subscription_id for new subscriptions and selected_subscription_key for legacy ones—not groups_str, group arrays, or header mirrors. Anything else on auth.identity is not part of TRLP matching; it exists for subscription selection (inputs to maas-api), Authorino cache/metadata, and telemetry at the gateway/mesh.
Identity metadata: groups, groups_str, and telemetry
Behavior is split between filters.identity (what gets serialized onto auth.identity for downstream consumers) and celGroups (what the controller uses for subscription selection and cache keys). Both are defined in maasauthpolicy_controller.go.
API key validation path
After Authorino runs API key validation, auth.metadata.apiKeyValidation is populated. In the generated AuthPolicy success response → filters.identity → json.properties, the controller sets:
groups→auth.metadata.apiKeyValidation.groupsgroups_str→auth.metadata.apiKeyValidation.groups.join(",")
So for API keys, groups_str is the comma-joined snapshot from validation metadata.
Kubernetes TokenReview (OpenShift bearer token) path
TokenReview returns groups on status.user.groups. Authorino surfaces that as auth.identity.user.groups.
The controller does not define a separate groups_str expression that reads TokenReview groups. The filters.identity fields above still reference auth.metadata.apiKeyValidation.groups only; without API key metadata those expressions do not mirror TokenReview—treat groups_str (and the sibling groups property in that same JSON block) as empty / irrelevant for pure bearer-token auth.
Subscription selection and caching use the celGroups CEL constant in the same file: auth.metadata.apiKeyValidation.groups when API key metadata exists; otherwise auth.identity.groups if present, else auth.identity.user.groups. For TokenReview, inspect auth.identity.user.groups, not groups_str from filters.identity.
OIDC path
When a JWT carries a groups claim, Authorino may populate auth.identity.groups. celGroups uses that branch before falling back to auth.identity.user.groups.
TokenRateLimitPolicy (maassubscription_controller.go)
TRLP when predicates match auth.identity.selected_subscription_id for subscriptions created after the short-ID rollout and auth.identity.selected_subscription_key for pre-upgrade subscriptions. Using groups_str for rate limits is obsolete.
Troubleshooting: which field to inspect
| Concern | API key flow | TokenReview (kube) token | OIDC |
|---|---|---|---|
| Raw groups after auth | auth.metadata.apiKeyValidation.groups |
auth.identity.user.groups (from TokenReview status.user.groups) |
auth.identity.groups if present, else auth.identity.user.groups |
Serialized groups_str in filters.identity |
From apiKeyValidation.groups.join(",") |
Not populated from TokenReview (still tied to apiKeyValidation expression) | Same—only apiKeyValidation drives that JSON property |
| Subscription selection + Authorino cache inputs | celGroups, celUsername, celSubscription + auth.metadata["subscription-info"] after select |
Same celGroups branch → auth.identity.user.groups |
celGroups → auth.identity.groups when set |
| Rate limiting | auth.identity.selected_subscription_id (new CRs) or selected_subscription_key (pre-upgrade CRs) after successful select |
Same | Same |
| Telemetry / debug subscription@model | auth.identity.selected_subscription_key |
Same | Same |
If you read older notes about a “string trick” solely for TRLP group matching, treat that as obsolete.
Identity headers and defense-in-depth
Model inference routes (HTTPRoutes to model workloads):
- Controller-generated AuthPolicies generally do not inject most identity headers (
X-MaaS-Username,X-MaaS-Group,X-MaaS-Key-Id) upstream to model pods, to reduce leakage via logs or misconfigured proxies.
For API keys, AuthPolicy injects X-MaaS-Subscription only when validation returns a nonempty bound subscription. For user tokens, AuthPolicy preserves a client-supplied subscription header and uses it for subscription selection; it does not inject another copy. A user-token request without that header does not gain one during authentication, so telemetry that reads the header may have an empty subscription label.
AuthPolicy always sets the llm-d flow-control headers, replacing any client-supplied values, so clients cannot select an InferenceObjective or fairness ID:
x-llm-d-inference-objectiveandx-gateway-inference-objective: theobjectivereturned by maas-api subscription selection, which is the requested model'sobjectiveNamefrom the subscription'sflowControlStatuses. Empty when the model has none (for example, an ExternalModel) or when another object holds the name, so the scheduler applies priority0.x-llm-d-inference-fairness-idandx-gateway-inference-fairness-id: thefairnessIdreturned by maas-api, which is the tenant name of the maas-api instance.
MaaS API routes use a separate static AuthPolicy that may inject headers required by maas-api middleware (trusted internal service).
Token validation (short)
OpenShift tokens: Authorino uses Kubernetes TokenReview; groups and username come from the review result.
API keys: Authorino calls MaaS API validation; the key carries a bound subscription; validation returns user fields used for identity and subscription resolution.
Groups for authorization: Values in MaaSAuthPolicy / MaaSSubscription must align with groups from TokenReview or API key validation, not only with OpenShift Group objects, unless your IdP maps them consistently.
TOKEN=$(kubectl create token default -n default --duration=1m)
echo '{"apiVersion":"authentication.k8s.io/v1","kind":"TokenReview","spec":{"token":"'$TOKEN'"}}' | \
kubectl create -o jsonpath='{.status.user.groups}' -f -