Skip to content

Commit 7c04026

Browse files
aniketpaludevin-ai-integration[bot]GowthamShanmugam
authored
feat: Extended OIDC support to extract groups & namespaces and token injection with multiple methods (#6089)
* feat: Extract groups and namespaces claims from JWT in OidcTokenParser Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Minor formatting Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * feat: Allow Feast SDK to accept a pre-existing OIDC token without contacting the identity provider Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * fix: Raise error when configured token_env_var is empty Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Minor formatting changes Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Activate _check_mutually_exclusive groups only when all fields are set, reject stray auth_discovery_url/client_id without client_secret Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Narrow OIDC client routing to use set-based key detection and extract _is_oidc_client_config helper Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Fix .sort() assertions in test_token_parser.py that always compared None == None Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Guard against missing roles key in resource_access to prevent unhandled KeyError Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Fixed lint errors Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Fixed lint error Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Fixed lint errors Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Added support to read ServiceAccount token and Minor improvements Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Improved code readibility Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Minor reformatting Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * fix: Use exact dict-key lookup for kubernetes.io claim to satisfy CodeQL substring sanitization check Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * feat: Add verify_ssl support to OIDC auth flow for self-signed certificates Signed-off-by: Aniket Paluskar <apaluska@redhat.com> Made-with: Cursor * feat: Lightweight SA token validation for OIDC auth — TokenReview only, no RBAC queries Replace KubernetesTokenParser delegation with a lightweight _validate_k8s_sa_token_and_extract_namespace() method in OidcTokenParser. Validates SA tokens via TokenReview API and extracts namespace from the authenticated identity. No RoleBinding/ClusterRoleBinding queries needed, so the server SA only requires tokenreviews/create permission. Also updates OIDC auth documentation with token priority, verify_ssl, groups claim, and multi-token support sections. Made-with: Cursor Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Minor reformatting & lint related changes Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Update sdk/python/feast/permissions/auth/oidc_token_parser.py Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * fix: Restore missing return in intra-comm check and add error handling for K8s token validation Signed-off-by: Aniket Paluskar <apaluska@redhat.com> Made-with: Cursor * Minor reformatting Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Checks preferred_username first (Keycloak default), then falls back to upn (Azure AD / Entra ID) Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * feat(operator): Split server/client OIDC config and add secretKeyName, tokenEnvVar, verifySSL CRD fields Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Reverted kustomization.yaml Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * fix: Harden OIDC token parsing and make client_id optional - Catch PyJWTError (not just InvalidTokenError) in token decode path - Guard _get_intra_comm_user against malformed tokens (DecodeError) - Reduce _extract_claim log noise: warning → debug for optional claims - Make client_id optional on server config (skip roles when absent) - Move client_id from required to optional in operator config Made-with: Cursor Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * cache K8s client, eliminate double JWT decode, improve error messages Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Minor formatting Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * feat(odh): wire OIDC_ISSUER_URL from params.env into operator pod - Add OIDC_ISSUER_URL to manager Deployment env (base) - Add param to ODH/RHOAI params.env with empty default - Kustomize replacements from feast-operator-parameters ConfigMap - Document Open Data Hub operator integration Pairs with opendatahub-operator injecting OIDC_ISSUER_URL at reconcile time from GatewayConfig when the cluster uses external OIDC (RHOAIENG-55767). Made-with: Cursor Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Add issuerUrl to OidcAuthz CRD and OIDC_ISSUER_URL env var support for Secret-less OIDC configuration Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Add caCertConfigMap to OidcAuthz CRD and ca_cert_path to SDK for self-signed OIDC provider TLS verification Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Reverted kustomization.yaml to use upstream image Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Shorten CRD field descriptions to fit maxDescLen=120 and revert kustomization.yaml to upstream default Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * fix: Remove unused param, nil deref in test, and update secrets baseline Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * fix: Remove unused secretExtractionFunc from client config chain and fix mypy attr-defined error Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Remove always-nil error from getClientRepoConfig and stop leaking ODH CA path into non-ODH OIDC config Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Remove always-nil error from getClientRepoConfig, stop leaking ODH CA path, pass ca_cert_path to client token fetch, and update secrets baseline Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Thread ODH CA bundle detection into resolveOidcCACertPath for proper 3-tier priority Signed-off-by: Aniket Paluskar <apaluska@redhat.com> * Provision TokenReview RBAC for OIDC auth and add SSL error logging in token parser Signed-off-by: Aniket Paluskar <apaluska@redhat.com> --------- Signed-off-by: Aniket Paluskar <apaluska@redhat.com> Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Gowtham Shanmugasundaram <gshanmug@redhat.com>
1 parent daf77c6 commit 7c04026

32 files changed

Lines changed: 1614 additions & 292 deletions

.secrets.baseline

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@
934934
"filename": "infra/feast-operator/api/v1/featurestore_types.go",
935935
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
936936
"is_verified": false,
937-
"line_number": 734
937+
"line_number": 761
938938
}
939939
],
940940
"infra/feast-operator/api/v1/zz_generated.deepcopy.go": [
@@ -950,14 +950,14 @@
950950
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
951951
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
952952
"is_verified": false,
953-
"line_number": 1261
953+
"line_number": 754
954954
},
955955
{
956956
"type": "Secret Keyword",
957957
"filename": "infra/feast-operator/api/v1/zz_generated.deepcopy.go",
958958
"hashed_secret": "c2028031c154bbe86fd69bef740855c74b927dcf",
959959
"is_verified": false,
960-
"line_number": 1266
960+
"line_number": 1300
961961
}
962962
],
963963
"infra/feast-operator/api/v1alpha1/featurestore_types.go": [
@@ -1140,14 +1140,14 @@
11401140
"filename": "infra/feast-operator/internal/controller/services/repo_config.go",
11411141
"hashed_secret": "44e17306b837162269a410204daaa5ecee4ec22c",
11421142
"is_verified": false,
1143-
"line_number": 109
1143+
"line_number": 114
11441144
},
11451145
{
11461146
"type": "Secret Keyword",
11471147
"filename": "infra/feast-operator/internal/controller/services/repo_config.go",
11481148
"hashed_secret": "e2fb052132fd6a07a56af2013e0b62a1f510572c",
11491149
"is_verified": false,
1150-
"line_number": 148
1150+
"line_number": 205
11511151
}
11521152
],
11531153
"infra/feast-operator/internal/controller/services/services.go": [
@@ -1539,5 +1539,5 @@
15391539
}
15401540
]
15411541
},
1542-
"generated_at": "2026-04-04T12:08:20Z"
1542+
"generated_at": "2026-04-07T15:56:56Z"
15431543
}

