Skip to content

feat(coderd/rbac): make organization-member a per-org system custom role#21359

Merged
geokat merged 49 commits intomainfrom
geokat/internal-1073-make-org-member-role-customizable-per-org
Jan 13, 2026
Merged

feat(coderd/rbac): make organization-member a per-org system custom role#21359
geokat merged 49 commits intomainfrom
geokat/internal-1073-make-org-member-role-customizable-per-org

Conversation

@geokat
Copy link
Contributor

@geokat geokat commented Dec 22, 2025

Part of a series (2 stacked PRs) that closes coder/internal#1073

  • PR 1/2 (this one)
  • PR 2/2

Migrating Org-Member Role to Database

Problem

The organization-member role’s permissions are generated in code (the built-in orgMember role). This keeps it easy to evolve alongside RBAC resource changes, but it prevents per-organization variation (e.g. introducing and respecting an organization’s workspace_sharing_disabled setting).

Solution: Database-Backed System Roles (per organization)

Store one organization-member role per organization in custom_roles with is_system=true. Create placeholder rows in the database (migration + trigger) and reconcile them at startup so the stored permissions stay in sync with the code-generated source of truth.

1. Schema Changes

  • Added custom_roles.is_system (boolean) to mark Coder-managed roles.
  • Added custom_roles.member_permissions (jsonb) to store member-scoped permissions (resources owned by the user).
  • Added organizations.workspace_sharing_disabled (boolean) as a per-org setting.

2. Migration + DB Trigger

  • Migration creates placeholder organization-member system roles for all existing organizations (with empty permissions).
  • A database trigger creates the placeholder system role for every newly inserted organization.
  • Permissions are populated using a source-of-truth function.

3. Permission Generation (code is source of truth)

  • rbac.OrgMemberPermissions(workspaceSharingDisabled bool) in coderd/rbac/roles.go is the source of truth.
  • It generates:
    • Org-scoped permissions (e.g. org reads, role listing, and sharing-related reads when sharing is enabled).
    • Member-scoped permissions via member_permissions.
  • When workspace_sharing_disabled=true, it includes a negated permission for workspace:share to disable sharing.

4. Org Creation (enterprise)

  • On org creation, the DB trigger inserts an empty placeholder system role row.
  • enterprise/coderd/organizations.go calls rolestore.ReconcileOrgMemberRole() (with a system-restricted auth context) to populate the role’s permissions immediately.

5. Startup Reconciliation (system role maintenance)

  • At startup, coderd/coderd.go calls rolestore.ReconcileSystemRoles().
  • ReconcileSystemRoles:
    • Acquires a PostgreSQL advisory lock (LockIDReconcileSystemRoles) to prevent concurrent reconciles across instances.
    • Iterates organizations and ensures the organization-member system role exists.
    • Compares stored vs expected permissions using set-based comparison (rbac.PermissionsEqual) and updates only when needed.
    • Treats missing roles as a critical condition (logs loudly and attempts to recreate as a last resort).

Key Design Decisions

Decision Rationale
Permissions remain the source of truth in code Avoids duplicating complex, evolving permission logic in the DB.
Introduce database-backed system roles is_system=true allows storing customized per-org roles while keeping them hidden from user-facing role CRUD.
Startup reconciliation Ensures new RBAC resources and org settings are reflected in stored permissions over time.
Set-based comparison Avoids unnecessary DB writes when permission sets are unchanged.
Blocking advisory lock Ensures safe reconciliation in multi-instance / rolling-upgrade deployments.

@github-actions github-actions bot added the stale This issue is like stale bread. label Jan 1, 2026
@github-actions github-actions bot closed this Jan 5, 2026
@geokat geokat reopened this Jan 5, 2026
@geokat geokat force-pushed the geokat/internal-1073-make-org-member-role-customizable-per-org branch from 5a38c10 to 06f14ee Compare January 5, 2026 16:31
Copy link
Member

@Emyrk Emyrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there no way to update the org setting at this moment?

@geokat geokat removed the stale This issue is like stale bread. label Jan 5, 2026
@geokat
Copy link
Contributor Author

geokat commented Jan 5, 2026

@Emyrk

Is there no way to update the org setting at this moment?

You mean updating the workspace sharing setting? That functionality is implemented in the second PR (#21376).
I split the work into two PRs to make review easier (sorry for the confusion).

@Emyrk
Copy link
Member

Emyrk commented Jan 6, 2026

You mean updating the workspace sharing setting? That functionality is implemented in the second PR (#21376). I split the work into two PRs to make review easier (sorry for the confusion).

Perfect!

@geokat geokat force-pushed the geokat/internal-1073-make-org-member-role-customizable-per-org branch 12 times, most recently from 7f9a717 to 3b8fdc6 Compare January 11, 2026 02:09
@geokat geokat requested a review from Emyrk January 11, 2026 19:19
Copy link
Member

@Emyrk Emyrk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder to rebase (or merge idc) right before merging to handle any migration number conflicts.

geokat added 26 commits January 12, 2026 17:57
@geokat geokat force-pushed the geokat/internal-1073-make-org-member-role-customizable-per-org branch from ba2c7a9 to 5467cd9 Compare January 13, 2026 02:02
@geokat geokat merged commit cc2efe9 into main Jan 13, 2026
32 checks passed
@geokat geokat deleted the geokat/internal-1073-make-org-member-role-customizable-per-org branch January 13, 2026 02:19
@github-actions github-actions bot locked and limited conversation to collaborators Jan 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement organization "disable workspace sharing" option

3 participants