Centralize membership management into a dedicated package to fix leaky relations
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 48/100
- Tipo di issue
- Refactoring
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Tranquilla
- Stack tecnologico
- go
- Ambito
- authorization, backend
Direzione di ricerca
Inizia dall'interfaccia proposta in core/membership/service.go e segui i nove call site elencati per organizzazioni, gruppi e serviceuser, incluse le relative operazioni esistenti di policy e relazione. Il lavoro è completo quando tali operazioni usano il servizio membership, le letture degli elenchi passano attraverso le policy, le relazioni di gruppo richieste rimangono presenti e i controlli dei permessi e le relazioni gerarchiche restano invariati.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Problem
When users are added to organizations or groups, we create both a policy (role binding) and a direct SpiceDB relation. The direct relations are redundant and cause bugs - for example, an org creator demoted from owner to member retains owner permissions because their direct owner relation persists.
Current State vs Proposed
| Resource | Action | Role (via policy) | Direct Relation (current) | Direct Relation (proposed) |
|---|---|---|---|---|
| Organization | Add user as owner | Owner (app_organization_owner) |
organization#owner@user |
None |
| Organization | Add user as admin | Admin (app_organization_manager) |
organization#member@user |
None |
| Organization | Add user as member | Member (app_organization_viewer) |
organization#member@user |
None |
| Organization | Add serviceuser | (same as user) | organization#member@serviceuser |
None |
| Organization | Add group | - | organization#member@group#member |
Keep |
| Group | Add user as owner | Team Owner (app_group_owner) |
group#owner@user |
None |
| Group | Add user as member | Team Member (app_group_member) |
group#member@user |
Keep |
| Project | Add user as owner | Project Owner (app_project_owner) |
None | None |
| Project | Add user as manager | Project Manager (app_project_manager) |
None | None |
| Project | Add user as viewer | Project Viewer (app_project_viewer) |
None | None |
Project is the clean model - only policies, no direct relations. We want org and group to follow this pattern.
Why keep these relations?
| Relation | Reason |
|---|---|
organization#member@group#member |
Gives group members automatic org visibility |
group#member@user |
Needed for SpiceDB to resolve group-level policies |
Recommended Approach: Membership Package
Instead of modifying the SpiceDB schema directly, introduce a membership package that centralises all policy and relation management. This gives us immediate bug fixes without schema changes or data migration.
Why this approach
- Fixes bugs now — reads move to policy-based listing immediately, so stale direct relations stop causing permission issues
- Single place to debug — all membership logic (add, remove, role change, listing) lives in one package instead of being scattered across org, group, and project services. When the next issue shows up, there's one place to look and one place to fix
- No migration needed — no SpiceDB schema changes, no data cleanup, single code deploy
- Easy rollback — if something breaks, revert one package
- Sets up full cleanup — once all call sites go through the package, removing direct relations becomes a one-file change inside the package (no external callers affected)
Package interface
// core/membership/service.go
// SetRole - add or change a principal's role on a resource
// Creates policy + direct relation (for backward compat)
func (s *Service) SetRole(ctx, resourceID, resourceType, principalID, principalType, roleID string) error
// Remove - remove a principal from a resource
// Deletes policies + direct relation
func (s *Service) Remove(ctx, resourceID, resourceType, principalID, principalType string) error
// ListByResource - list principals on a resource (always via policies)
func (s *Service) ListByResource(ctx, resourceID, resourceType string) ([]policy.Policy, error)
// ListByPrincipal - list resources a principal belongs to (replaces LookupResources)
func (s *Service) ListByPrincipal(ctx, principalID, principalType, resourceType string) ([]string, error)
Call sites to update (9 total)
| # | Current | Replace with |
|---|---|---|
| 1 | organization.AddMember (policy + direct relation) |
membership.SetRole |
| 2 | organization.ListByUser (SpiceDB LookupResources) |
membership.ListByPrincipal |
| 3 | organization.RemoveUsers (policy delete + relation delete) |
membership.Remove |
| 4 | group.addMember (policy + direct relation) |
membership.SetRole |
| 5 | group.addOwner (policy + direct relation) |
membership.SetRole |
| 6 | group.ListByUser (SpiceDB LookupResources) |
membership.ListByPrincipal |
| 7 | group.RemoveMembers (relation delete) |
membership.Remove |
| 8 | serviceuser.Create (direct member relation on org) |
membership.SetRole |
| 9 | organization.SetMemberRole (already policy-only) |
membership.SetRole |
What does NOT change
- Hierarchy relations (
project#org,group#org,serviceuser#org) — these are entity links, not membership - Policy/rolebinding system (
policy.AssignRole) — stays in policy service - Role definitions (
role.createRolePermissionRelation) — stays in role service - Platform relations (user sudo) — stays in user service
- Permission checks (CheckPermission, BatchCheck) — unchanged
Impact on Project Inherited Permissions
Projects inherit permissions from organizations via the org relation:
permission delete = org->project_delete + granted->app_project_administer + ...
permission project_delete = ... + granted->app_organization_administer + owner
The app_organization_owner role includes app_organization_administer permission, so the inheritance still works via the policy path:
project#delete
→ org#project_delete
→ granted->app_organization_administer ✅ (owner role has this)
| User State | Current Access | After Change |
|---|---|---|
| Has policy + relation | ✅ via both paths | ✅ via policy path |
| Has only policy | ✅ via policy | ✅ via policy |
| Has only relation (bug state) | ✅ via relation | ❌ loses access |
The third case (only relation, no policy) is the bug we're fixing.
Benefits
| Benefit | Description |
|---|---|
| Fixes permission bug | Role changes will actually work - no more stale permissions from direct relations |
| Code simplicity | One package for all membership operations instead of scattered logic |
| SDK simplicity | SDK only deals with role-based operations (assign role, change role, remove role). Policies and relations are internal constructs - SDK consumers don't need to know about them |
| Consistency | Same pattern for org, group, and project membership |
| Single source of truth | Reads always use policies - easier to debug and audit |
| No migration risk | No schema changes, no SpiceDB data migration needed |
Potential Downsides
| Concern | Impact | Mitigation |
|---|---|---|
| Two systems remain | Direct relations still created alongside policies | Package ensures they stay in sync; can remove later |
| Package enforcement | New code must use the package | Code review + documentation |
Future: Full Cleanup (Phase 2)
Once the membership package is in place and stable, removing direct relations becomes a contained change:
- Remove
relationServicecalls inside the membership package - Update SpiceDB schema (remove
+ owner, narrowmember) - Run migration to clean up stale relations
No external callers change — only the internals of the membership package.
See #1479 for the additional improvement of using computed permissions for group membership.
- Lingua principale
- Go
- Stelle
- 344
- Fork
- 47
- Merge medio
- 4g 4h
- PR unite (30g)
- 26
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di raystack/frontier
-
enhancement go
Difficoltà 4/5 3-5 giorni Idoneità per principianti 62/100
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 58/100
-
authz bug
Difficoltà 3/5 1-2 giorni Idoneità per principianti 70/100
-
Add OpenTelemetry tracing Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 45/100
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 38/100
Tutte le issue di raystack/frontier
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
bazel-contrib/rules_go#4726 · 1 commento ·
-
area/auto-scaling area/monitoring area/ops-productivity kind/enhancement
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
Type/Improvement
Difficoltà 1/5 1-3 ore Idoneità per principianti 90/100
OpenNSW/nsw-srilanka#522 ·
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 92/100