Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions .github/hack/cleanup-odh.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# - ODH operator namespace (odh-operator)
# - OpenDataHub application namespace (opendatahub)
# - MaaS subscription namespace (models-as-a-service)
# - Keycloak identity provider (if deployed)
# - ODH CRDs (optional)
#
# Usage: ./cleanup-odh.sh [--include-crds]
Expand Down Expand Up @@ -123,33 +124,52 @@ for policy_ns in kuadrant-system rh-connectivity-link; do
"authorinos.operator.authorino.kuadrant.io" "kuadrants.kuadrant.io" "limitadors.limitador.kuadrant.io"
done

# 11. Delete llm namespace and model resources
echo "11. Deleting LLM models and namespace..."
# 11. Delete Keycloak identity provider (if installed)
echo "11. Deleting Keycloak namespace (if installed)..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../.. && pwd)"
if [[ -f "${SCRIPT_DIR}/scripts/cleanup-keycloak.sh" ]]; then
# Pass --delete-crds if --include-crds was specified for this script
if $INCLUDE_CRDS; then
"${SCRIPT_DIR}/scripts/cleanup-keycloak.sh" --force --delete-crds 2>/dev/null || true
else
"${SCRIPT_DIR}/scripts/cleanup-keycloak.sh" --force 2>/dev/null || true
fi
else
# Fallback if cleanup script not found - direct cleanup
force_delete_namespace "keycloak-system" "keycloaks.k8s.keycloak.org"
if $INCLUDE_CRDS; then
kubectl delete crd keycloaks.k8s.keycloak.org --ignore-not-found 2>/dev/null || true
kubectl delete crd keycloakrealmimports.k8s.keycloak.org --ignore-not-found 2>/dev/null || true
fi
fi

# 12. Delete llm namespace and model resources
echo "12. Deleting LLM models and namespace..."
force_delete_namespace "llm" "llminferenceservice" "inferenceservice" "maasmodelrefs.maas.opendatahub.io"

# 12. Delete gateway resources in openshift-ingress
echo "12. Deleting gateway resources..."
# 13. Delete gateway resources in openshift-ingress
echo "13. Deleting gateway resources..."
kubectl delete gateway maas-default-gateway -n openshift-ingress --ignore-not-found 2>/dev/null || true
kubectl delete envoyfilter -n openshift-ingress -l kuadrant.io/managed=true --ignore-not-found 2>/dev/null || true
kubectl delete envoyfilter kuadrant-auth-tls-fix -n openshift-ingress --ignore-not-found 2>/dev/null || true
kubectl delete authpolicy -n openshift-ingress --all --ignore-not-found 2>/dev/null || true
kubectl delete ratelimitpolicy -n openshift-ingress --all --ignore-not-found 2>/dev/null || true
kubectl delete tokenratelimitpolicy -n openshift-ingress --all --ignore-not-found 2>/dev/null || true

# 13. Delete MaaS RBAC (ClusterRoles, ClusterRoleBindings - can conflict with other managers)
echo "13. Deleting MaaS RBAC..."
# 14. Delete MaaS RBAC (ClusterRoles, ClusterRoleBindings - can conflict with other managers)
echo "14. Deleting MaaS RBAC..."
kubectl delete clusterrolebinding maas-api maas-controller-rolebinding --ignore-not-found 2>/dev/null || true
kubectl delete clusterrole maas-api maas-controller-role --ignore-not-found 2>/dev/null || true

# 14. Optionally delete CRDs
# 15. Optionally delete CRDs
if $INCLUDE_CRDS; then
echo "14. Deleting ODH CRDs..."
echo "15. Deleting ODH CRDs..."
kubectl delete crd datascienceclusters.datasciencecluster.opendatahub.io --ignore-not-found 2>/dev/null || true
kubectl delete crd dscinitializations.dscinitialization.opendatahub.io --ignore-not-found 2>/dev/null || true
kubectl delete crd datasciencepipelinesapplications.datasciencepipelinesapplications.opendatahub.io --ignore-not-found 2>/dev/null || true
# Add more CRDs as needed
else
echo "14. Skipping CRD deletion (use --include-crds to remove CRDs)"
echo "15. Skipping CRD deletion (use --include-crds to remove CRDs)"
fi

echo ""
Expand All @@ -158,4 +178,4 @@ echo ""
echo "Verify cleanup with:"
echo " kubectl get subscription -A | grep -i odh"
echo " kubectl get csv -A | grep -i odh"
echo " kubectl get ns | grep -E 'odh|opendatahub|models-as-a-service|kuadrant|rh-connectivity-link|llm'"
echo " kubectl get ns | grep -E 'odh|opendatahub|models-as-a-service|kuadrant|rh-connectivity-link|keycloak-system|llm'"
15 changes: 15 additions & 0 deletions deployment/overlays/odh/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ patches:
target:
kind: Deployment
name: maas-api
- path: maas-api-auth-policy-oidc-patch.yaml
target:
kind: AuthPolicy
name: maas-api-auth-policy

