Skip to content

Origin/18124 custom role feature#22815

Open
maxgraustenzel-create wants to merge 16 commits into
goharbor:mainfrom
maxgraustenzel-create:origin/18124-custom-role-feature
Open

Origin/18124 custom role feature#22815
maxgraustenzel-create wants to merge 16 commits into
goharbor:mainfrom
maxgraustenzel-create:origin/18124-custom-role-feature

Conversation

@maxgraustenzel-create

Copy link
Copy Markdown

This change adds custom roles to harbor.

IMPORTANT: This is a POC, the feature is not finalized. The goal is to have some discussion about the proposed design before finalizing the implementation.

Directly Solved Issues:

#18124 RBAC - be able to create a custom role
#18143 Create custom project roles
#21222 Add role for security auditor
#15711 Add a readonly admin role in Harbor
#12062 Make permission system more fine-grained
#21306 - Read-only auditor role (December 2024 - very recent!)
#8632 - Limited guest role
#1486 - Guest shouldn't see all members (from 2016!)
...

Partially Solved Issues:

#10159 - Repository-level permissions (48 👍 - highest demand!)
...

Design changes :

  • Store permissions in role_permission and permission_policy table (from rbac_role.go, migration with init_permissions.sql)
  • Permission management functionality for the system administrator (swagger + ui)
  • Dynamic permission selection when adding members
  • Caching of role permissions for the user and session in rbac_user.go (contains now the roles object as part of session with the associated permissions)

TODO (Partial):

  • Bugs (display, translation, ...)
  • Implement security (avoid escalation when creating robots and assigning other members)
  • Implement testcases

For more details:

Harbor_Custom_Roles_CURRENT_STATUS.md
Harbor_Custom_Roles_Documentation.pdf
https://claude.ai/share/98f704a9-f643-41d6-828e-f2afc81e68bf

@chlins

chlins commented Feb 5, 2026

Copy link
Copy Markdown
Member

@maxgraustenzel-create Please first submit a proposal in the community repository for discussion.

@github-actions

github-actions Bot commented Apr 6, 2026

Copy link
Copy Markdown

This PR is being marked stale due to a period of inactivty. If this PR is still relevant, please comment or remove the stale label. Otherwise, this PR will close in 30 days.

@github-actions github-actions Bot added the Stale label Apr 6, 2026
@Max-303

Max-303 commented Apr 10, 2026

Copy link
Copy Markdown

@maxgraustenzel-create Please first submit a proposal in the community repository for discussion.

This is the proposal:
goharbor/community#276

@github-actions github-actions Bot removed the Stale label Apr 11, 2026
@pknomad

pknomad commented May 14, 2026

Copy link
Copy Markdown

Tagging issue #18308 to this since it's also related to some of the issues opened.

@maxgraustenzel-create maxgraustenzel-create force-pushed the origin/18124-custom-role-feature branch from 1a2ee15 to 236bb14 Compare May 20, 2026 14:44
maxgraustenzel-create and others added 16 commits June 2, 2026 10:59
- Portal: replace hardcoded built-in role buttons in project member
  list, add-member, and add-group dialogs with dynamic role list loaded
  from API; built-in roles show translated names + blue badge; custom
  roles listed after a divider; scroll when list overflows
- Portal: role column in member datagrid now resolves role name via API
  (translated for built-ins, raw name for custom roles)
- Backend: fire audit log events for role create / update / delete;
  add UpdateRoleEvent topic and metadata; subscribe all three topics
  in handler init so events actually reach the audit log handler
- Backend: fix typo in DeleteRoleEvent audit message ("robot" → "role")
- DB: add 0190 migration schema placeholder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend: add checkNoEscalation to member handler — CreateProjectMember
and UpdateProjectMember now verify that every permission in the target
role is held by the calling user before accepting the request. Sysadmins
pass through unconditionally via the RBAC layer. Returns 403 Forbidden
on escalation attempt, closing the TODO in member controller isValidRole.

Frontend: compute assignableRoleIds from the current user's own role
permissions (loaded via forkJoin with the member list). Built-in roles
below projectAdmin level and custom roles that are a strict subset of
the caller's permissions remain selectable; others are disabled and
visually dimmed in the action dropdown, add-member, and add-group dialogs.
Default selection in add-member/add-group modals snaps to the first
assignable role so the form is always valid on open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sysadmin_flag lives on SessionUserBackend; the resolved SessionUser type
only exposes has_admin_role — fixes TS2339 compilation error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Migration 0040 renames master → maintainer before 0190 runs,
so the UPDATE was silently skipping the maintainer role.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Role permission cache (L1 sync.Map + L2 Redis) in controller/role/controller.go
  eliminates per-request role_permission DB queries after first warm-up
