Publié par

Il y a 1 mois -

Temps de lecture 13 minutes

GCP : faciliter l’administration de son IAM grâce à une représentation graphe (Neo4j) [1/2]

Introduction

Tous les administrateurs de plateforme Cloud vous le diront : l’administration des ressources, quel que soit le provider, n’est pas une chose aisée ; notamment lorsqu’il s’agit de sécurité et de contrôle d’accès aux ressources. Déterminer facilement qui a accès à quoi (et pourquoi) est primordial, mais cela peut s’avérer fastidieux avec les outils par défaut.

Nous allons voir ici comment simplifier cette tâche sur Google Cloud Platform. Dans ce premier article, nous verrons que, grâce à une représentation graphe des ressources et des rôles des utilisateurs sur celles-ci (grâce à l’IAM, Identity Access Management), l’administration de son organisation GCP n’aura jamais été aussi simple. Pour répondre à notre besoin principal de contrôle d’accès, ce graphe représente principalement les rôles entre les utilisateurs et les ressources, mais l’architecture modulaire pourra permettre d’intégrer facilement d’autres types de ressources. Le second article (à venir d’ici quelques jours!) présentera plus en détail l’architecture logicielle mise en place.

Sans plus tarder, voilà le genre de graphe représentant nos ressources que nous pouvons manipuler :

Pourquoi un graphe?

Avant de débuter, revenons sur les besoins qui nous ont poussés à développer cette solution.

Outils existants et limitations

Pour visualiser les droits et rôles sur GCP, 2 outils sont à notre disposition par défaut :

Bien que pratique pour faire des scripts, il peut parfois être fastidieux d’utiliser la CLI, et notamment de devoir enchaîner les commandes pour naviguer à travers les différentes ressources. Son pendant graphique – la Console – permet quand à elle de visualiser d’un seul coup d’oeil les droits sur une ressource, que ce soit via la page IAM (niveau organisation, folder, projet), où sont aussi affichés les droits hérités :

ou bien via l’info panel sur des ressources particulières (dataset, bucket, …) :

Mais, à moins de faire des copies d’écran, le niveau de visualisation supplémentaire qu’offre la Console n’est pas vraiment exploitable.

De plus, avec ces 2 méthodes, nous pouvons seulement répondre à la question : « Qui a accès à une ressource donnée?« . Cependant, la plupart du temps, cela n’est pas suffisant lorsqu’on en vient à parler sécurité ou audit. La question inverse, c’est-à-dire : « À quelle ressource (à travers tous les projets de l’organisation) peut accéder cet utilisateur ou service account? » est aussi primordiale quand on gère des problématiques d’accès aux données.

Répondre à cette question directement n’est néanmoins pas si simple, car il faut notamment prendre en compte, en plus des droits sur les ressources directement, des droits hérités. Les droits peuvent être hérités d’un folder aux projets de ce folder, ou bien même d’une organisation. Un utilisateur peut aussi appartenir à un groupe (au sens groupe GSuite) et bénéficier alors transitivement des droits donnés à ce groupe.

Même en gérant de manière déclarative et autoritaire les droits via de l’infra-as-code (avec Terraform, ou Google Deployment Manager par exemple), on se rend compte qu’il n’est pas toujours simple et rapide de retrouver ces informations.

Besoins

En somme, les outils proposés par défaut sur GCP (CLI et Console) ne répondent pas à 3 besoins principaux :

  • représentation visuelle des ressources et des interactions (au moins d’un point de vue IAM)
  • exploration : naviguer simplement dans l’IAM et découvrir qui a quel droit, ou plus généralement comment les ressources sont liées entre elles
  • détection d’anomalies : il s’agit là d’une sous-partie du monitoring, qui pourrait par exemple nous aider à détecter des rôles inutiles. Prenons l’exemple d’un utilisateur qui possède à la fois un rôle X sur un projet et ce même rôle X sur le folder contenant le projet; dans ce cas, le rôle X sur le projet est inutile

Une représentation sous forme de graphe de nos ressources nous a entièrement permis de répondre à ces besoins.

Représentation graphe

Cette solution a été mise en place dans le cadre d’une mission où l’administration de GCP couvrait environ 200 projets. Au début de la création de cet outil (mars 2019), quelques projets existaient pour représenter ces données sous forme d’un graphe :

Spotify a aussi publié un article il y a quelques jours sur le sujet (Painting a picture of your infrastructure in minutes), prouvant ainsi qu’un réel besoin est présent. Cela peut aussi laisser penser que GCP pourrait peut-être bientôt intégrer une telle représentation directement dans leur plateforme. En attendant patiemment cette intégration (croisons les doigts!), je vais vous présenter notre graphe, ainsi que l’architecture technique mise en place pour sa création (dans un 2nd article à venir pour les plus curieux). Sans entrer dans le détail ici, sachez tout d’abord que la base de données graphe choisie est Neo4j, et ce pour 3 raisons principales :

  • sa vaste adoption : Neo4j est sans aucun doute la base de données graphe la plus utilisée
  • son langage de manipulation des données (Cypher)
  • son interface graphique prête à l’emploi (Neo4j browser)

