In quasi ogni pannello di amministrazione c'è una proprietà che significa, allo stesso tempo, usare il prodotto, registrare persone, concedere privilegi e attraversare confini. Di solito si chiama admin. È comoda fino al giorno in cui qualcuno chiede: un amministratore del cliente può creare un altro amministratore uguale a lui?
La separazione dei compiti parte dall'idea che certe autorità non dovrebbero andare insieme solo per comodità. Nel catalogo, questo si traduce in permission distinte:
users.write -- registrare persone
authz.roles.assign_tenant -- assegnare role del tenant
authz.grants.direct -- concedere eccezioni temporanee
platform.tenant.bypass -- attraversare i tenantPerché “concedi solo ciò che hai” non funziona
La regola più comune è: un utente può concedere solo le permission che ha lui stesso. Sembra sicura e ha due difetti.
Il primo è l'auto-replicazione. Se l'amministratore possiede tutte le permission di admin, il subset check gli permette anche di concedere admin a chiunque. La policy “l'admin del tenant registra persone e assegna ruoli operativi, ma non crea un altro admin” semplicemente non è esprimibile.
Il secondo è l'inverso: la regola obbliga chi ha solo bisogno di concedere una capacità a possederla. Un responsabile che distribuisce l'accesso ai microdati finisce per leggere i microdati, senza alcun motivo per farlo.
La matrice di autorità
La via d'uscita è modellare direttamente l'autorità di concessione:
role_grantable(grantor_role, target_role)
tenant_admin -> viewer
tenant_admin -> analyst
platform_admin -> tenant_admin
platform_admin -> viewer
platform_admin -> analystNota cosa manca: tenant_admin -> tenant_admin. La regola di business smette di essere l'effetto collaterale di un confronto tra insiemi e diventa una riga ispezionabile. Per i grant diretti vale lo stesso ragionamento, con un'altra matrice:
permission_grantable(grantor_role, permission_code)
tenant_admin -> analysis.run
tenant_admin -> data.query
tenant_admin -> content.shareE sopra le due matrici c'è un hard deny: nessuna autorità di tenant concede permission di piattaforma, indipendentemente da quello che dice la matrice. Se qualcuno la configura male, il confine resta in piedi.
Il predicato, con le parentesi al loro posto
can_assign(actor, target, role) :=
(
tenant_branch(actor, target)
OR platform_branch(actor)
)
AND role_is_grantable(actor, role)
AND target_role_content_is_allowed(actor, role)Sembra pedanteria, ma qui la precedenza è sicurezza. Senza parentesi, A OR B AND C AND D diventa A OR (B AND C AND D), e qualsiasi attore che soddisfi il ramo di tenant salta i controlli della matrice. È un bypass completo nascosto in un operatore. L'implementazione deve rispecchiare la formula, o usare early return equivalenti che non lascino dubbi.
L'ultimo termine, target_role_content_is_allowed, conta quando il tenant può creare role propri: la validazione guarda il contenuto del role, non il nome. Un role custom chiamato “Lettore” che contiene authz.roles.assign_tenant non è un lettore.
Registrare non è concedere
POST /users -- crea l'identità
POST /users/{id}/roles -- concede autoritàDue endpoint, due servizi, due tracce. Unirli in un form di “nuovo utente” con un menu a tendina dei role trasforma ogni registrazione in una concessione, e l'audit deve poi separare ciò che l'architettura ha unito.
Bloccare l'autoassegnazione, actor == target, è una salvaguardia ragionevole. Ma è solo questo: il controllo vero è la matrice. Se la matrice permette l'escalation, impedire a qualcuno di farla su se stesso lo costringe solo a chiederla a un collega.
Grant diretti con scadenza
Le eccezioni temporanee esistono, ed è meglio modellarle che fingere che non ci siano. Ma il grant diretto non può diventare una seconda implementazione incontrollata dell'RBAC. Le regole che seguo:
expires_atobbligatorio. Non esistono grant eterni.- Motivazione obbligatoria, salvata insieme al grant.
- Concessione registrata nell'audit nella stessa transazione.
- La scadenza entra nel calcolo della freschezza della sessione.
- Ricorrenza monitorata: lo stesso grant rinnovato ogni mese è un role che nessuno ha voluto creare.
- Permission di piattaforma non delegabili tramite alcun meccanismo di tenant.
Provenienza
Per ogni permission effettiva, il sistema deve saper dire da dove arriva: role di sistema, role custom o grant diretto, chi l'ha concessa, quando e fino a quando. Questo trasforma “perché questa persona riesce a fare questa cosa?” in una query, e non in un'indagine manuale sui log.
Questo testo fa parte di una serie che comincia con Autorizzazione in quattro domande.