- Restore isValidRole() validation in member controller
- Fix permissions endpoint: open to all authenticated users (used by project UI)
- Remove orm.Debug=true and leftover debug log.Debug("*** ...") statements
- Uncomment and fix RBAC evaluator tests for fork's 3-arg NewBuilderForUser
- Remove TODO placeholder comments from production code
- Add [TIMING:*] instrumentation to measure permission evaluation breakdown:
  Can(), project_get, project_assemble, list_roles_sql, builder, role_get
- Extended timing to project controller and DAO for detailed DB breakdown

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ckstop

The L1+L2 role-permission cache had two correctness gaps:
- L2 had no TTL (cache.Default() Expiration=0), so out-of-band SQL changes to
  role_permission were never reflected until a core restart.
- invalidateRole() cleared only the local L1; Get() reads L1 first, so other
  nodes served stale permissions until their process restarted.

Fix:
- Global Redis "version token" (role:version) bumped on every in-app write;
  nodes poll it throttled to ~1s and bump a local generation that invalidates
  all L1 entries → cross-node propagation within ~1s. Lock-free (atomics+CAS).
- 30-min TTL on both L1 (per-entry expiresAt) and L2 (Save expiration) as a
  self-healing backstop for out-of-band changes.
- Read order: version-refresh -> L1 (gen+TTL) -> L2 -> DB (populates both).
  Write order: bump version -> delete L2 -> delete L1.
- Documented the bounded cache-aside repopulation race.

Also fixes a pre-existing test bug: TestUpdateCustomRole never mocked
roleMgr.Update (failed on the committed baseline too).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings in upstream through eb944bb (incl. Angular 21 / Clarity 18 / Node 22
portal upgrade and go.mod OSS bumps). Backend auto-merged and builds clean.

Conflicts resolved (portal): kept the custom-roles feature logic while adopting
upstream's Angular-21 control-flow syntax:
- member.component.html: @if blocks + getMemberRoleDisplayName()
- add-group/add-group.component.html: @for over dynamic roles with builtin badges
- package-lock.json: took upstream's Angular-21 lockfile (fork adds no npm deps)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Angular 19+ makes components standalone by default. The fork's RolesComponent,
AddRoleComponent and RolePermissionsPanelComponent are declared in roles.module.ts,
so they must opt out of standalone (NG6008). Portal now builds green under
Angular 21 / Clarity 18 / Node 22.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a runtime toggle so Get() with permissions can skip L1/L2 entirely and read
the role + role_permission from the DB on every Can() call. Replaces the fragile
build-time source-stripping used for the no-cache perf baseline (which broke after
the version-key refactor) with a same-binary env switch. Off by default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Prepare the custom-roles branch for a clean merge request:
- Remove all [TIMING:*] perf-logging (Can/builder/evaluator/project_ctrl/
  list_roles_sql/security_ctx) and the timing-only changes in dao.go,
  session.go and project/controller.go (reverted to upstream).
- Drop the per-role permission lookup's timing scaffolding in
  rbac/project/evaluator.go, keeping the feature (ctl_r.Get) and adding
  proper error handling; collapse a redundant nested if-err.
- Remove the "TODO MGS" scratch comment and the commented-out dead code
  (convertScope/toScope/SetProject/CreateSec/IsValidSec) plus the now-unused
  regex vars and regexp import in controller/role/controller.go.

No functional change to the custom-roles feature or the permission cache.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
End-to-end checks for the custom-roles feature: role CRUD + built-in
immutability, built-in role permissions via ListRole, project/group/robot
member anti-escalation, sysadmin bypass, is_builtin display flags, and edge
cases (zero-permission role, empty-roles fallback).

Run: python3 tests/integration/test_custom_roles.py --url <harbor> --user admin --password <pw>

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add clrDgSelectionType="multi" to the roles datagrid so Clarity 18
renders the selection checkbox column (binding [(clrDgSelected)] to an
array alone is no longer sufficient), and migrate the row iteration from
*ngFor to @for to match every other datagrid in the portal. Without the
checkboxes admins could not select a role to enable the Edit/Delete
actions in the action bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@maxgraustenzel-create maxgraustenzel-create force-pushed the origin/18124-custom-role-feature branch from 850fcfc to 2af19e6 Compare June 17, 2026 14:56
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