modele-permissions-rbac.md 6011 octets

Comprendre le modèle de permissions RBAC de gitrust

Ce que vous allez comprendre

  • Analyser comment les 4 rôles (Reader, Developer, Maintainer, Owner) s'appliquent à un dépôt.
  • Évaluer la différence entre accès direct et accès via équipe, et comment le rôle effectif est calculé.
  • Comparer le modèle gitrust avec les ACL Unix (user/group/other).

Le problème concret

Vous avez un dépôt privé alice/myproject. Alice souhaite que :

  • Bob puisse lire le code (pas pusher).
  • L'équipe backend-devs puisse pusher.
  • Charlie, membre de backend-devs, soit quand même restreint en lecture seule.

Sans un modèle de permissions précis, impossible de savoir quel accès Charlie obtient réellement.

L'analogie

Le modèle gitrust ressemble aux ACL Unix, mais à deux dimensions.

Sur Unix, chaque fichier a trois niveaux : user (propriétaire), group, other. Le niveau effectif est le premier qui correspond, dans cet ordre.

Gitrust fonctionne de manière similaire, avec deux axes :

Axe UnixAxe gitrust
useraccès individuel direct (resource_shares)
groupaccès via équipe (team_repository_access)
otheraccès public (resources.is_public)

La différence clé : gitrust prend le maximum entre l'accès individuel et l'accès équipe, pas le premier correspondant.

Le modèle

Les 4 rôles et leur hiérarchie

graph LR
    R[Reader] --> D[Developer]
    D --> M[Maintainer]
    M --> O[Owner]
RôlePermissions cumulatives
ReaderClone, browse (lecture seule)
DeveloperReader + push sur branches non protégées
MaintainerDeveloper + gestion dépôt (settings, collaborateurs, labels, protection de branches)
OwnerMaintainer + suppression, transfert

La hiérarchie est strictement cumulative : un Maintainer a toutes les permissions d'un Developer, qui a toutes celles d'un Reader.

Sources d'accès

Un utilisateur peut accéder à un dépôt par trois chemins indépendants :

flowchart TD
    User[Utilisateur]

    subgraph "Accès individuel"
        Owner["Owner (resources.owner_id)"]
        Share["resource_shares\n(read / write / admin)"]
        Public["Accès public\n(resources.is_public = true)"]
    end

    subgraph "Accès équipe"
        TeamMember["team_members\n(l'utilisateur est membre)"]
        TeamAccess["team_repository_access\n(permission de l'équipe sur le dépôt)"]
    end

    Effective["Rôle effectif\n= max(individuel, équipe)"]

    User --> Owner
    User --> Share
    User --> Public
    User --> TeamMember
    TeamMember --> TeamAccess

    Owner -->|"owner (full)"| Effective
    Share -->|"read/write/admin"| Effective
    Public -->|"read only"| Effective
    TeamAccess -->|"read/write/admin"| Effective

Calcul du rôle effectif

Le rôle effectif d'un utilisateur sur un dépôt est le maximum de tous ses accès :

rôle_effectif = max(
    owner_directement ? owner : none,
    resource_shares.permission_level,
    is_public ? read : none,
    max(team_repository_access.permission pour chaque équipe dont il est membre)
)

Exemple — réponse au problème concret :

UtilisateurAccès individuelAccès via équipeRôle effectif
Aliceownerowner
Bobread (partagé directement)read
Charlieread (partagé directement)write (via backend-devs)write (max)
Dave (membre backend-devs)writewrite

Charlie obtient write car le max(read, write) = write. Pour restreindre Charlie en lecture seule malgré son appartenance à backend-devs, il faudrait le retirer de l'équipe ou utiliser une protection de branche.

Arbre de décision

flowchart TD
    Q1{L'utilisateur\nest-il owner ?}
    Q1 -->|Oui| Full[Accès owner\n(toutes permissions)]
    Q1 -->|Non| Q2{resource_shares\nexiste pour cet utilisateur ?}
    Q2 -->|Oui| Q3{Dépôt public ?}
    Q2 -->|Non| Q3
    Q3 -->|Oui| Q4[Accès public = read]
    Q3 -->|Non| Q4b[Pas d'accès public]
    Q4 --> Q5{Membre d'une équipe\navec accès au dépôt ?}
    Q4b --> Q5
    Q5 -->|Oui| Calc["Rôle = max(share, public, team)"]
    Q5 -->|Non| Calc2["Rôle = max(share, public)"]
    Calc --> Result[Rôle effectif final]
    Calc2 --> Result

Mapping vers les niveaux ResourceService

ResourceService (rustwarden-core) utilise les niveaux "read", "write", "admin". Le mapping gitrust est :

Niveau ResourceServiceRôle gitrust
"read"Reader
"write"Developer
"admin"Maintainer
owner (ownership directe)Owner

Alternatives et compromis

Pourquoi pas un modèle RBAC pur avec des rôles nommés par dépôt ? Gitea/Forgejo utilisent des rôles nommés (reporter, developer, maintainer). Gitrust utilise des niveaux numériques (read < write < admin) héritables, ce qui simplifie le calcul du maximum mais rend impossible l'attribution de permissions non hiérarchiques (ex : « peut créer des issues mais pas pusher »).

Pourquoi pas des ACL par fichier ? La granularité fichier (comme dans Perforce) crée une complexité de maintenance considérable pour des équipes de 3-20 personnes. Les protections de branches couvrent 90 % des besoins de contrôle granulaire.

Vérifier votre compréhension

  1. Alice est owner de myrepo. Elle partage le dépôt avec Bob en read. L'équipe ops a accès en admin. Bob rejoint ops. Quel est le rôle effectif de Bob ?

  2. Un visiteur anonyme accède à un dépôt dont resources.is_public = true. Peut-il pusher ? Pourquoi ?

Pour aller plus loin