Repository navigation
Epic: Kuadrant AuthPolicy #100
Copy link
Copy link
Description
Activity
- added a parent issue
on Aug 7, 2026 - moved this to Next in AI Grid Network - Inference Fundamentals
on Aug 7, 2026 - moved this from Next to Done in AI Grid Network - Inference Fundamentals
on Aug 20, 2026 25 remaining items
As a litmus test, here is a specific example of a relatively complete AuthPolicy which will need to be supported:
apiVersion: kuadrant.io/v1 kind: AuthPolicy metadata: name: maas-gateway-auth namespace: openshift-ingress spec: defaults: rules: authentication: api-keys: metrics: false plain: selector: request.headers.authorization priority: 0 when: - operator: matches selector: request.headers.authorization value: ^Bearer sk-oai-.* api-keys-x-api-key: metrics: false plain: selector: request.headers.x-api-key priority: 1 when: - predicate: '"x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*") && !request.headers.authorization.matches("^Bearer sk-oai-.*")' openshift-identities: kubernetesTokenReview: audiences: - 'https://kubernetes.default.svc' metrics: false priority: 2 when: - predicate: '!(request.headers.authorization.matches("^Bearer sk-oai-.*") || ("x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*")))' authorization: auth-valid: cache: key: selector: '"api-key|" + (request.headers.authorization.matches("^Bearer sk-oai-.*") ? request.headers.authorization.replace("Bearer ", "") : request.headers["x-api-key"]) + "|" + (size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" ? request.path.split("/").filter(x, x != "")[0] + "/" + request.path.split("/").filter(x, x != "")[1] : ("x-gateway-model-name" in request.headers ? request.headers["x-gateway-model-name"] : ""))' ttl: 60 metrics: false opa: allValues: false rego: |- allow { object.get(input.auth.metadata, "apiKeyValidation", {}) input.auth.metadata.apiKeyValidation.valid == true } allow { not input.auth.metadata.apiKeyValidation } priority: 0 deny-api-key-mint: metrics: false patternMatching: patterns: - predicate: '!(has(auth.metadata) && has(auth.metadata.apiKeyValidation))' priority: 0 when: - predicate: request.method == "POST" && request.path.endsWith("/v1/api-keys") deny-client-identity-headers: metrics: false patternMatching: patterns: - predicate: '!("x-maas-username" in request.headers)' - predicate: '!("x-maas-group" in request.headers)' priority: 0 require-group-membership: cache: key: selector: '((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.userId : (has(auth.identity.preferred_username) ? auth.identity.preferred_username : (has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username))) + "|" + ((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.groups : (has(auth.identity.groups) ? auth.identity.groups : auth.identity.user.groups)).join(",") + "|" + ((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.subscription : ("x-maas-subscription" in request.headers ? request.headers["x-maas-subscription"] : "")) + "|" + (size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" ? request.path.split("/").filter(x, x != "")[0] + "/" + request.path.split("/").filter(x, x != "")[1] : ("x-gateway-model-name" in request.headers ? request.headers["x-gateway-model-name"] : ""))' ttl: 60 metrics: false opa: allValues: false rego: |- allow { object.get(input.auth.metadata["subscription-info"], "accessAllowed", false) == true } priority: 0 when: - predicate: '(size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" || "x-gateway-model-name" in request.headers)' subscription-valid: cache: key: selector: '((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.userId : (has(auth.identity.preferred_username) ? auth.identity.preferred_username : (has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username))) + "|" + ((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.groups : (has(auth.identity.groups) ? auth.identity.groups : auth.identity.user.groups)).join(",") + "|" + ((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.subscription : ("x-maas-subscription" in request.headers ? request.headers["x-maas-subscription"] : "")) + "|" + (size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" ? request.path.split("/").filter(x, x != "")[0] + "/" + request.path.split("/").filter(x, x != "")[1] : ("x-gateway-model-name" in request.headers ? request.headers["x-gateway-model-name"] : ""))' ttl: 60 metrics: false opa: allValues: false rego: "allow {\n\tobject.get(input.auth.metadata[\"subscription-info\"], \"name\", \"\") != \"\"\n\tobject.get(input.auth.metadata[\"subscription-info\"], \"error\", \"\") == \"\"\n\tphase := object.get(input.auth.metadata[\"subscription-info\"], \"phase\", \"\")\n\tany([phase == \"Active\", phase == \"Degraded\"])\n\tobject.get(input.auth.metadata[\"subscription-info\"], \"deletionTimestamp\", \"\") == \"\"\n}" priority: 0 when: - predicate: '(size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" || "x-gateway-model-name" in request.headers)' metadata: apiKeyValidation: cache: key: selector: 'request.headers.authorization.matches("^Bearer sk-oai-.*") ? request.headers.authorization.replace("Bearer ", "") : request.headers["x-api-key"]' ttl: 60 http: body: expression: '{"key": request.headers.authorization.matches("^Bearer sk-oai-.*") ? request.headers.authorization.replace("Bearer ", "") : request.headers["x-api-key"]}' contentType: application/json method: POST url: 'https://maas-api.redhat-ai-gateway-infra.svc.cluster.local:8443/internal/v1/api-keys/validate' metrics: false priority: 0 when: - predicate: 'request.headers.authorization.matches("^Bearer sk-oai-.*") || ("x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*"))' subscription-info: cache: key: selector: '((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.userId : (has(auth.identity.preferred_username) ? auth.identity.preferred_username : (has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username))) + "|" + ((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.groups : (has(auth.identity.groups) ? auth.identity.groups : auth.identity.user.groups)).join(",") + "|" + ((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.subscription : ("x-maas-subscription" in request.headers ? request.headers["x-maas-subscription"] : "")) + "|" + (size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" ? request.path.split("/").filter(x, x != "")[0] + "/" + request.path.split("/").filter(x, x != "")[1] : ("x-gateway-model-name" in request.headers ? request.headers["x-gateway-model-name"] : ""))' ttl: 60 http: body: expression: |- { "groups": (has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.groups : (has(auth.identity.groups) ? auth.identity.groups : auth.identity.user.groups), "username": (has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.username : (has(auth.identity.preferred_username) ? auth.identity.preferred_username : (has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username)), "requestedSubscription": (has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.subscription : ("x-maas-subscription" in request.headers ? request.headers["x-maas-subscription"] : ""), "requestedModel": (size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" ? request.path.split("/").filter(x, x != "")[0] + "/" + request.path.split("/").filter(x, x != "")[1] : ("x-gateway-model-name" in request.headers ? request.headers["x-gateway-model-name"] : "")) } contentType: application/json method: POST url: 'https://maas-api.redhat-ai-gateway-infra.svc.cluster.local:8443/internal/v1/subscriptions/select' metrics: false priority: 1 when: - predicate: '(size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" || "x-gateway-model-name" in request.headers)' response: success: filters: identity: json: properties: selected_subscription: expression: 'has(auth.metadata["subscription-info"].name) ? auth.metadata["subscription-info"].name : ""' selected_subscription_key: expression: '(has(auth.metadata["subscription-info"].namespace) && has(auth.metadata["subscription-info"].name)) ? auth.metadata["subscription-info"].namespace + "/" + auth.metadata["subscription-info"].name + "@" + (has(auth.metadata["subscription-info"].resolvedModel) && auth.metadata["subscription-info"].resolvedModel != "" ? auth.metadata["subscription-info"].resolvedModel : (size(request.path.split("/").filter(x, x != "")) >= 2 && request.path.split("/").filter(x, x != "")[0] != "v1" && request.path.split("/").filter(x, x != "")[0] != "maas-api" ? request.path.split("/").filter(x, x != "")[0] + "/" + request.path.split("/").filter(x, x != "")[1] : ("x-gateway-model-name" in request.headers ? request.headers["x-gateway-model-name"] : ""))) : ""' subscription_error: expression: 'has(auth.metadata["subscription-info"].error) ? auth.metadata["subscription-info"].error : ""' groups_str: expression: '((has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.groups : (has(auth.identity.groups) ? auth.identity.groups : auth.identity.user.groups)).join(",")' userid: expression: '(has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.username : (has(auth.identity.preferred_username) ? auth.identity.preferred_username : (has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username))' keyName: expression: '(has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.keyName : ""' subscription_error_message: expression: 'has(auth.metadata["subscription-info"].message) ? auth.metadata["subscription-info"].message : ""' subscription_info: expression: 'has(auth.metadata["subscription-info"].name) ? auth.metadata["subscription-info"] : {}' groups: expression: '(has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.groups : (has(auth.identity.groups) ? auth.identity.groups : auth.identity.user.groups)' keyId: expression: '(has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.keyId : ""' metrics: true priority: 0 headers: X-MaaS-Group: metrics: false plain: expression: 'size(auth.metadata.apiKeyValidation.groups) > 0 ? ''["'' + auth.metadata.apiKeyValidation.groups.join(''","'') + ''"]'' : ''[]''' priority: 0 when: - predicate: 'request.headers.authorization.matches("^Bearer sk-oai-.*") || ("x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*"))' X-MaaS-Group-Token: key: X-MaaS-Group metrics: false plain: expression: 'has(auth.identity.groups) ? (size(auth.identity.groups) > 0 && auth.identity.groups.all(g, g.matches(''^[A-Za-z0-9:._/-]+$'')) ? ''["system:authenticated","'' + auth.identity.groups.join(''","'') + ''"]'' : ''["system:authenticated"]'') : (has(auth.identity.user.groups) && size(auth.identity.user.groups) > 0 ? ''["system:authenticated","'' + auth.identity.user.groups.join(''","'') + ''"]'' : ''["system:authenticated"]'')' priority: 1 when: - predicate: '!(request.headers.authorization.matches("^Bearer sk-oai-.*") || ("x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*")))' X-MaaS-Subscription: metrics: false plain: expression: '(has(auth.metadata) && has(auth.metadata.apiKeyValidation)) ? auth.metadata.apiKeyValidation.subscription : ("x-maas-subscription" in request.headers ? request.headers["x-maas-subscription"] : "")' priority: 0 when: - predicate: (has(auth.metadata) && has(auth.metadata.apiKeyValidation) && auth.metadata.apiKeyValidation.subscription != "") || "x-maas-subscription" in request.headers X-MaaS-Username: metrics: false plain: selector: auth.metadata.apiKeyValidation.username priority: 0 when: - predicate: 'request.headers.authorization.matches("^Bearer sk-oai-.*") || ("x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*"))' X-MaaS-Username-Token: key: X-MaaS-Username metrics: false plain: expression: 'has(auth.identity.preferred_username) ? auth.identity.preferred_username : (has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username)' priority: 1 when: - predicate: '!(request.headers.authorization.matches("^Bearer sk-oai-.*") || ("x-api-key" in request.headers && request.headers["x-api-key"].matches("^sk-oai-.*")))' unauthenticated: code: 401 message: value: Authentication required unauthorized: body: expression: 'has(auth.metadata["subscription-info"].message) ? auth.metadata["subscription-info"].message : "Access denied"' code: 403 headers: content-type: value: text/plain x-ext-auth-reason: expression: 'has(auth.metadata["subscription-info"].error) ? auth.metadata["subscription-info"].error : "unauthorized"' strategy: atomic when: - predicate: request.path != "/maas-api/health" || request.method != "GET" targetRef: group: gateway.networking.k8s.io kind: Gateway name: maas-default-gateway
- removed a parent issue
on Sep 19, 2026 - added a sub-issue
on Oct 8, 2026
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsEpics
Be compatible with the Kuadrant
AuthPolicyAPI: any validAuthPolicyshould translate to Praxis configuration and be enforced with the same semantics.Where we are
The PoC from praxis-proxy/praxis#716 delivered:
transpile-authpolicyCLI that ingests anAuthPolicyand emits a Praxispolicyfilter block plus a CPEX policy document, with a coverage report classifying every construct as translated, approximated, or skipped.policyfilter, so the emitted config actually enforces. Experimental, feature-gated.Covered today: JWT authentication, CEL authorization (
patternMatching,when,patterns, and the legacyselector/operator/valueform), and the RBAC membership idiom mapped onto CPEXrole.*,perm.*,team.*.Everything else is reported rather than silently dropped. This epic tracks closing those reports one by one.
Scope
AuthPolicyauthenticationmethods, credential location, and rule priority/fallback semantics (set decision and scoping).authorizationbackend (set decision and scoping).metadataandcallbacks.response.successinjection anddenyWithon both authentication and authorization denials.defaults/overridesand Gateway to route merge.targetRefbinding (?).Non-goals
AuthPolicy).Sub-Tasks
Need a discussion on the following items to decide final scope of work.
apiKeyauthentication Epic: API-key identity support #108x509/ mTLS authenticationoauth2IntrospectionauthenticationkubernetesTokenReviewauthenticationanonymousauthenticationcredentials: header, query string, cookie)opaauthorizationspicedbauthorizationkubernetesSubjectAccessReviewauthorizationmetadata(http, userInfo, uma) andauth.metadata.*in CELcallbacksdenyWithcustom denial responsesresponse.successheader and dynamic metadata injection feat:assertions:to control http headers propagation #56defaults/overridesand Gateway to route mergetargetRefbindingcache) feat(pdp): add an opt-in bounded decision cache #101Area
Security