-
Notifications
You must be signed in to change notification settings - Fork 82
feat(bff): introduce support of multiple auth methods (internal, user_token) #918
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
var identity *kubernetes.RequestIdentity | ||
|
||
switch app.config.AuthMethod { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
return services, nil | ||
} | ||
|
||
func (kc *SharedClientLogic) GetServiceDetailsByName(sessionCtx context.Context, namespace string, serviceName string) (ServiceDetails, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
type RequestIdentity struct { | ||
UserID string | ||
Groups []string | ||
Token string | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 In 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>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a couple of things I wanna discuss first I don't fully understand the PR, moving the conversation to other channels.
@@ -1,6 +1,5 @@ | |||
package models | |||
|
|||
type User struct { | |||
UserID string `json:"userId"` | |||
ClusterAdmin bool `json:"clusterAdmin"` | |||
UserID string `json:"userId"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UserID string `json:"userId"` | |
UserID string `json:"userId"` | |
ClusterAdmin bool `json:"clusterAdmin"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lucferbux fixed on 1a27427
|
||
if formattedUser == "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should still get both the ClusterAdmin
check and the username
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed on 1a27427
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the pointers Lucas! I fixed it 1a27427
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…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 In 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've just identified one small change, otherwise this looks all good!
} | ||
|
||
allowed, err := app.kubernetesClient.PerformSARonSpecificService(user, userGroups, namespace, modelRegistryID) | ||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll fix in a FUP PR!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
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-dora
to both namespacesBind
limited-bella
to only ns-bella5-) Create Model Registry Services
change ns name and apply to ns-bella
6-) Get Tokens
7-) Custom Headers
Merge criteria:
DCO
check)ok-to-test
has been added to the PR.If you have UI changes