Un GRANT par objet existant, un Future Grant par type d'objet, et des centaines de lignes dans SHOW GRANTS que plus personne ne lit. C'est le quotidien de tout RBAC Snowflake un peu mûr. Le 10 septembre 2026, Snowflake a rendu Generally Available (GA, la fonctionnalité est activable sur tous les comptes) une réponse nouvelle à ce problème : les Inherited Grants. Je les ai testés sur un vrai compte Snowflake — pas juste lus dans la doc. Verdict : je remplace mes Future Grants. Mais il y a un piège de revoke qui mérite qu'on s'attarde, parce qu'il ne provoque ni erreur, ni log, ni alerte.

Le problème : le coût caché du "grant par objet"
Si vous avez lu mon article sur le RBAC 3 couches — le RBAC, pour role-based access control (le contrôle d'accès par rôles) —, vous connaissez le modèle : des Access Roles qui portent les privilèges techniques, des Functional Roles métier qui en héritent, zéro GRANT direct aux utilisateurs. Sur le papier, c'est propre. En pratique, chaque couple (Access Role, privilege, type d'objet) se traduit par deux statements :
-- Les objets qui existent déjà
GRANT SELECT ON ALL TABLES IN SCHEMA PROD.FINANCE
TO ROLE AR_FIN_READ_PRD;
-- Les objets à venir
GRANT SELECT ON FUTURE TABLES IN SCHEMA PROD.FINANCE
TO ROLE AR_FIN_READ_PRD;
Multipliez par le nombre de schémas, de rôles et de types d'objets (TABLES, VIEWS, DYNAMIC TABLES...), et le catalogue de grants explose. Pire : les deux statements peuvent drifter. Le jour où quelqu'un recrée le ON FUTURE sans le ON ALL (ou l'inverse), certains objets sont couverts, d'autres non, et rien ne le signale. Chaque nouvel objet ajoute aussi une ligne de plus dans les vues d'audit — jusqu'à ce que plus personne ne les lise.
Les Inherited Grants attaquent exactement ça : un seul statement décrit le besoin entier, pour les objets existants ET à venir.
Ce que c'est
Un Inherited Grant est un GRANT posé sur un conteneur — ACCOUNT, DATABASE ou SCHEMA — qui s'applique automatiquement à tous les objets du type précisé, présents et futurs, dans ce conteneur.
Première chose à savoir : c'est un opt-in. Le paramètre est désactivé par défaut (je l'ai vérifié sur mon compte : default = DISABLED) :
-- Un seul paramètre active les deux features de cet article
ALTER ACCOUNT SET FEATURE_RBAC_INHERITED_GRANTS = 'ENABLED';
-- (et ALTER ACCOUNT ... = 'DISABLED' pour revenir en arrière)
La syntaxe :
GRANT INHERITED <privilege> [ , <privilege> ... ]
ON ALL <object_type_plural>
IN { ACCOUNT | DATABASE <db> | SCHEMA <schema> }
TO { ROLE <role> | DATABASE ROLE <db_role> | USER <user> | APPLICATION <app> }
Deux prérequis qui m'ont coûté quelques minutes de débogage :
OWNERSHIPne suffit pas. Un rôle propriétaire du schéma qui tente unGRANT INHERITEDse prend une erreur sèche :
003102 (42501): Grant not executed: Insufficient privileges.
Il faut MANAGE GRANTS sur le conteneur — ou sur un conteneur supérieur. Créer le conteneur, y créer des objets, même en être propriétaire : rien de tout ça n'autorise un Inherited Grant.
- Les privilèges conteneurs restent nécessaires. Un
GRANT INHERITED SELECTsur les tables ne remplace pas leUSAGEsur la base et le schéma — sans eux, la résolution du nom échoue avant même la vérification duSELECT.
Test 1 : remplacer le duo ON ALL + ON FUTURE
Scénario testé sur mon compte : un schéma FINANCE avec des tables existantes, un rôle lecteur FIN_READER, et — le vrai test — une table créée après le grant.
USE ROLE FIN_ACCESS_ADMIN; -- rôle avec MANAGE GRANTS sur le schéma
GRANT INHERITED SELECT ON ALL TABLES IN SCHEMA PROD.FINANCE
TO ROLE FIN_READER;
Puis, côté producteur de données, comme un pipeline le ferait n'importe quel matin :
USE ROLE FINANCE_OWNER; -- le rôle du pipeline, avec CREATE TABLE
CREATE TABLE PROD.FINANCE.ORDERS_2026_10 (ID INT, AMOUNT NUMBER(12,2));
Et côté lecteur, sans qu'aucun nouveau GRANT n'ait été passé :
USE ROLE FIN_READER;
SELECT * FROM PROD.FINANCE.ORDERS_2026_10; -- fonctionne, zéro action
L'audit raconte exactement ce qui se passe. Voici la sortie réelle de SHOW GRANTS ON TABLE — colonnes tronquées aux utiles — après ce test :
privilege | granted_on | granted_to | grantee_name | IS_INHERITED | INHERITED_FROM | INHERITED_FROM_DATABASE | INHERITED_FROM_SCHEMA
----------+------------+------------+---------------+--------------+----------------+-----------------------+----------------------
OWNERSHIP | TABLE | ROLE | FIN_OWNER | false | | |
SELECT | TABLE | ROLE | FIN_READER | true | SCHEMA | PROD | FINANCE
Une ligne SELECT, IS_INHERITED = true, la source du droit tracée jusqu'au conteneur. Là où mon ancien modèle générait un record ON ALL + un record ON FUTURE + potentiellement N records par objet, il reste un grant, une ligne, lisible.
Bonus documenté par Snowflake : moins de records à évaluer, c'est aussi une compilation légèrement plus rapide pour les requêtes qui touchent beaucoup d'objets. Je n'ai pas mesuré ce point précisément, la doc parle d'un gain modeste — je n'inventerai pas un chiffre.
Le piège : le revoke silencieux
C'est LE moment de l'article. Scénario : FIN_READER lit toutes les tables du schéma via un Inherited Grant, et une des tables contient désormais des données de paie qui ne devraient pas être lisibles. Le réflexe de tout administrateur de base de données (DBA) depuis quinze ans :
-- ⛔ Ce que je faisais instinctivement
REVOKE SELECT ON TABLE PROD.FINANCE.SALAIRES FROM ROLE FIN_READER;
Snowflake répond, très poli :
Statement executed successfully. 0 objects affected.
Zéro erreur. Zéro avertissement. Et le lecteur... lit toujours la table. Je l'ai vérifié en conditions propres : après ce "revoke" réussi, le SELECT passe encore. Le droit vient du conteneur, le revoke s'adresse à l'objet — les deux ne se parlent pas. La doc le formule sans détour : il n'existe pas d'exception objet sur un Inherited Grant.
Le seul revoke qui fonctionne :
-- ✅ Le mécanisme du revoke doit matcher celui du grant
REVOKE INHERITED SELECT ON ALL TABLES IN SCHEMA PROD.FINANCE
FROM ROLE FIN_READER;
Statement executed successfully. 1 object(s) affected.
Cette fois le SELECT échoue. Notez le contraste des outputs : 0 objects affected (il ne s'est rien passé) contre 1 object(s) affected (le grant a été retiré). Ce "0 objects affected" est le seul signal que Snowflake vous donnera — apprenez à le lire.
À noter dans le même esprit : les niveaux sont indépendants. Revoker le grant hérité au niveau DATABASE ne retire pas ceux posés au niveau SCHEMA — il faut les revoker séparément (comportement documenté).
Ce qu'une couverture uniforme ne remplace pas
Le Inherited Grant applique le privilège à tout ce qui matche, sans exception possible. C'est sa force et sa limite. Ce que mes tests et la doc m'ont fait intégrer :
Un statement par type d'objet. J'ai accordé TABLES et tenté de lire une vue et une Dynamic Table avec le même rôle :
002003 (42S02): Object 'PROD.FINANCE.V_ORDERS' does not exist
or not authorized.
Normal : GRANT INHERITED SELECT ON ALL TABLES ne couvre ni VIEWS, ni DYNAMIC TABLES, ni ICEBERG TABLES. Il faut un statement par type. Et piège documenté : pour un scanner de conformité censé voir tout un conteneur, un type non couvert est invisible — les grants existants fonctionnent, rien ne signale le trou.
Les rejets explicites. Testés, avec les messages réels :
-- WITH GRANT OPTION
004702 (42601): Invalid option 'GRANT_OPTION' for GRANT inherited grants.
-- OWNERSHIP
004719 (42601): Privilege 'OWNERSHIP' cannot be used for inherited grants.
-- USAGE sur un Streamlit
004719 (42601): Privilege 'USAGE' cannot be used for inherited grants.
-- et pas de SHARE, APPLICATION PACKAGE, INTEGRATION comme cible non plus
Le conteneur est une frontière d'accès — attention aux moves et clones. Créer, déplacer ou cloner un objet dans un conteneur avec Inherited Grants peut changer qui y accède, sans aucun nouveau GRANT. Un clone de schéma cross-conteneur peut donc exposer des objets sensibles à des rôles qui n'auraient jamais dû les voir. Avant de cloner vers un conteneur, regardez ce qu'il hérite.
Le scope ACCOUNT va jusqu'au bout. Un GRANT INHERITED ... IN ACCOUNT atteint toutes les bases du compte, y compris les personal databases — ces espaces de travail privés par utilisateur — et celles créées après. J'ai testé le scope ACCOUNT sur les warehouses (GRANT INHERITED USAGE ON ALL WAREHOUSES IN ACCOUNT), objet de niveau compte, cas légitime. Pour des données, restez au schéma le plus étroit possible.
Les objets exécutables. Un Inherited Grant peut donner USAGE/EXECUTE sur des procédures stockées, tasks, alerts... qui s'exécutent parfois avec les privilèges du propriétaire (owner's rights). Accorder ça en aveugle sur un conteneur, c'est déléguer potentiellement plus que du SELECT. La doc recommande de revoir soigneusement ces types avant tout grant hérité d'exécution.
L'information schema, en transition. Avant l'opt-in au bundle de changements de comportement 2026_07, les vues INFORMATION_SCHEMA ne voient pas les accès portés par un Inherited Grant — un objet accessible n'apparaît pas. Pour auditer : SHOW GRANTS ON <objet>, SHOW INHERITED GRANTS IN <conteneur> ou la vue SNOWFLAKE.ACCOUNT_USAGE.GRANTS_TO_ROLES (les quatre nouvelles colonnes y sont).
La délégation qui va avec : MANAGE GRANTS au niveau conteneur
La GA du 10 septembre couvre deux features. La seconde : MANAGE GRANTS peut désormais être accordé sur une base ou un schéma, pas seulement sur le compte.
GRANT MANAGE GRANTS ON SCHEMA PROD.FINANCE
TO ROLE FIN_ACCESS_ADMIN;
-- redéléguer plus bas exige WITH GRANT OPTION
Le rôle devient administrateur des grants de son conteneur uniquement — directs et hérités — sans toucher à SECURITYADMIN ni ACCOUNTADMIN. Le tableau de la doc résume la frontière :
| Aspect | MANAGE GRANTS ON ACCOUNT |
MANAGE GRANTS sur DATABASE/SCHEMA |
|---|---|---|
| Détenteur typique | SECURITYADMIN |
Rôle délégué (ex. FIN_ACCESS_ADMIN) |
| Périmètre | Tout le compte | Le conteneur uniquement |
| Transfert d'ownership | Oui | Non |
| Hypothèse de confiance | Compte entier | Le conteneur |
Trois avertissements de la doc qui comptent en prod :
- Révoquer
MANAGE GRANTSne défait rien. Les grants créés par l'administrateur délégué survivent à la révocation de son privilège — même avecCASCADE. En cas d'incident : retirer l'autorité, puis passer en revue ce qu'il a accordé. - Ne donnez pas
MANAGE GRANTSpour résoudre un besoin d'accès. Si le besoin se décrit directement (c'est le cas canonique d'un Inherited Grant), accordez l'accès, pas l'autorité d'en accorder. - C'est une frontière de confiance. Le détenteur peut accorder des droits exposant des données ou exécutant du code owner's rights — dans sa limite, mais quand même.
Mon pattern préféré, testé : le rôle du pipeline peut rester propriétaire du schéma, mais sans MANAGE GRANTS il ne pose pas de grants — OWNERSHIP seul sur le schéma → erreur 003102 ; le MANAGE GRANTS accordé à ce rôle → grant hérité accepté. La doc a raison sur toute la ligne, et c'est exactement le découplage qui évite que le rôle du pipeline ne fasse du RBAC à sa main.
Et en Terraform ?
Le provider Snowflake supporte les Inherited Grants depuis la v2.19.0 (30 juillet 2026) : bloc inherited dans snowflake_grant_privileges_to_account_role, dans grant_privileges_to_database_role, dans la data source snowflake_grants, plus la détection de drift sur ces grants — utile précisément parce que la feature est neuve et que quelqu'un finira par en poser un à la main.
resource "snowflake_grant_privileges_to_account_role" "fin_read" {
privileges = ["SELECT"]
account_role_name = snowflake_account_role.fin_reader.name
on_schema_object {
inherited {
object_type_plural = "TABLES"
in_schema = snowflake_schema.finance.fully_qualified_name
# in_database = ... # tout le schéma->base
# in_account = true # tout le compte (lire la section pièges avant)
}
}
}
L'opt-in du paramètre de compte reste de mon côté un ALTER ACCOUNT manuel (ACCOUNTADMIN) — à faire une fois, et à documenter dans votre runbook (le guide d'exploitation de l'équipe).
Migrer sans ouvrir d'accès par erreur
Le risque n°1 de la migration est documenté noir sur blanc : un Inherited Grant couvre les objets existants et futurs. Si vous remplacez un grant qui ne couvrait que le futur...
-- ⛔ Ce remplacement ÉLARGIT l'accès aux tables déjà présentes
GRANT SELECT ON FUTURE TABLES IN DATABASE PROD
TO ROLE FIN_READER; -- ne touche PAS les tables existantes
GRANT INHERITED SELECT ON ALL TABLES IN DATABASE PROD
TO ROLE FIN_READER; -- les touche, immédiatement
...vous venez d'ouvrir silencieusement des années d'historique. La séquence sûre, documentée :
- Inventaire :
SHOW FUTURE GRANTS IN <conteneur>,SHOW GRANTS TO ROLE <role>— repérez les duosON ALL+ON FUTUREidentiques (rôle, privilege, type, conteneur). - Vérifiez l'absence d'exceptions : un grant objet à part, une table exclue ? Alors pas de migration pour ce couple — le cas "exceptions" reste en grants directs.
- Créez l'Inherited Grant AVANT de retirer l'ancien (pas de trou d'accès pendant la transition).
- Vérifiez par l'audit, pas par un SELECT qui passe : tant que les anciens grants existent, une requête qui réussit ne prouve rien — plusieurs chemins d'autorisation coexistent.
SHOW INHERITED GRANTS IN <conteneur>puisSHOW GRANTS ON <objet>:IS_INHERITED = trueetINHERITED_FROMdoivent pointer sur le bon conteneur. - Retirez l'ancien (
ON ALL, puisON FUTURE), puis re-vérifiez que l'accès passe encore par le seul grant hérité.
| Situation | Recommandation |
|---|---|
Même privilège, même rôle, ON ALL + ON FUTURE identiques |
Migrer vers l'Inherited Grant |
| Future grant seul | Migrer seulement si couvrir l'existant est voulu |
| Exceptions par objet | Rester en grants directs (ou resserrer le conteneur) |
| Accès uniforme + lignes/colonnes sensibles | Inherited Grant + masking/row access policies |
| Petit ensemble stable d'objets | Rester en direct, sauf bénéfice admin clair |
| Admin délégué qui maintient des grants uniformes à la main | Migrer, puis réévaluer son MANAGE GRANTS |
Les erreurs que j'ai faites pour vous
| Erreur | Symptôme | Correction |
|---|---|---|
| Tester avec un user qui porte tous les rôles en secondary (secondary roles ALL, mon cas sur ce compte) | Tous mes tests "ça doit échouer" passaient | USE SECONDARY ROLES NONE + SELECT CURRENT_SECONDARY_ROLES() avant chaque test d'accès |
| Se fier au result cache pour valider un changement de droits | Un accès "toujours actif" après revoke — ou l'inverse | ALTER SESSION SET USE_CACHED_RESULT = FALSE + une requête différente à chaque test |
| Revoker l'objet au lieu du conteneur | 0 objects affected, l'accès reste actif |
Le REVOKE doit porter le même mécanisme (INHERITED) que le GRANT |
Croire qu'OWNERSHIP sur le schéma autorise le grant |
003102 Insufficient privileges |
MANAGE GRANTS sur le conteneur (ou plus haut) |
Croire que TABLES couvre les vues |
does not exist or not authorized sur la vue |
Un GRANT INHERITED par type d'objet |
Alors, la fin des Future Grants ?
Dans le cas canonique — "ce rôle lit tout ce schéma, aujourd'hui et demain" — oui, et de loin : une intention au lieu de deux statements, un record d'audit au lieu d'une prolifération, zéro maintenance à la création d'objet. Ce que je garde en grants directs : tout ce qui a des exceptions par objet. Ce que je garde en MANAGE GRANTS délégué : les vrais administrateurs de domaine, pas les besoins d'accès déguisés. Et ce que je surveille : le scope ACCOUNT (les personal databases, souvenez-vous), les types d'objets non couverts (un grant par type), et les revokes qui ne portent pas le mot INHERITED.
Mon checklist RBAC gagne une ligne : là où l'intention d'accès est uniforme, un Inherited Grant remplace le duo ON ALL + ON FUTURE. Un grant qui décrit le modèle, plutôt que mille records qui racontent comment on le contourne.