Ce choix n’impacte cependant pas la modélisation présentée ci-dessous, qui est valide pour n’importe quel format de graphe.

Modélisation

Les nœuds et relations dans notre graphe peuvent être résumés avec le modèle suivant (simplifié pour les besoins de l’article) :

Nœuds

On retrouve parmi les nœuds principaux :

  • Project : représente un projet GCP
  • Folder : représente un folder ou une organization : nous avons ici fait le choix de regrouper les 2 concepts car représentant tous les deux un regroupement de folders ou de projets
  • Account : représente un utilisateur, un groupe ou un service account; ils sont discriminés par une propriété du nœud (accountType)
  • Dataset
  • Bucket

Relations

Les noms des relations, ainsi que les types de nœuds sources et destinations devraient suffire à comprendre leur signification, mais une courte description ne fera pas de mal :

  • HAS_ROLE : cette relation possède une propriété role, correspondant à des rôles IAM prédéfinis, comme roles/owner, roles/bigquery.dataViewer, …
  • HAS_PARENT : elle permet de relier les projets ou les folders à leurs folders/organizations parents
  • IS_IN : relie un Bucket/Dataset au projet auquel il appartient
  • BELONGS_TO : relie un Account au projet auquel il appartient
  • IS_MEMBER_OF : représente le lien entre un Account de type « utilisateur » et un Account de type « groupe ». Ces liens sont obtenus grâce à un mapping entre les utilisateurs et les groupes, récupéré depuis GSuite

Exemple de requêtes

Voyons tout de suite comment, grâce à ce graphe, il est possible de résoudre facilement certains de nos problèmes initiaux :

  • grâce au langage de requêtage Cypher
  • ou bien directement depuis le Neo4j browser

Chacune des questions ci-dessous reprend de vraies problématiques rencontrées dans le cadre d’administration de nos ressources.

Quel rôle a un utilisateur?

Ici, pas besoin de requête : en sélectionnant simplement notre utilisateur (entouré en rouge) dans le Neo4j browser, et en affichant ses noeuds voisins, nous pouvons voir et explorer facilement les rôles qu’il possède sur les projets, les datasets, mais aussi les groupes auxquels il appartient, et donc transitivement les rôles hérités de ces groupes.

Dans l’interface graphique, survoler un noeud ou une relation permet d’afficher ses détails (propriétés comme l’adresse mail de l’utilisateur, le role associé à une relation HAS_ROLE, …), et cliquer sur un nœud permet d’étendre ses relations. Le Neo4j browser est ici un véritable atout pour l’exploration de données.

Qui a le rôle bigquery.dataViewer sur un projet se trouvant sous un folder donné?

Pour cette question, une requête Cypher est nécessaire car il faut filtrer sur plusieurs paramètres (rôle et nom du folder). La syntaxe est assez claire et ne devrait pas nécessiter d’explication particulière. Pour les néophytes de Cypher, il faut imaginer le contenu de la clause MATCH comme un parcours de graphe (entre parenthèses on retrouve les nœuds et entre crochets les relations). La clause WHERE permet de filtrer sur des propriétés, comme en SQL.

MATCH	(a: Account)-[role: HAS_ROLE]->(p: Project)-[has_parent :HAS_PARENT]->(folder: Folder)
WHERE	role.role = "roles/bigquery.dataViewer" AND folder.displayName = "my-folder"
RETURN	a, role, p, has_parent, folder;

Résultat :

On peut voir sur le graphe résultant une certaine symétrie. Dans notre exemple, c’est voulu, car entre les 2 projets sous ce folder (en bleu, tout en haut), seul l’environnement varie (pré-prod à gauche et prod à droite). On retrouve donc de chaque côté les mêmes service accounts, avec les mêmes rôles. Les service accounts au centre sont ceux communs aux 2 environnements, typiquement ici des service accounts d’applications qui ont besoin de requêter des données à la fois sur la pré-prod et sur la prod.

Cette requête est très pratique pour vérifier, visuellement, si il existe des différences entre les 2 projets (ce qui ne devrait pas être le cas ici).

Quels rôles a un groupe (et ses utilisateurs) sur les différents projets?

Ici, la requête est un peu plus complexe : on va récupérer, pour un groupe donné, l’ensemble des rôles sur l’ensemble des projets, ainsi que tous les utilisateurs de ce groupe avec leurs rôles (aussi sur l’ensemble des projets). Puisqu’une requête vaut mieux que mille explications :

