feature: introduce group-level access support in central dashboard and KFAM - #317
feature: introduce group-level access support in central dashboard and KFAM#317adrianAzoitei wants to merge 15 commits into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
7fcb123 to
058420e
Compare
christian-heusel
left a comment
There was a problem hiding this comment.
Hey adrian, thanks for driving this forward! 😄
I see that even though you're aware of the AI Policy your commits do not carry any Assisted-By: <...> / Co-authored-by: <...> tags, so I wanted to make sure that you're aware of these specific requirements around the Kubeflow AI Policy. 🤗 🤖
|
@christian-heusel I went with the more convenient option of adding the disclosure in the PR description 😄 . Will amend the commits where it's relevant too. |
|
Team, do we have some updates for this FR? We are also facing same issue and this group binding will resolve much issues. |
There was a problem hiding this comment.
🟡 Changes recommended
Group lookup serialization, binding annotations, compatibility, and identity validation contain blocking defects.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Adds group-level namespace access across Central Dashboard and KFAM.
Changes:
- Propagates group identity into namespace discovery.
- Adds group contributor management and labels.
- Creates group RBAC and Istio authorization resources.
File summaries
| File | Description |
|---|---|
components/centraldashboard/types/index.d.ts |
Adds user groups. |
components/centraldashboard/public/components/namespace-selector.js |
Labels direct/group access. |
components/centraldashboard/public/components/manage-users-view-contributor.pug |
Adds group controls. |
components/centraldashboard/public/components/manage-users-view-contributor.js |
Manages group contributors. |
components/centraldashboard/public/components/manage-users-view-contributor_test.js |
Tests contributor UI. |
components/centraldashboard/public/components/logout-button.js |
Formatting cleanup. |
components/centraldashboard/app/server.ts |
Configures groups header. |
components/centraldashboard/app/clients/profile_controller.ts |
Extends KFAM client. |
components/centraldashboard/app/attach_user_middleware.ts |
Parses group identity. |
components/centraldashboard/app/attach_user_middleware_test.ts |
Tests group parsing. |
components/centraldashboard/app/api_workgroup.ts |
Handles group bindings and deduplication. |
components/centraldashboard/app/api_workgroup_test.ts |
Tests group namespace access. |
components/access-management/main.go |
Adds group configuration. |
components/access-management/kfam/model_binding.go |
Generalizes binding subjects. |
components/access-management/kfam/bindings.go |
Implements group RBAC and authorization. |
components/access-management/kfam/bindings_test.go |
Tests binding filtering. |
components/access-management/kfam/api_default.go |
Extends KFAM endpoints. |
components/access-management/kfam/api_default_test.go |
Tests groups queries. |
components/access-management/api/swagger.yaml |
Documents group APIs. |
Review details
Suppressed comments (4)
components/access-management/kfam/api_default.go:282
- The dashboard client serializes this parameter as
group-a,group-b, butjson.Unmarshalonly accepts JSON such as["group-a","group-b"]. Consequently every/env-inforequest for a user with at least one group receives a 401 from KFAM. The linked issue also specifies a comma-separated query parameter; parse that format here and align the Swagger/tests, or serialize JSON consistently end to end.
err = json.Unmarshal([]byte(queries.Get("groups")), &groups)
components/centraldashboard/app/api_workgroup.ts:249
cTypeis optional, and the validation above explicitly treats an omitted value as a user, but forwardingundefinedmakesmapSimpleBindingToWorkgroupBindingcallcharAton it and return a 500. Legacy add/remove request bodies omitcType, so preserve that path by defaulting it touser.
kind: cType,
components/access-management/kfam/api_default.go:348
- This checks only the header length, not that the configured prefix is actually present. A value with arbitrary leading text of the same length is accepted and stripped before the result is used by
isOwnerOrAdmin; requirestrings.HasPrefixso malformed identity headers cannot be interpreted as another user.
if h := header.Get(c.userIdHeader); len(h) > len(c.userIdPrefix) {
components/centraldashboard/app/api_workgroup.ts:239
cTypecomes from request JSON, so the TypeScript union does not validate it at runtime. Values outsideuser/groupskip the email check and are converted into arbitrary RBAC subject kinds; notablycType: "User"creates a valid User while bypassing the email requirement. Reject unknown values before mapping the binding.
if ((!cType || cType === "user") && !EMAIL_RGX.test(contributor)) {
- Files reviewed: 18/19 changed files
- Comments generated: 4
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Assisted-by: claude-sonnet-4.6 Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Assisted-by: claude-sonnet-4.6 Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
… group tests Assisted-by: claude-sonnet-4.6 Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
…n ns Assisted-by: claude-sonnet-4.6 Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 19 changed files in this pull request and generated 4 comments.
Suppressed comments (5)
components/access-management/kfam/bindings.go:143
- The annotation value must be the subject name, not its kind. As written, newly created bindings are annotated
user: Userorgroup: Group;Listcompares that value with the requested email/group name, so these bindings never appear in user/group-filtered reads such as/env-info.
Annotations: map[string]string{subjectKind: binding.Subject.Kind, ROLE: binding.RoleRef.Name},
components/centraldashboard/app/api_workgroup.ts:239
cTyperemains optional for the existing contributor API, but a request that omits it passes email validation and then callssubjectKind.charAtwithundefined, returning a server error. Runtime input can also supply an unsupported type and bypass user validation. Default omitted values touserand reject anything exceptuser/groupbefore constructing the binding.
if ((!cType || cType === "user") && !EMAIL_RGX.test(contributor)) {
components/access-management/kfam/model_binding.go:17
- The description says existing user cases remain untouched, but renaming the v1 wire field from
usertosubjectbreaks existing KFAM clients. Requests using the previous schema deserialize with a nilSubjectand then panic when creation/deletion dereferences it, while response consumers no longer finduser. Preserve a backward-compatible alias/migration path or version this API change.
Subject *rbacv1.Subject `json:"subject,omitempty"`
components/centraldashboard/app/api_workgroup.ts:113
- Correct the typo in the precedence documentation.
* Most powerful role that matches takes precendece.
components/access-management/main.go:57
- The new
--groups-headeroption is nonfunctional:groupsHeaderis passed toNewKfamClient, but that constructor neither stores nor uses it, and no KFAM handler reads a groups header. Operators changing this advertised flag will see no behavior change. Either wire it into request processing or remove the flag/parameter.
flag.StringVar(&groupsHeader, GROUPSHEADER, "groups", "Key of request header containing groups JSON array")
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
- initial assumption about how istio might be passing the header is void Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Adrian Azoitei <azo.adrian@gmail.com>
Description
closes: #308
related: kubeflow/community-distribution#2910 (comment)
Demo setup
During testing the implementation, the following setup was used:
kindclusteruser@example.com- cluster adminuser1@example.com- member of thefoogroupnamespace-1andnamespace-2, both owned byuser@example.comprofile-user1owned byuser1@example.comAdding contributors
user@example.comadding:user1@example.comas a direct contributor tonamespace-1foogroup tonamespace-2, indirectly granting access touser1@example.comhere as well.RoleBindings and AuthorizationPolicies
The user cases are left untouched.
For the group cases, we see a
GroupRoleBinding innamespace-2.And the following
AuthorizationPolicy.NOTE: The approach to the
AuthorizationPolicyis still up for debate. See istio section in #308.Namespace selector
The namespace selector shows a

(direct)label when the user is a direct contributor - i.e.UserRoleBinding - to aProfile, and a
(group)one when the user is granted access through a group. This is next to the existing(Owner)label.What happens when there's a tie
If
user1@example.comis added both directly and through a group to the same profile, the direct membership takes precedence. In other words, the namespace selector does NOT change foruser1@example.comin the following case. ThecentraldashboardAPI deduplicates namespaces returned from/env-infoin this case.See

deduplicateNamespacesand the test case.TODO
GrouptypeRoleBindings: reading, adding, deletingAuthorizationPolicysuitable for group supportRequestAuthenticationin each namespace owned by a Profilekubeflow-groupsheaderRoleBindings based on bothkubeflow-useridandkubeflow-groupsheadersAI Policy Disclosure
See https://www.kubeflow.org/docs/about/ai_policy/.
claude-sonnet-4.6was used as a coding assistant, mostly on the frontend -centraldashboard- component. The model did NOT run in agentic mode, every generated snippet of code was reviewed and / or modified by the submitter.