Skip to content

Conversation

@geokat
Copy link
Contributor

@geokat geokat commented Dec 16, 2025

wip: the PR is currently in draft mode to discuss the migration of the org-member role to the DB

Migrating Org-Member Role to Database

Problem

The organization-member role is hardcoded. This makes it easy to keep it in sync with resource types supported by Coder, but prevents per-org customization (e.g., workspace_sharing_disabled).

Solution: Database-Backed System Roles

Store organization-member roles per-org in the custom_roles table with an is_system flag. Create the roles during org creation and reconcile them at startup to keep permissions in sync with the codebase.

  1. Schema Changes
  • Added is_system boolean column to the custom_roles table — marks Coder-managed roles
  • Added member_permissions — for member-scoped permissions (resources owned by the user)
  • Added workspace_sharing_disabled to organizations — per-org setting
  1. Migration
  • Brings existing organizations up to date by creating empty placeholder organization-member system roles for all existing organizations
  • Permissions are empty initially (eventually backfilled by the startup reconciliation hook)
  1. Permission Generation (coderd/rbac/roles.go)
  • OrgMemberPermissions(workspaceSharingDisabled bool) is the source of truth
  • Generates org-level and member-level permissions dynamically
  • Respects the workspace sharing setting (adds negation for ActionShare when disabled)
  1. Org Creation Hook (enterprise/coderd/organizations.go)
  • When a new org is created, the organization-member role is created in the same transaction
  • Uses OrgMemberPermissions(false) since new orgs have sharing enabled by default
  1. Startup Reconciliation (coderd/systemroles.go)
  • ReconcileOrgMemberRoles() runs at startup with an advisory lock
  • Compares expected vs stored permissions using set-based comparison
  • Updates roles if permissions differ (handles RBAC resource changes)
  • Creates missing roles (handles rolling upgrade edge cases)
  • Blocking lock ensures each instance reconciles, catching orgs created by old instances

Key Design Decisions

Decision Rationale
Permissions as source of truth in code Moving them to the DB is a big project; duplicating them in the DB is error-prone
Introduce system roles is_system = true makes them transparent to the CRUD API
Startup reconciliation Resource type changes in code are propagated to roles stored in the DB
Set-based comparison Avoids unnecessary writes when permissions are unchanged
Blocking advisory lock Ensures correctness during rolling upgrades

closes: coder/internal#1073

@bpmct bpmct added the doc-check Assign this label to PRs to check for any doc changes. label Dec 16, 2025
@geokat
Copy link
Contributor Author

geokat commented Dec 16, 2025

@aslilac @Emyrk More work is needed to address all of the coder/internal#1073 requirements, but I wanted to run this approach for migrating the built-in role by you early on. The code isn't ready for review yet, but it should demonstrate the pros and cons of the chosen approach.

Other alternatives I can see--using the DB as the source of truth for permissions or duplicating them in the DB--seem to be more involved and error-prone than running reconciliation at startup. But please let me know if you disagree or have other ideas.

@geokat geokat force-pushed the geokat/internal-1073-organization-disable-workspace-sharing-option branch 2 times, most recently from 0e2cd81 to 3f8082b Compare December 17, 2025 16:18
@geokat geokat force-pushed the geokat/internal-1073-organization-disable-workspace-sharing-option branch from 3f8082b to 7a3ffb0 Compare December 17, 2025 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc-check Assign this label to PRs to check for any doc changes.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement organization "disable workspace sharing" option

3 participants