MATCH	(user: Account {accountType: "user"})-[is_member_of: IS_MEMBER_OF]->(group: Account{accountType: "group", email: "gcp_my_group@myorganization.com"})
WITH	user, is_member_of, group
MATCH	(user)-[user_role: HAS_ROLE]->(p1: Project)
WITH	user, is_member_of, group, user_role, p1
MATCH	(group)-[group_role: HAS_ROLE]->(p2: Project)
RETURN	user, group, is_member_of, user_role, group_role, p1, p2;

Résultat :

Cette requête permet ici de voir que beaucoup d’utilisateurs de ce groupe ont des rôles sur des projets (notamment l’utilisateur de droite qui a beaucoup de rôles owner). C’est un pattern que l’on cherche à éviter au maximum, puisqu’il est plus simple (et plus sécurisé dans le cas général) de gérer des droits sur des groupes plutôt que sur des utilisateurs nominativement (imaginez le cas d’un utilisateur qui quitte l’organisation).

Quels sont les rôles inutiles?

Reprenons ici l’exemple présenté en introduction (détection d’anomalies) : « si un utilisateur possède à la fois un rôle X sur un projet et ce même rôle X sur le folder contenant le projet, alors le rôle X sur le projet est inutile ». Cela se traduit par la requête suivante, qui va nous permettre de visualiser les rôles identiques entre utilisateurs et projets/folders :

MATCH	(user: Account)-[isMemberOf :IS_MEMBER_OF]->(group: Account),
		(group)-[groupRole :HAS_ROLE]->(roleTarget),
		(user)-[userRole :HAS_ROLE]->(roleTarget)
WHERE	groupRole.role = userRole.role
RETURN	user, isMemberOf, group, groupRole, userRole, roleTarget;

L’égalité entre les 2 rôles (rôle du groupe et rôle de l’utilisateur) est décrite par la clause WHERE. Notons aussi ici que la destination du rôle n’est pas forcément un projet, mais n’importe quelle type de ressource.

Résultat :

Le résultat fait apparaître l’ensemble des triplets (utilisateur, groupe, ressource) qui correspondent à notre critère. Pour chacun de ces clusters les rôles entre utilisateur et ressource pourront être éliminés sans aucune conséquence, puisque l’utilisateur a déjà ce rôle via le groupe auquel il appartient. Cela permettra de clarifier notre graphe, qui est déjà bien assez complexe!

Ce cas est vraiment intéressant car il montre quelque chose qui aurait été très difficile, voire impossible à détecter sans un graphe.

Qui peut voir les données d’un dataset?

Pour répondre à cette question, la requête Cypher ne sera pas présentée, car peu intéressante et légèrement complexe (et redondante). Sachez simplement que l’on peut déterminer qu’un utilisateur peut voir les données d’un dataset si :

  • il a un des rôles suivants (roles/owner, roles/editor, roles/viewer, roles/bigquery.admin, roles/bigquery.dataEditor, roles/bigquery.dataOwner, roles/bigquery.dataViewer, roles/bigquery.metadataViewer) sur le dataset
  • il appartient à un groupe avec un des rôles précédents sur le dataset
  • il a un de ces rôles sur le projet contenant le dataset
  • il a un de ces rôles sur le folder contenant le projet contenant le dataset
  • il a un de ces rôles sur l’organisation contenant le folder contenant le projet contenant le dataset

Cette requête ressemble beaucoup à ce que l’on peut récupérer depuis la Console, à l’exception qu’ici on bénéficie d’informations supplémentaires :

  • on peut filtrer sur plusieurs rôles (les rôles qui permettent de lire de la donnée dans BigQuery)
  • on peut récupérer les droits hérités d’un groupe

Ici, la représentation graphe n’a pas vraiment de sens, donc le résultat peut être affiché sous forme de tableau récapitulatif.

Résultat :

Dans cet exemple, seuls 7 utilisateurs sont capables de lire les données du dataset donné. Certains ont des droits directs, d’autres des droits hérités du projet ou de l’organisation; parmi eux, certains ont aussi des droits car ils appartiennent à un groupe particulier. La raison (colonne why) permet de déterminer simplement pourquoi l’utilisateur a ce rôle.

Conclusion

Actuellement en place dans notre mission, cette solution permet de manipuler à ce jour environ 3500 nœuds et 9500 relations pour un graphe. Les graphes sont versionnés, ce qui permet facilement de voir l’IAM sous forme de graphe à un instant t. Grâce à cette solution, nous avons gagné en visibilité sur notre IAM, et en facilité d’exploration. La représentation graphe des ressources et de l’IAM est très adaptée et très efficace, puisque le temps de réponse moyen d’une requête comme celles présentées ci-dessus est d’1 seconde maximum. Le langage Cypher est très expressif mais nécessite une phase de prise en main pour des requêtes plus avancées.

Dans le prochain article, je détaillerais pas à pas l’architecture logicielle mise en place pour arriver à ce résultat.

Publié par

Publié par Jonathan Norblin

Data Engineer & Software Craftsman

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.