docs/getting-started/components/authz_manager.md

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,52 +40,87 @@ auth:
4040
With OIDC authorization, the Feast client proxies retrieve the JWT token from an OIDC server (or [Identity Provider](https://openid.net/developers/how-connect-works/))
4141
and append it in every request to a Feast server, using an [Authorization Bearer Token](https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#bearer).
4242

43-
The server, in turn, uses the same OIDC server to validate the token and extract the user roles from the token itself.
43+
The server, in turn, uses the same OIDC server to validate the token and extract user details — including username, roles, and groups — from the token itself.
4444

4545
Some assumptions are made in the OIDC server configuration:
4646
* The OIDC token refers to a client with roles matching the RBAC roles of the configured `Permission`s (*)
47-
* The roles are exposed in the access token that is passed to the server
47+
* The roles are exposed in the access token under `resource_access.<client_id>.roles`
4848
* The JWT token is expected to have a verified signature and not be expired. The Feast OIDC token parser logic validates for `verify_signature` and `verify_exp` so make sure that the given OIDC provider is configured to meet these requirements.
49-
* The preferred_username should be part of the JWT token claim.
50-
49+
* The `preferred_username` should be part of the JWT token claim.
50+
* For `GroupBasedPolicy` support, the `groups` claim should be present in the access token (requires a "Group Membership" protocol mapper in Keycloak).
5151

5252
(*) Please note that **the role match is case-sensitive**, e.g. the name of the role in the OIDC server and in the `Permission` configuration
5353
must be exactly the same.
5454

55-
For example, the access token for a client `app` of a user with `reader` role should have the following `resource_access` section:
55+
For example, the access token for a client `app` of a user with `reader` role and membership in the `data-team` group should have the following claims:
5656
```json
5757
{
58+
"preferred_username": "alice",
5859
"resource_access": {
5960
"app": {
6061
"roles": [
6162
"reader"
6263
]
6364
}
64-
}
65+
},
66+
"groups": [
67+
"data-team"
68+
]
6569
}
6670
```
6771

68-
An example of feast OIDC authorization configuration on the server side is the following:
72+
#### Server-Side Configuration
73+
74+
The server requires `auth_discovery_url` and `client_id` to validate incoming JWT tokens via JWKS:
6975
```yaml
7076
project: my-project
7177
auth:
7278
type: oidc
73-
client_id: _CLIENT_ID__
79+
client_id: _CLIENT_ID_
7480
auth_discovery_url: _OIDC_SERVER_URL_/realms/master/.well-known/openid-configuration
7581
...
7682
```
7783

78-
In case of client configuration, the following settings username, password and client_secret must be added to specify the current user:
84+
When the OIDC provider uses a self-signed or untrusted TLS certificate (e.g. internal Keycloak on OpenShift), set `verify_ssl` to `false` to disable certificate verification:
85+
```yaml
86+
auth:
87+
type: oidc
88+
client_id: _CLIENT_ID_
89+
auth_discovery_url: https://keycloak.internal/realms/master/.well-known/openid-configuration
90+
verify_ssl: false
91+
```
92+
93+
{% hint style="warning" %}
94+
Setting `verify_ssl: false` disables TLS certificate verification for all OIDC provider communication (discovery, JWKS, token endpoint). Only use this in development or internal environments where you accept the security risk.
95+
{% endhint %}
96+
97+
#### Client-Side Configuration
98+
99+
The client supports multiple token source modes. The SDK resolves tokens in the following priority order:
100+
101+
1. **Intra-communication token** — internal server-to-server calls (via `INTRA_COMMUNICATION_BASE64` env var)
102+
2. **`token`** — a static JWT string provided directly in the configuration
103+
3. **`token_env_var`** — the name of an environment variable containing the JWT
104+
4. **`client_secret`** — fetches a token from the OIDC provider using client credentials or ROPC flow (requires `auth_discovery_url` and `client_id`)
105+
5. **`FEAST_OIDC_TOKEN`** — default fallback environment variable
106+
6. **Kubernetes service account token** — read from `/var/run/secrets/kubernetes.io/serviceaccount/token` when running inside a pod
107+
108+
**Token passthrough** (for use with external token providers like [kube-authkit](https://github.com/opendatahub-io/kube-authkit)):
109+
```yaml
110+
project: my-project
111+
auth:
112+
type: oidc
113+
token_env_var: FEAST_OIDC_TOKEN
114+
```
115+
116+
Or with a bare `type: oidc` (no other fields) — the SDK falls back to the `FEAST_OIDC_TOKEN` environment variable or a mounted Kubernetes service account token:
79117
```yaml
118+
project: my-project
80119
auth:
81120
type: oidc
82-
...
83-
username: _USERNAME_
84-
password: _PASSWORD_
85-
client_secret: _CLIENT_SECRET__
86121
```
87122

88-
Below is an example of feast full OIDC client auth configuration:
123+
**Client credentials / ROPC flow** (existing behavior, unchanged):
89124
```yaml
90125
project: my-project
91126
auth:
@@ -97,6 +132,12 @@ auth:
97132
auth_discovery_url: http://localhost:8080/realms/master/.well-known/openid-configuration
98133
```
99134

135+
When using client credentials or ROPC flows, the `verify_ssl` setting also applies to the discovery and token endpoint requests.
136+
137+
#### Multi-Token Support (OIDC + Kubernetes Service Account)
138+
139+
When the Feast server is configured with OIDC auth and deployed on Kubernetes, the `OidcTokenParser` can handle both Keycloak JWT tokens and Kubernetes service account tokens. Incoming tokens that contain a `kubernetes.io` claim are validated via the Kubernetes Token Access Review API and the namespace is extracted from the authenticated identity — no RBAC queries are performed, so the server service account only needs `tokenreviews/create` permission. All other tokens follow the standard OIDC/Keycloak JWKS validation path. This enables `NamespaceBasedPolicy` enforcement for service account tokens while using `GroupBasedPolicy` and `RoleBasedPolicy` for OIDC user tokens.
140+
100141
### Kubernetes RBAC Authorization
101142
With Kubernetes RBAC Authorization, the client uses the service account token as the authorizarion bearer token, and the
102143
server fetches the associated roles from the Kubernetes RBAC resources. Feast supports advanced authorization by extracting user groups and namespaces from Kubernetes tokens, enabling fine-grained access control beyond simple role matching. This is achieved by leveraging Kubernetes Token Access Review, which allows Feast to determine the groups and namespaces associated with a user or service account.

infra/feast-operator/api/v1/featurestore_types.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,34 @@ type KubernetesAuthz struct {
713713
// OidcAuthz defines the authorization settings for deployments using an Open ID Connect identity provider.
714714
// https://auth0.com/docs/authenticate/protocols/openid-connect-protocol
715715
type OidcAuthz struct {
716-
SecretRef corev1.LocalObjectReference `json:"secretRef"`
716+
// OIDC issuer URL. The operator appends /.well-known/openid-configuration to derive the discovery endpoint.
717+
// +optional
718+
// +kubebuilder:validation:Pattern=`^https://\S+$`
719+
IssuerUrl string `json:"issuerUrl,omitempty"`
720+
// Secret with OIDC properties (auth_discovery_url, client_id, client_secret). issuerUrl takes precedence.
721+
// +optional
722+
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"`
723+
// Key in the Secret containing all OIDC properties as a YAML value. If unset, each key is a property.
724+
// +optional
725+
SecretKeyName string `json:"secretKeyName,omitempty"`
726+
// Env var name for client pods to read an OIDC token from. Sets token_env_var in client config.
727+
// +optional
728+
TokenEnvVar *string `json:"tokenEnvVar,omitempty"`
729+
// Verify SSL certificates for the OIDC provider. Defaults to true.
730+
// +optional
731+
VerifySSL *bool `json:"verifySSL,omitempty"`
732+
// ConfigMap with the CA certificate for self-signed OIDC providers. Auto-detected on RHOAI/ODH.
733+
// +optional
734+
CACertConfigMap *OidcCACertConfigMap `json:"caCertConfigMap,omitempty"`
735+
}
736+
737+
// OidcCACertConfigMap references a ConfigMap containing a CA certificate for OIDC provider TLS.
738+
type OidcCACertConfigMap struct {
739+
// ConfigMap name.
740+
Name string `json:"name"`
741+
// Key in the ConfigMap holding the PEM certificate. Defaults to "ca-bundle.crt".
742+
// +optional
743+
Key string `json:"key,omitempty"`
717744
}
718745

719746
// TlsConfigs configures server TLS for a feast service. in an openshift cluster, this is configured by default using service serving certificates.

infra/feast-operator/api/v1/zz_generated.deepcopy.go

Lines changed: 36 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

infra/feast-operator/bundle/manifests/feast-operator.clusterserviceversion.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ metadata:
5050
}
5151
]
5252
capabilities: Basic Install
53-
createdAt: "2026-03-10T20:00:10Z"
53+
createdAt: "2026-04-07T13:49:25Z"
5454
operators.operatorframework.io/builder: operator-sdk-v1.38.0
5555
operators.operatorframework.io/project_layout: go.kubebuilder.io/v4
5656
name: feast-operator.v0.61.0
@@ -175,6 +175,17 @@ spec:
175175
- get
176176
- patch
177177
- update
178+
- apiGroups:
179+
- monitoring.coreos.com
180+
resources:
181+
- servicemonitors
182+
verbs:
183+
- create
184+
- delete
185+
- get
186+
- list
187+
- patch
188+
- watch
178189
- apiGroups:
179190
- policy
180191
resources:
@@ -259,6 +270,7 @@ spec:
259270
value: quay.io/feastdev/feature-server:0.61.0
260271
- name: RELATED_IMAGE_CRON_JOB
261272
value: quay.io/openshift/origin-cli:4.17
273+
- name: OIDC_ISSUER_URL
262274
image: quay.io/feastdev/feast-operator:0.61.0
263275
livenessProbe:
264276
httpGet:

0 commit comments

Comments
 (0)