feat: Extended OIDC support to extract groups & namespaces and token injection with multiple methods#6089
feat: Extended OIDC support to extract groups & namespaces and token injection with multiple methods#6089aniketpalu wants to merge 34 commits intofeast-dev:masterfrom
Conversation
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
…tacting the identity provider Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
…t, reject stray auth_discovery_url/client_id without client_secret Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
… _is_oidc_client_config helper Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
…one == None Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
…ed KeyError Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
bcd3bd3 to
b15294d
Compare
…y, 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>
b15294d to
712a2b9
Compare
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
bd69747 to
2ff5568
Compare
…g for K8s token validation Signed-off-by: Aniket Paluskar <apaluska@redhat.com> Made-with: Cursor
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
09858f9 to
9c26b7b
Compare
…o upn (Azure AD / Entra ID) Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
|
|
||
| def _fetch_token_from_idp(self) -> str: | ||
| """Obtain an access token via client_credentials or ROPG flow.""" | ||
| assert self.auth_config.auth_discovery_url is not None |
There was a problem hiding this comment.
raise ValueError instead of assert
| """ | ||
| from kubernetes import client, config | ||
|
|
||
| config.load_incluster_config() |
There was a problem hiding this comment.
re-creates the API client on every SA token validation, may be initialized once and reused ?
| ) | ||
|
|
||
| @staticmethod | ||
| def _read_sa_token() -> str | None: |
There was a problem hiding this comment.
| def _read_sa_token() -> str | None: | |
| def _read_sa_token() -> Optional[str]: |
| """ | ||
| if "preferred_username" in data: | ||
| return data["preferred_username"] | ||
| if "upn" in data: |
There was a problem hiding this comment.
_get_intra_comm_user also need to read upn ?
…, tokenEnvVar, verifySSL CRD fields Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Remove 5 permissions test files (38 tests) that would conflict with feast-dev#6089 which restructures auth_model.py validation logic and OIDCDiscoveryService constructor. Removed files: - test_auth_model.py (14 tests) - test_enforcer.py (7 tests) - test_oidc_service.py (7 tests) - test_token_extractor.py (7 tests) - test_token_parser.py (3 tests) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
||
| #### Multi-Token Support (OIDC + Kubernetes Service Account) | ||
|
|
||
| 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. |
There was a problem hiding this comment.
Do we support OIDC token passing from ODH Notebook instead of service account token ? This we alreayd support for kubernetes token.
Not a blocker though, just for consistency.
| description: The name of the environment variable that client | ||
| pods will use to read a pre-existing OIDC token. |
There was a problem hiding this comment.
Could you clarify which client pods we are talking about here ?
| type: object | ||
| required: | ||
| - feastProject | ||
| - replicas |
There was a problem hiding this comment.
Is this change from auto-scaling commit. Does this PR need rebase ?
| raise AuthenticationError("Invalid token.") | ||
|
|
||
| @staticmethod | ||
| async def _validate_k8s_sa_token_and_extract_namespace(access_token: str) -> User: |
There was a problem hiding this comment.
I think we should create a common function for k8s sa token extraction to be used between both kubeterntes auth and OIDC auth.
| """ | ||
| partial = [name for name, vals in groups.items() if any(vals) and not all(vals)] | ||
| if partial: | ||
| raise ValueError( |
There was a problem hiding this comment.
The current message is generic and doesn't specify which fields are actually required. A more helpful error would list the incomplete group's field names. For example:
raise ValueError(
f"Incomplete configuration for '{partial[0]}': "
f"all required fields for this group must be set together. "
f"Check the documentation for valid credential combinations."
)Or even better, you could inspect the groups parameter to show exactly what's missing:
raise ValueError(
f"Incomplete configuration for '{partial[0]}': "
f"configure all of these fields together, or none at all."
)This would give users clearer guidance on what went wrong and how to fix it.
| unverified = jwt.decode(access_token, options={"verify_signature": False}) | ||
| except jwt.exceptions.DecodeError as e: | ||
| raise AuthenticationError(f"Failed to decode token: {e}") | ||
| return isinstance(unverified.get("kubernetes.io"), dict) |
There was a problem hiding this comment.
This runs on every non-intra-comm request before the normal OIDC path. The decoded payload is then discarded and the token is decoded again via _decode_token() for standard OIDC tokens. Consider caching the unverified decode result or restructuring to decode once.
What this PR does / why we need it:
Extracts
groupsandnamespacesclaims from the decoded JWT inOidcTokenParser.user_details_from_access_token()and passes them to theUserobject.Previously, the OIDC token parser only read
preferred_usernameandresource_accessroles from the JWT, always returningUser(username, roles)with empty groups and namespaces. This meant thatGroupBasedPolicy,NamespaceBasedPolicy, andCombinedGroupNamespacePolicycould never grant access for OIDC-authenticated users — even when the JWT contained valid claims.Server-Side Changes
Files:
oidc_token_parser.py,utils.pyWhen
auth.type: oidc, the Feast server now handles two types of incoming tokens:preferred_username,groups, androles. Used byGroupBasedPolicy.kubernetes.ioclaim, delegated to existingKubernetesTokenParserwhich validates via TokenAccessReview and extracts namespace. Used byNamespaceBasedPolicy.Token type detection happens before any cryptographic validation — a lightweight unverified decode checks for the
kubernetes.ioclaim. If the K8s API is unavailable (non-K8s deployment), the server falls back to Keycloak-only mode.Client-Side Changes
Files:
oidc_authentication_client_manager.py,auth_model.py,repo_config.pyOidcAuthClientManager.get_token()now supports multiple token sources with clear priority:token,token_env_var, orclient_secret+ IDP network callFEAST_OIDC_TOKENenv var, then mounted SA token fileThis means a workbench pod with just
auth: {type: oidc}automatically picks up its SA token. Human users can setFEAST_OIDC_TOKEN. Existingclient_credentials/ROPG flows are unchanged.Which issue(s) this PR fixes:
#6088
Misc