Skip to content

Users, Groups & Permissions

Portalcrane's permission model has three layers: accounts (who can log in), groups (named sets of accounts), and folders (where permissions actually live). Understanding how they connect makes the rest of the access-control story straightforward.

graph LR
    U1[User: alice] --> G1[Group: backend-team]
    U2[User: bob] --> G1
    U3[User: carol] --> G2[Group: sre]
    G1 -- can_pull / can_push --> F1["Folder: backend/"]
    G2 -- can_pull / can_push_external --> F2["Folder: __root__"]

Accounts

Two kinds of accounts exist:

  • Built-in admin — the admin account (ADMIN_USERNAME). Its password is auto-generated and printed in the logs on first launch. It cannot be deleted, renamed, or demoted through the UI — only its password can be changed.
  • Local users — created via Settings → Accounts (POST /api/auth/users), each with:
  • a username and password (minimum 8 characters),
  • an is_admin flag (full access to everything, bypassing folder checks entirely),
  • a disabled flag (blocks login without deleting the account or its group memberships),
  • a can_bypass_vuln_block flag (lets the account push an image even when Trivy flagged a blocking-severity CVE — see below).

OIDC-provisioned accounts (see Authentication & OIDC) show up in the same list with auth_source: "oidc" — they have no local password and can't have one set through the API.

curl -X POST http://<host>:8000/api/auth/users \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"username": "alice", "password": "correct-horse-battery", "is_admin": false}'

Admins always bypass folder checks

An account with is_admin: true has unconditional pull/push access to every folder and every external registry. Folder permissions only matter for non-admin accounts.

Vulnerability-block bypass

Independently of folder permissions, the Staging Pipeline and Transfers refuse to push an image whose Trivy scan flagged a blocking severity (VULN_SCAN_SEVERITIES, see Vulnerability Scanning) — the API rejects the push with 403 Forbidden and the UI hides the push controls for that job.

An admin can grant an individual account a standing exception to this block by setting can_bypass_vuln_block: true on the account, from Settings → Accounts (edit the user, toggle CVE bypass) or via the API:

curl -X PATCH http://<host>:8000/api/auth/users/<user_id> \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"can_bypass_vuln_block": true}'

When the flag is set, a staging job stuck at scan_vulnerable shows its push panel again (with a warning banner), and a transfer whose scan is blocked proceeds to push instead of stopping at scan_vulnerable.

Admins always bypass this too

An account with is_admin: true never needs the flag explicitly — it already pushes past a blocking scan unconditionally, the same way it bypasses folder checks. can_bypass_vuln_block only matters for non-admin accounts you want to trust with this specific exception without making them a full admin.

Groups

Folder permissions are granted to groups, never directly to individual users. A group is just a named list of usernames (Settings → Groups, /api/groups):

curl -X POST http://<host>:8000/api/groups \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "backend-team", "description": "Backend engineers"}'

curl -X PUT http://<host>:8000/api/groups/<group_id>/members \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"username": "alice"}'

A user's effective access to a folder is the union of the permissions of every group they belong to — if alice is in both backend-team (push access to backend/) and sre (pull access to __root__), she has both.

Deleting a group cascades: its permission entries are purged from every folder it had been granted access to. Removing a user (local or OIDC) removes them from every group, which is enough to fully revoke their inherited access without touching folder configuration.

Why groups instead of per-user grants?

Modeling permissions on groups means onboarding a new team member is "add them to a group," not "recreate five folder grants." It also means a folder's permission list stays small and legible even as headcount changes.

Roles at a glance

Role Granted by Scope
Admin is_admin: true on the account Everything — every folder, every registry, all settings
Folder pull/push Group membership + a folder permission grant Read/write on the local registry, scoped to a namespace prefix
External pull/push Group membership + a folder permission grant with can_pull_external/can_push_external Transfers with external registries via the Staging/Transfer pages
No grant (default) Denied — a user whose groups have no entry on the matched folder gets no access at all

The full mechanics of folder matching and the four independent permission flags are covered in Folder-Based Access Control.

Personal Access Tokens

Any authenticated account — local or OIDC — can generate its own Personal Access Tokens for docker login or API access, without an admin having to issue credentials manually.