Skip to content

feat: Extended OIDC support to extract groups & namespaces and token injection with multiple methods#6089

Open
aniketpalu wants to merge 34 commits intofeast-dev:masterfrom
aniketpalu:oidc-support
Open

feat: Extended OIDC support to extract groups & namespaces and token injection with multiple methods#6089
aniketpalu wants to merge 34 commits intofeast-dev:masterfrom
aniketpalu:oidc-support

Conversation

@aniketpalu
Copy link
Copy Markdown
Contributor

@aniketpalu aniketpalu commented Mar 10, 2026

What this PR does / why we need it:

Extracts groups and namespaces claims from the decoded JWT in OidcTokenParser.user_details_from_access_token() and passes them to the User object.

Previously, the OIDC token parser only read preferred_username and resource_access roles from the JWT, always returning User(username, roles) with empty groups and namespaces. This meant that GroupBasedPolicy, NamespaceBasedPolicy, and CombinedGroupNamespacePolicy could never grant access for OIDC-authenticated users — even when the JWT contained valid claims.


Server-Side Changes

Files: oidc_token_parser.py, utils.py
When auth.type: oidc, the Feast server now handles two types of incoming tokens:

  • Keycloak JWTs (from human users via UI/Swagger) — validated against Keycloak JWKS, extracts preferred_username, groups, and roles. Used by GroupBasedPolicy.
  • Kubernetes SA tokens (from workbench pods) — detected via kubernetes.io claim, delegated to existing KubernetesTokenParser which validates via TokenAccessReview and extracts namespace. Used by NamespaceBasedPolicy.
    Token type detection happens before any cryptographic validation — a lightweight unverified decode checks for the kubernetes.io claim. 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.py
OidcAuthClientManager.get_token() now supports multiple token sources with clear priority:

  • Explicit config (exclusive — only one is used): token, token_env_var, or client_secret + IDP network call
  • Bare config fallbacks (when nothing explicit is set): FEAST_OIDC_TOKEN env var, then mounted SA token file
    This means a workbench pod with just auth: {type: oidc} automatically picks up its SA token. Human users can set FEAST_OIDC_TOKEN. Existing client_credentials/ROPG flows are unchanged.

Which issue(s) this PR fixes:

#6088

Misc


Open with Devin

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
@aniketpalu aniketpalu requested a review from a team as a code owner March 10, 2026 12:50
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…t, reject stray auth_discovery_url/client_id without client_secret

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

… _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>
devin-ai-integration[bot]

This comment was marked as resolved.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
@aniketpalu aniketpalu marked this pull request as draft March 11, 2026 10:54
devin-ai-integration[bot]

This comment was marked as resolved.

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…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>
devin-ai-integration[bot]

This comment was marked as resolved.

aniketpalu and others added 3 commits March 23, 2026 21:20
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>
@aniketpalu aniketpalu force-pushed the oidc-support branch 2 times, most recently from bd69747 to 2ff5568 Compare March 23, 2026 16:00
devin-ai-integration[bot]

This comment was marked as resolved.

…g for K8s token validation

Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
Made-with: Cursor
Signed-off-by: Aniket Paluskar <apaluska@redhat.com>
@aniketpalu aniketpalu force-pushed the oidc-support branch 2 times, most recently from 09858f9 to 9c26b7b Compare March 23, 2026 16:21
…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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raise ValueError instead of assert

"""
from kubernetes import client, config

config.load_incluster_config()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re-creates the API client on every SA token validation, may be initialized once and reused ?

)

@staticmethod
def _read_sa_token() -> str | None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_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>
devin-ai-integration[bot]

This comment was marked as resolved.

Srihari1192 pushed a commit to Srihari1192/feast that referenced this pull request Mar 25, 2026
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.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +84 to +85
description: The name of the environment variable that client
pods will use to read a pre-existing OIDC token.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify which client pods we are talking about here ?

type: object
required:
- feastProject
- replicas
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants