Les applications sont de plus en plus complexes. Chaque utilisateur peut interagir avec des ressources variées et des données parfois sensibles. Dans ce contexte, les contrôles d’accès (ACL) deviennent essentiels pour définir qui peut accéder à quoi et selon quelles conditions.
Les systèmes traditionnels comme le RBAC (Role-Based Access Control) offrent une approche simplifiée mais parfois rigide de la gestion des accès. Faire évoluer un système de permissions existant afin de déléguer des droits sur certaines ressources est souvent difficile, voire risqué…
SpiceDB, inspiré par le modèle Zanzibar de Google, permet de définir des relations complexes entre les utilisateurs et les ressources tout en rendant possible un contrôle d’accès granulaire adapté aux environnements dynamiques.
Dans cet article, nous allons explorer les concepts fondamentaux des ACL et découvrir comment SpiceDB peut transformer la gestion des permissions dans vos applications.
L’approche RBAC (Role Based Access Control) se base sur des rôles prédéfinis assignés aux utilisateurs. Les permissions sont accordées selon le rôle de l’utilisateur dans l’organisation.
Les cas d’usage typiques : systèmes d’entreprise traditionnels, applications avec hiérarchies claires
- Simplicité : logique intuitive facile à comprendre pour les administrateurs. La hiérarchie de rôles est claire et lisible.
- Performance : pas de calculs complexes à l’exécution. Un rôle est une association à des permissions (lookup table) et peut être mis en cache facilement.
- Standard : écosystème riche avec des standards bien établis et de nombreuses implémentations.
Quand les besoins d’accès deviennent granulaires, RBAC nécessite de créer de nombreux rôles spécialisés.
Prenons l’exemple de la gestion des cours dans une université avec des notions de matières, classes et semestres.
Nous avons besoin de définir des accès spécifiques selon les critères suivants:
- Professeur de Math, Classe A, Semestre 1
- Professeur de Math, Classe B, Semestre 1
- Professeur de Physique, Classe A, Semestre 1
- Professeur de Physique, Classe A, Semestre 2
Vous devez créer les rôles suivants:
- Prof_Math_ClasseA_S1
- Prof_Math_ClasseB_S1
- Prof_Physique_ClasseA_S1
- Prof_Physique_ClasseA_S2
Combinaisons : Matières × Classes × Semestres × Permissions spéciales
Avec 10 matières, 20 classes, 2 semestres = 400 rôles potentiels
La croissance du nombre de rôles est exponentielle : O(n^k)
où k = nombre de dimensions.
L’approche ReBAC (Relation Based Access Control) se base sur les relations contextuelles entre les entités (utilisateurs, ressources, organisations). Les permissions dépendent des relations spécifiques.
Les cas d’usage typiques : réseaux sociaux, plateformes collaboratives, systèmes où les relations changent fréquemment.
Pour reprendre l’exemple de l’université, on modéliserait les relations suivantes:
Enseignants:
- ProfMartin ENSEIGNE Math
- ProfDubois ENSEIGNE Physique
Affectations:
- ProfMartin ASSIGNÉ ClasseA
- ProfMartin ASSIGNÉ ClasseB
- ProfDubois ASSIGNÉ ClasseA
Contexte temporel:
- ClasseA ACTIVE_PENDANT Semestre1
- ClasseA ACTIVE_PENDANT Semestre2
- ClasseB ACTIVE_PENDANT Semestre1
Exemple de définition d’une permission (pseudo code) :
- ALLOW si (User ENSEIGNE Matière) ET (User ASSIGNÉ Classe) ET (Classe ACTIVE_PENDANT Semestre_actuel)
La croissance des règles est linéaire: O(n)
- Basé sur les relations : utilise les relations pour définir les permissions. Cela permet de spécifier des règles d’accès en fonction des relations entre les entités (utilisateurs, groupes, ressources, etc.).
- Granularité : permet de définir des permissions très fines et spécifiques, ce qui le rend adapté aux applications avec des besoins complexes et dynamiques en matière de contrôle d’accès.
- Flexibilité : Le modèle déclaratif utilisé peut être adapté à une grande variété de scénarios d’accès. Les permissions évoluent avec les relations.
- Complexité : En raison de sa granularité et de sa flexibilité, cela peut nécessiter plus de temps pour maîtriser les concepts et être configuré correctement.
- Performances : Plus complexe à l’exécution car il faut parcourir les relations et les permissions (notion de graphe)
Maintenant que nous avons établi les avantages du ReBAC, explorons concrètement comment SpiceDB implémente ces concepts à travers son architecture et ses fonctionnalités.
SpiceDB utilise un schéma pour définir les relations et les permissions. Il comprend trois composantes principales :
- Définition : Représente typiquement une entité dans votre système. Exemple: vous pourriez avoir un utilisateur et un document.
- Relation : Une relation décrit comment deux définitions sont liées. Exemple: un utilisateur peut avoir la relation utilisateur et un document.
- Permission : Une permission représente typiquement une action dans votre système. Exemple, un utilisateur avec la relation “editor” d’un document peut avoir la permission “edit” pour modifier un document
Pour exprimer les relations et permissions, vous pouvez utiliser le modèle mental suivant :
Is this actor allowed to perform this action on this resource?
/¯¯¯¯¯¯¯/ /¯¯¯¯¯¯¯¯¯/ /¯¯¯¯¯¯¯¯¯¯¯/
object permission or object
(subject) relation (resource)
ReBAC (Relation Based Access Control) va plus loin en permettant de modéliser des relations complexes entre les entités et de fournir des permissions granulaires et contextuelles. Les entités peuvent être imbriquées comme un groupe avec des sous groupes.
Exemple de définition de schéma pour ces relations et permissions
definition user {}
definition document {
relation editor: user
relation viewer: user
permission edit = editor
permission view = viewer
}
Une fois le schéma défini avec ses entités et permissions, SpiceDB utilise une notion spécifique pour représenter les relations
resource subject
ID type
\ˍˍˍˍˍ\ \ˍˍ\
document:readme#editor@user:emilia
/¯¯¯¯¯¯¯/ /¯¯¯¯¯/ /¯¯¯¯¯/
resource permission subject
type or relation ID
Ici, l’utilisateur avec ID ‘emilia’ a la permission ‘edit’ sur le document avec ID ‘readme’ car elle a la relation ‘editor’
Avec la gestion des droits déportée dans SpiceDB il n’est plus nécessaire de définir une structure de tables dans la base de données de son application.
Les permissions sont maintenant vérifiées via gRPC et HTTP/JSON APIs.
Dans le cas d’une application constituée de microservices, vous pouvez facilement intégrer ce système de permissions.
SpiceDB permet de facilement persister ses données via des “datastore” populaires et éprouvées comme PostgreSQL, MySQL ou encore CockroachDB.
SpiceDB fournit un écosystème d’outils complet avec notamment un environnement de playground
Cet environnement permet de définir, tester et partager vos schémas. Avantage majeur, vous pouvez tester et valider vos données avec des assertions.
Note: il est également possible de tester les schemas en installant le langage Zed(voir la documentation)
Après avoir exploré les concepts théoriques et l’outillage disponible, mettons en pratique avec un cas d’usage concret du domaine médical.
Les spécifications (simples) seraient :
- un patient est rattaché à une infirmière et un médecin
- un patient est associé à un dossier médical et des mesures de santé (ex: poids, pression artérielle…)
- un médecin peut modifier le dossier médical d’un patient ainsi que les mesures de santé
- une infirmière ne peut modifier que les mesures de santé d’un patient
graph TD
%% Définition des entités
User[👤 User]
Patient[🏥 Patient]
VitalSigns[📊 Vital Signs]
MedicalInfo[📋 Medical Info]
%% Relations directes
User -->|doctor| Patient
User -->|nurse| Patient
Patient -->|patient| VitalSigns
Patient -->|patient| MedicalInfo
%% Permissions sur Patient
Patient_UpdateMedicalInfo[📝 update_medical_info]
Patient_UpdateVitalSigns[🔧 update_vital_signs]
%% Permissions sur Vital Signs et Medical Info
VitalSigns_Update[✏️ update]
MedicalInfo_Update[✏️ update]
%% Règles de permissions pour Patient
User -.->|doctor only| Patient_UpdateMedicalInfo
User -.->|doctor| Patient_UpdateVitalSigns
User -.->|nurse| Patient_UpdateVitalSigns
%% Règles de permissions héritées
Patient_UpdateVitalSigns -.->|inherited via patient relation| VitalSigns_Update
Patient_UpdateMedicalInfo -.->|inherited via patient relation| MedicalInfo_Update
%% style des liens
linkStyle 0 stroke:#01579b,stroke-width:3px
linkStyle 1 stroke:#01579b ,stroke-width:3px
linkStyle 2 stroke:#01579b ,stroke-width:3px
linkStyle 3 stroke:#01579b ,stroke-width:3px
linkStyle 4 stroke:#4a148c ,stroke-width:3px
linkStyle 5 stroke:#4a148c ,stroke-width:3px
linkStyle 6 stroke:#4a148c ,stroke-width:3px
linkStyle 7 stroke:#4a148c ,stroke-width:3px
linkStyle 8 stroke:#4a148c ,stroke-width:3px
%% Style des nœuds
classDef entityClass fill:#e1f5fe,stroke:#01579b,stroke-width:2px
classDef permissionClass fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
classDef relationClass stroke-dasharray: 5 5
class User,Patient,VitalSigns,MedicalInfo entityClass
class Patient_UpdateMedicalInfo,Patient_UpdateVitalSigns,VitalSigns_Update,MedicalInfo_Update permissionClass
definition user {}
definition patient {
relation doctor: user
relation nurse: user
permission update_medical_info = doctor
permission update_vital_signs = doctor + nurse
}
definition vital_signs {
relation patient: patient
permission update = patient->update_vital_signs
}
definition medical_info {
relation patient: patient
permission update = patient->update_medical_info
}
Définir une relation consiste en une simple ligne
// Relations Patient-Doctor
patient:patient_01#doctor@user:dr_martin
patient:patient_02#doctor@user:dr_julie
// Relations Patient-Nurse
patient:patient_01#nurse@user:nurse_bob
patient:patient_02#nurse@user:nurse_sara
// Relations Vital Signs-Patient
vital_signs:vs_01#patient@patient:patient_01
vital_signs:vs_02#patient@patient:patient_02
// Relations Medical Info-Patient
medical_info:mi_01#patient@patient:patient_01
medical_info:mi_02#patient@patient:patient_02
SpiceDB met à disposition des outils de validation pour vérifier nos permissions (aka: assertions comme dans nos tests unitaires)
assertTrue:
# Doctors can update medical info for their patients
- patient:patient_01#update_medical_info@user:dr_martin
- patient:patient_02#update_medical_info@user:dr_julie
# Doctors can update vital signs for their patients
- patient:patient_01#update_vital_signs@user:dr_martin
- patient:patient_02#update_vital_signs@user:dr_julie
# Nurses can update vital signs for their patients
- patient:patient_01#update_vital_signs@user:nurse_bob
- patient:patient_02#update_vital_signs@user:nurse_sara
# Doctors can update vital signs records directly
- vital_signs:vs_01#update@user:dr_martin
- vital_signs:vs_02#update@user:dr_julie
# Nurses can update vital signs records directly
- vital_signs:vs_01#update@user:nurse_bob
- vital_signs:vs_02#update@user:nurse_sara
# Doctors can update medical info records directly
- medical_info:mi_01#update@user:dr_martin
- medical_info:mi_02#update@user:dr_julie
assertFalse:
# Nurses cannot update medical info for any patient
- patient:patient_01#update_medical_info@user:nurse_bob
- patient:patient_02#update_medical_info@user:nurse_sara
# Nurses cannot update medical info records directly
- medical_info:mi_01#update@user:nurse_bob
- medical_info:mi_02#update@user:nurse_sara
# Cross-patient access denied - doctors
- patient:patient_01#update_medical_info@user:dr_julie
- patient:patient_02#update_medical_info@user:dr_martin
- patient:patient_01#update_vital_signs@user:dr_julie
- patient:patient_02#update_vital_signs@user:dr_martin
# Cross-patient access denied - nurses
- patient:patient_01#update_vital_signs@user:nurse_sara
- patient:patient_02#update_vital_signs@user:nurse_bob
# Cross-patient access denied - vital signs
- vital_signs:vs_01#update@user:dr_julie
- vital_signs:vs_01#update@user:nurse_sara
- vital_signs:vs_02#update@user:dr_martin
- vital_signs:vs_02#update@user:nurse_bob
# Cross-patient access denied - medical info
- medical_info:mi_01#update@user:dr_julie
- medical_info:mi_02#update@user:dr_martin
Nous avons donc la possibilité de vérifier via des tests automatisés que notre gestion des permissions correspond toujours à nos spécifications.
Vous pouvez retrouver cette exemple, avec les schémas associés et tests dans le playground de SpiceDB.
L’exemple précédent illustrait des permissions statiques. Explorons maintenant comment SpiceDB gère des permissions dynamiques qui varient selon le contexte.
Imaginons maintenant que les infirmières de garde ne puissent modifier les mesures que pendant les heures non ouvrées.
Il est possible de rajouter du contexte via les Caveats
(voir la documentation).
Cela permet de dynamiser les permissions.
Les infirmières de garde travaillent :
- de 18h à 8h les jours de la semaine
- le samedi et le dimanche toute la journée
Le contexte contiendra l’heure et le jour afin de déterminer si l’accès est autorisé ou non.
caveat emergency_hours(current_hour int, current_day string) {
// Emergency hours: evenings (after 18h), nights (before 8h), and weekends
current_hour < 8 || current_hour >= 18 || current_day == "saturday" || current_day == "sunday"
}
On ajoute cette condition à la permission souhaitée :
definition user {}
definition patient {
relation doctor: user
relation day_nurse: user
relation emergency_nurse: user with emergency_hours
permission update_medical_info = doctor
permission update_vital_signs = doctor + day_nurse + emergency_nurse
}
definition vital_signs {
relation patient: patient
permission update = patient->update_vital_signs
}
definition medical_info {
relation patient: patient
permission update = patient->update_medical_info
}
Ajoutons des infirmières de garde aux patients
// Relations Patient-Emergency Nurse (with caveat context)
patient:patient_01#emergency_nurse@user:nurse_emma[emergency_hours]
patient:patient_02#emergency_nurse@user:nurse_paul[emergency_hours]
Ajoutons des cas de tests en fonction des relations et du contexte
assertTrue:
# previous assertions skipped
# Emergency nurses can update vital signs during emergency hours (evening)
- 'patient:patient_01#update_vital_signs@user:nurse_emma with {"current_hour": 20, "current_day": "monday"}'
- 'patient:patient_02#update_vital_signs@user:nurse_paul with {"current_hour": 22, "current_day": "wednesday"}'
# Emergency nurses can update vital signs during emergency hours (early morning)
- 'patient:patient_01#update_vital_signs@user:nurse_emma with {"current_hour": 6, "current_day": "tuesday"}'
- 'patient:patient_02#update_vital_signs@user:nurse_paul with {"current_hour": 7, "current_day": "friday"}'
# Emergency nurses can update vital signs during weekends
- 'patient:patient_01#update_vital_signs@user:nurse_emma with {"current_hour": 14, "current_day": "saturday"}'
- 'patient:patient_02#update_vital_signs@user:nurse_paul with {"current_hour": 10, "current_day": "sunday"}'
assertCaveated:
# Emergency nurses are caveated for vital signs access
- patient:patient_01#update_vital_signs@user:nurse_emma
- patient:patient_02#update_vital_signs@user:nurse_paul
assertFalse:
# previous assertions skipped
# Emergency nurses cannot update medical info for any patient
- patient:patient_01#update_medical_info@user:nurse_emma
- patient:patient_02#update_medical_info@user:nurse_paul
# Emergency nurses cannot update vital signs during normal working hours
- 'patient:patient_01#update_vital_signs@user:nurse_emma with {"current_hour": 10, "current_day": "monday"}'
- 'patient:patient_02#update_vital_signs@user:nurse_paul with {"current_hour": 14, "current_day": "tuesday"}'
- 'patient:patient_01#update_vital_signs@user:nurse_emma with {"current_hour": 16, "current_day": "wednesday"}'
- 'patient:patient_02#update_vital_signs@user:nurse_paul with {"current_hour": 12, "current_day": "friday"}'
# Nurses cannot update medical info records directly
# Cross-patient access denied - emergency nurses
- 'patient:patient_01#update_vital_signs@user:nurse_paul with {"current_hour": 20, "current_day": "saturday"}'
- 'patient:patient_02#update_vital_signs@user:nurse_emma with {"current_hour": 22, "current_day": "sunday"}'
Nous avons rajouté la possibilité de dynamiquement modifier les permissions en fonction du contexte via quelques lignes de configuration.
Vous pouvez retrouver cet exemple avec les schémas associés et tests dans le playground de SpiceDB
SpiceDB a des atouts pour devenir un outil de gestion de permissions incontournable dans nos stacks applicatives.
Selon moi, sa force principale réside dans son outillage. Cela permet d’avoir une expérience développeur de grande qualité. Cela permet de concevoir et tester ses schémas de façon rapide et efficace. Le site de playground est très bien pensé.
Cette introduction sur SpiceDB s’est focalisée sur ses concepts et fonctionnement.
Une fois votre schéma défini, il ne reste plus qu’à intégrer ce système de permission dans votre application.
De nombreux clients sont disponibles dans vos langages préférés