feat(bff): introduce support of multiple auth methods (internal, user_token)#918
Conversation
|
/assign @alexcreasy |
|
@dhirajsb pls review |
| ctx, cancel := context.WithTimeout(ctx, 30*time.Second) | ||
| defer cancel() | ||
|
|
||
| for _, verb := range []string{"get", "list"} { |
There was a problem hiding this comment.
This is copied from the old implementation, but I don't think we need both get and list when no name attribute is supplied to a SSAR.
There was a problem hiding this comment.
Christian, I've added to my list to double check with MR team, but I'm almost sure that they explicitly asked for this.
| if err != nil { | ||
| kc.Logger.Warn("user is not allowed to list namespaces or failed to list namespaces") | ||
| return []corev1.Namespace{}, nil | ||
| } |
There was a problem hiding this comment.
log error instead? Also, do include the error in the log to help with debugging.
Why does this function suppress the error instead of sending it back to the caller?
| } | ||
| var identity *kubernetes.RequestIdentity | ||
|
|
||
| switch app.config.AuthMethod { |
There was a problem hiding this comment.
I don't particularly like seeing a switch on switch app.config.AuthMethod { (here or elsewhere) aside from creating a single client. It defeats the purpose of abstracting the implementation details of the individual clients to have different behaviors.
| return services, nil | ||
| } | ||
|
|
||
| func (kc *SharedClientLogic) GetServiceDetailsByName(sessionCtx context.Context, namespace string, serviceName string) (ServiceDetails, error) { |
There was a problem hiding this comment.
This is inefficient to fetch all services only to find the one by name instead of fetching the one by name in the first place.
| type RequestIdentity struct { | ||
| UserID string | ||
| Groups []string | ||
| Token string | ||
| } |
There was a problem hiding this comment.
Ideally each auth method should have their own type to avoid having to mix every property together. But I can accept that all these properties have a relation to identity to stay together as well.
There was a problem hiding this comment.
We discussed this in a call and got an agreement that this type will be reused on MR API calls.
|
@christianvogt thank you so much for the review, I believe I've addressed all our points. |
80e4980 to
0e6efb1
Compare
|
Tested the PR as per the /lgtm |
|
@christianvogt: changing LGTM is restricted to collaborators DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
…_token) - Introduce auth-method flag (default: internal) to select auth strategy - internal: uses pod service account (in-cluster) or current kubeconfig (local) - user_token: uses bearer token from X-Forwarded-Access-Token header - Implement separate SAR (internal) and SSAR (user_token) logic - Clarify behavior and usage in README Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
… reuse methods) Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
lucferbux
left a comment
There was a problem hiding this comment.
There's a couple of things I wanna discuss first I don't fully understand the PR, moving the conversation to other channels.
| type User struct { | ||
| UserID string `json:"userId"` | ||
| ClusterAdmin bool `json:"clusterAdmin"` | ||
| UserID string `json:"userId"` |
There was a problem hiding this comment.
| UserID string `json:"userId"` | |
| UserID string `json:"userId"` | |
| ClusterAdmin bool `json:"clusterAdmin"` |
| } | ||
| var formattedUser = user | ||
|
|
||
| if formattedUser == "" { |
There was a problem hiding this comment.
We should still get both the ClusterAdmin check and the username
| if formattedUser == "" { | ||
| //if we are using token based auth, we still need to implement how to | ||
| //safely get the user from the token | ||
| formattedUser = "unknown" |
There was a problem hiding this comment.
You can get the user from a token with kubectl auth whoami, this calls /apis/authentication.k8s.io/v1/selfsubjectreviews, which returns values like:
ATTRIBUTE VALUE
Username kubernetes-admin
Groups [kubeadm:cluster-admins system:authenticated]
Extra: authentication.kubernetes.io/credential-id [X509SHA256=230423670e4531f1d6b8b5a8a9680954b3bb95b35353019a1944b29d5ad03148]There was a problem hiding this comment.
Thanks for the pointers Lucas! I fixed it 1a27427
…ract user name in token case Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
|
Just commenting here, @alexcreasy @christianvogt if @ederign changes the token to |
- Introduced `AuthTokenHeader` and `AuthTokenPrefix` fields to EnvConfig - Default token extraction uses `Authorization` header with `Bearer ` prefix - Updated TokenClientFactory to dynamically parse token using configured header and prefix - Added new CLI flags: `--auth-token-header` and `--auth-token-prefix` - Updated Makefile to support overriding header and prefix via `AUTH_TOKEN_HEADER` and `AUTH_TOKEN_PREFIX` - Improved error messages and testability of token parsing logic - Added Ginkgo unit tests for TokenClientFactory.ExtractRequestIdentity with and without prefixes - Cleaned up README: - Replaced all `X-Forwarded-Access-Token` references with `Authorization: Bearer` - Documented how to override token header and prefix via CLI, env, or Makefile Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
|
Re-tested following the curl commands successfully. |
|
@christianvogt: changing LGTM is restricted to collaborators DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
alexcreasy
left a comment
There was a problem hiding this comment.
I've just identified one small change, otherwise this looks all good!
| allowed, err := client.CanAccessServiceInNamespace(r.Context(), identity, namespace, serviceName) | ||
|
|
||
| if err != nil { | ||
| app.forbiddenResponse(w, r, "failed to perform SAR: %v") |
There was a problem hiding this comment.
It's good security practice not to return any additional information to the user for an authentication response as you can leak clues to the internal architecture of the system, that could lead to CWEs like a response discrepancy.
It's probably a good idea to alter the forbiddenResponse function to not write the error message to the http response and just log it, whilst returning simply 403 forbidden.
There was a problem hiding this comment.
Thank you for the review!
|
/lgtm |
|
/approve |
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: alexcreasy The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
Signed-off-by: Eder Ignatowicz <ignatowicz@gmail.com>
…ocker digest to e421039 (kubeflow#918) Signed-off-by: konflux-internal-p02 <170854209+konflux-internal-p02[bot]@users.noreply.github.com> Co-authored-by: konflux-internal-p02[bot] <170854209+konflux-internal-p02[bot]@users.noreply.github.com>

Description
This PR introduces a pluggable kubernetes client authentication mechanism for the Model Registry UI BFF, enabling support for two modes:
internal (default and current one) — uses the credentials of the backend service:
user_token — uses a user-provided token from the
Authorization: Bearer <your-token>headerTo support this, I introduced a KubernetesClientFactory abstraction that cleanly encapsulates all logic related to Kubernetes client creation. This avoids leaking the client instance throughout the codebase, enables per-request instantiation for token-based clients, and simplifies mocking in tests. The result is a more modular, secure, and testable architecture.
File-by-file breakdown of significant changes
Makefile
README.md
cmd/main.go
internal/api/app.go
internal/api/middleware.go
internal/api/errors.go
internal/api/*.go (handler files)
internal/api/*_test.go
internal/integrations/kubernetes
internal/integrations/kubernetes/k8mocks
How Has This Been Tested?
This is how I tested and my suggestion for anyone reviewing this is to test it carefully, has this touches in a crucial part of our bff.
AuthMethodInternal = "internal" (default)
First, to make sure that I didn't break anything on the AuthMethodInternal = "internal" (default)
Run the front-end and do a quick sanity check
i.e. user@example.com should be able to see all namespaces (cluster admin on env test)
On a kubeflow installation, change the deployment to use image: quay.io/ederignatowicz/model-registry-ui-auth:latest . I've build this image to test it... you will see on the logs a "Starting Model Registry"
A word of warning. If you have an old installation, make sure to update the healthcheck path for api/healthcheck instead of api/v1/healthcheck
Do a quick sanity check there just to make sure I didn't break anything! :)
AuthMethodInternal = "user_token"
Let's try the new auth mode.
This will work just on BFF because our front end doesn't support tokens yet. Standalone=true enable namespaces endpoint
make run MOCK_K8S_CLIENT=false MOCK_MR_CLIENT=true AUTH_METHOD=user_token STANDALONE_MODE=true
1-) Create SAs
2-) Create namespaces
3-) Create roles
do the same for namespace ns-bella
4-) Apply them to the right namespaces:
5-) Create RoleBindings
Bind
admin-dorato both namespacesBind
limited-bellato only ns-bella5-) Create Model Registry Services
change ns name and apply to ns-bella
6-) Get Tokens
7-) Custom Headers
Merge criteria:
DCOcheck)ok-to-testhas been added to the PR.If you have UI changes