replacements:
# Gateway policies must be in openshift-ingress to target maas-default-gateway
Expand Down Expand Up @@ -141,3 +145,14 @@ replacements:
options:
delimiter: "."
index: 1
- source:
kind: ConfigMap
version: v1
name: maas-parameters
fieldPath: data.oidc-issuer-url
targets:
- select:
kind: AuthPolicy
name: maas-api-auth-policy
fieldPaths:
- spec.rules.authentication.oidc-identities.jwt.issuerUrl
38 changes: 38 additions & 0 deletions deployment/overlays/odh/maas-api-auth-policy-oidc-patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
apiVersion: kuadrant.io/v1
kind: AuthPolicy
metadata:
name: maas-api-auth-policy
spec:
rules:
authentication:
# Match JWT-shaped bearer tokens first so external OIDC can coexist
# with opaque OpenShift tokens handled by TokenReview.
oidc-identities:
when:
- predicate: '!request.headers.authorization.startsWith("Bearer sk-oai-") && request.headers.authorization.matches("^Bearer [^.]+\\.[^.]+\\.[^.]+$")'
jwt:
issuerUrl: https://oidc.example.invalid/realms/maas
priority: 1
openshift-identities:
when:
- predicate: '!request.headers.authorization.startsWith("Bearer sk-oai-")'
priority: 2
response:
success:
headers:
X-MaaS-Username-OC:
plain:
$patch: replace
expression: >-
has(auth.identity.preferred_username) ?
auth.identity.preferred_username :
(has(auth.identity.sub) ? auth.identity.sub : auth.identity.user.username)
X-MaaS-Group-OC:
plain:
$patch: replace
expression: >-
has(auth.identity.groups) ?
(size(auth.identity.groups) > 0 ?
'["system:authenticated","' + auth.identity.groups.join('","') + '"]' :
'["system:authenticated"]') :
'["' + auth.identity.user.groups.join('","') + '"]'
3 changes: 3 additions & 0 deletions deployment/overlays/odh/params.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ gateway-namespace=openshift-ingress
gateway-name=maas-default-gateway
app-namespace=opendatahub
api-key-max-expiration-days=30
# External OIDC issuer for JWT validation (Keycloak example or corporate IdP).
# Replace the placeholder with a real issuer URL before enabling OIDC in a live deployment.
oidc-issuer-url=https://oidc.example.invalid/realms/maas
18 changes: 9 additions & 9 deletions docs/content/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ graph TB

**Flow summary:**

1. User sends `POST /v1/api-keys` with Bearer `{oc-token}`.
1. User sends `POST /v1/api-keys` with Bearer `{identity-token}`.
2. Gateway routes the request to AuthPolicy (Authorino).
3. AuthPolicy validates the OpenShift token via TokenReview.
3. AuthPolicy validates the presented identity token via the configured auth method (`kubernetesTokenReview` for OpenShift, or OIDC JWT validation when enabled).
4. Gateway forwards the authenticated request and user context to the Key Minting Service.

```mermaid
Expand All @@ -88,18 +88,18 @@ graph TB
KMS[MaaS API]
end

U -->|"1. POST /v1/api-keys<br/>Bearer {oc-token}"| G
U -->|"1. POST /v1/api-keys<br/>Bearer {identity-token}"| G
G -->|"2. Route /maas-api"| AP
AP -->|"3. TokenReview<br/>validate OpenShift token"| G
AP -->|"3. Validate identity token<br/>TokenReview or OIDC JWT"| G
G -->|"4. Forward + user context"| KMS

style KMS fill:#1976d2,stroke:#333,stroke-width:2px,color:#fff
style G fill:#7b1fa2,stroke:#333,stroke-width:2px,color:#fff
style AP fill:#e65100,stroke:#333,stroke-width:2px,color:#fff
```

!!! Tip "Future Plans"
Today, validation uses the **OpenShift token flow** (TokenReview). Future plans include optional integration with other OIDC providers (e.g., external IdPs, Keycloak).
!!! Tip "OIDC Support"
The `maas-api` route can be configured to validate external OIDC tokens (for example Keycloak-issued JWTs) in addition to the existing OpenShift TokenReview flow. Model routes still use the current API-key policy, so the interim OIDC flow is: authenticate with OIDC at `maas-api`, mint an `sk-oai-*` key, then use that key for model discovery and inference.


### Key Minting Service (Default Implementation)
Expand Down Expand Up @@ -291,7 +291,7 @@ graph LR

### 1. API Key Creation Flow (MaaS API)

Users create API keys by authenticating with their OpenShift token. The MaaS API generates a key, stores only the hash in PostgreSQL, and returns the plaintext once:
Users create API keys by authenticating with an accepted identity token (OpenShift today, or OIDC when configured on the `maas-api` route). The MaaS API generates a key, stores only the hash in PostgreSQL, and returns the plaintext once:

```mermaid
sequenceDiagram
Expand All @@ -301,9 +301,9 @@ sequenceDiagram
participant MaaS as MaaS API
participant DB as PostgreSQL

User->>Gateway: POST /maas-api/v1/api-keys<br/>Authorization: Bearer {openshift-token}
User->>Gateway: POST /maas-api/v1/api-keys<br/>Authorization: Bearer {identity-token}
Gateway->>Authorino: Enforce MaaS API AuthPolicy
Authorino->>Authorino: TokenReview (validate OpenShift token)
Authorino->>Authorino: Validate token (TokenReview or OIDC JWT)
Authorino->>Gateway: Authenticated
Gateway->>MaaS: Forward request with user context

Expand Down
24 changes: 17 additions & 7 deletions docs/content/configuration-and-management/token-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ The validation endpoint (`/internal/v1/api-keys/validate`) is called by Authorin

## Model Discovery

The `/v1/models` endpoint allows you to discover which models you're authorized to access. This endpoint works with any valid authentication token — you can use your OpenShift token or an API key.
The `/v1/models` endpoint allows you to discover which models you're authorized to access. The API forwards the same `Authorization` header you send to each model route, so the result depends on what those model routes accept.

### How It Works

Expand All @@ -134,9 +134,19 @@ flowchart LR

This means you can:

1. **Authenticate with OpenShift or OIDC** — use your existing identity and the same token you would use for inference.
2. **Use an API key** — use your `sk-oai-*` key in the Authorization header.
3. **Call `/v1/models` immediately** — see only the models you can access, without creating an API key first (if using OpenShift token).
1. **Use an API key** — this is the most portable option because the current model-route AuthPolicies already validate `sk-oai-*` keys.
2. **Use an identity token directly** — only when the model routes themselves accept that token type.
3. **Create a key first for the interim OIDC flow** — when OIDC is enabled only on the `maas-api` route, use your OIDC token to call `POST /v1/api-keys`, then call `/v1/models` with the minted API key.

### Interim OIDC Flow

When the `maas-api` AuthPolicy is configured for OIDC but model HTTPRoutes still use the existing API-key-only policy, the flow is:

1. Authenticate to your IdP and obtain an OIDC access token.
2. Call `POST /v1/api-keys` with that OIDC token.
3. Use the returned `sk-oai-*` key for `GET /v1/models` and inference requests.

This preserves compatibility with the current model-route policy while allowing non-OpenShift identities to onboard through `maas-api`.

---

Expand Down Expand Up @@ -223,15 +233,15 @@ A: Yes. Your API key inherits your group membership at creation time. If your su

---

**Q: What's the difference between my OpenShift token and an API key?**
**Q: What's the difference between my OpenShift/OIDC token and an API key?**

A: Your **OpenShift token** is your identity token from authentication (e.g., OpenShift or OIDC). An **API key** is a long-lived credential created via `POST /v1/api-keys` and stored as a hash in PostgreSQL. For **GET /v1/models**, the API passes your Authorization header as-is to each model endpoint; you can use either. For inference, use a token your gateway accepts (OpenShift token or API key as configured).
A: Your **OpenShift/OIDC token** is your identity token from authentication. An **API key** is a long-lived credential created via `POST /v1/api-keys` and stored as a hash in PostgreSQL. The API passes your `Authorization` header as-is to each model endpoint, so use whichever credential type the model route accepts. In the current interim OIDC rollout, use the OIDC token to mint an API key first, then use that key for `/v1/models` and inference.

---

**Q: Do I need an API key to list available models?**

A: No. Call **GET /v1/models** with your OpenShift/OIDC token (or an API key) in the Authorization header. The API uses that same header to probe each model endpoint and returns only models you can access.
A: Not always. If the target model routes accept your OpenShift or OIDC token directly, call **GET /v1/models** with that token. If only the `maas-api` route is OIDC-enabled and the model routes still use API-key auth, mint an API key with `POST /v1/api-keys` first and then call **GET /v1/models** with the returned key.

---

Expand Down
Loading
Loading