À la découverte de Vault by HashiCorp

Rédigé par Mohamed Reda LARBI YOUCEF le 18/07/2023
Temps de lecture: 16 minutes

1. Introduction

Aujourd’hui, toutes les applications et les infrastructures ont besoin de manipuler des paramètres sensibles pour pouvoir effectuer des appels API, se connecter aux bases de données, ou se connecter à d’autres services. Stocker et rendre ces paramètres sensibles accessibles de manière sécurisée est un des enjeux majeurs de nos métiers aujourd’hui.

Il existe de nombreuses solutions sur le marché. Certaines managées, comme Secret Manager sur AWS et GCP. Ou bien, des solutions open source, comme Vault by HashiCorp.

Dans le cadre de cet article, nous allons nous intéresser à ce dernier outil, extrêmement populaire et très utilisé.

2. Vault

Vault a été développé par HashiCorp, une société autrice de plusieurs produits très pratiques pour la gestion d’infrastructures, notamment Terraform et Packer. C’est un outil Open Source, écrit en Go et dont la première release remonte à avril 2015.

Vault est un coffre-fort qui permet de stocker plusieurs types de secrets, comme des tokens, des access/secret keys AWS, des identifiants de base de données, des clefs SSH et bien d’autres. Il permet aussi de les rendre accessibles de manière sécurisée.

3. Architecture

Avant d’attaquer la partie installation, nous allons jeter un oeil sur l’architecture de Vault. L’architecture de Vault est une architecture Client-Serveur. Le serveur expose une API REST pour que le client puisse y accéder. Il existe 3 manières d’interagir avec le serveur : Requêtes HTTP, CLI ou Web UI.

Pour stocker les données, Vault s’appuie sur un backend de stockage. Plusieurs backends sont supportés : Le stockage intégré qui s’appuie sur l’algorithme Raft pour répliquer les données, Consul, DynamoDB, etcd…

Vault considère le backend de stockage comme étant un composant externe, et ne lui accorde aucune confiance. De ce fait, les données sont encryptées avant d’être stockées dans le backend de stockage, utilisant l’algorithme AES256 GCM. Ci-dessous, un schéma d’architecture décrivant le fonctionnement de Vault :

Vault Architecture Schema
Architecture de Vault

4. Installation d’un serveur de développement

Il existe deux manières de disposer d’un serveur Vault : self hosted ou bien un Vault managé. Dans le cadre de cet article, nous allons opter pour la première option.

Nous allons commencer par télécharger la CLI de Vault, que vous trouverez ici. À l’heure où j’écris cet article, la dernière version est la 1.13.2. Voici donc une procédure pour l’installer :

1$ wget https://releases.hashicorp.com/vault/1.13.2/vault_1.13.2_linux_amd64.zip 
2$ unzip vault_1.13.2_linux_amd64.zip
3$ mv vault /usr/local/bin
4$ rm vault_1.13.2_linux_amd64.zip
5$ vault version
1Vault v1.13.2 (b9b773f1628260423e6cc9745531fd903cae853f), built 2023-04-25T13:02:50Z

Pour cet article, nous allons avoir besoin d’un serveur de développement en local (vous verrez plus tard pourquoi). Pour ce faire, il suffit d’utiliser la commande suivante pour lancer le serveur localement :

1$ vault server -dev
 1WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
 2and starts unsealed with a single unseal key. The root token is already
 3authenticated to the CLI, so you can immediately begin using Vault.
 4
 5You may need to set the following environment variables:
 6
 7    $ export VAULT_ADDR='http://127.0.0.1:8200'
 8
 9The unseal key and root token are displayed below in case you want to
10seal/unseal the Vault or re-authenticate.
11
12Unseal Key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
13Root Token: XXXXXXXXXXXXXXXXXXXXXXXXX
14
15Development mode should NOT be used in production installations!

Petit warning, comme son nom l’indique (et comme le montre le message au démarrage) ceci est un serveur de développement, il ne doit donc pas être installé de cette manière en production. Dans un serveur de développement, les données sont en mémoire (avec le backend de stockage inmem). En production, nous devons plutôt opter pour un cluster en haute disponibilité (ainsi que bien sûr la mise en place du monitoring, des backups, des logs etc). Il faut aussi un backend de stockage autre que inmem, sous peine de perdre ses données à chaque redémarrage.

Quoiqu’il en soit, maintenant que nous avons lancé notre serveur Vault, nous devons configurer deux variables d’environnement pour s’y connecter :

1$ export VAULT_ADDR='http://127.0.0.1:8200'
2$ export VAULT_TOKEN="ROOTTOKENFOURNIAUDEMMARAGE"

Nous pouvons maintenant vérifier que notre serveur Vault tourne bien, et que nous arrivons bien à nous connecter dessus :

1$ vault status
 1Key             Value
 2---             -----
 3Seal Type       shamir
 4Initialized     true
 5Sealed          false
 6Total Shares    1
 7Threshold       1
 8Version         1.13.2
 9Build Date      2023-04-25T13:02:50Z
10Storage Type    inmem
11Cluster Name    vault-cluster-94c63bf7
12Cluster ID      36a90efc-b487-80f9-937c-72b18df1b516
13HA Enabled      false

Et voila, nous sommes prêts à le manipuler.

5. Création de notre premier secret

Pour stocker des secrets, nous devons utiliser un Secrets Engine. Qu’est-ce qu’un Secrets Engine alors ?
Afin de prendre en charge les différents types de secrets, Vault dispose de composants internes permettant d’implémenter la génération et le stockage de secrets pour un type donné. Ces composants sont donc les Secrets Engines. Si nous reprenons notre exemple de stockage de secrets pour PostgreSQL, nous devons au préalable activer le Secrets Engine de PostgreSQL, puis l’interroger afin de gérer des secrets pour PostgreSQL (Vault fera le nécessaire pour nous).

Chaque objet Vault est identifié par un Path. Il s’agit d’un chemin unique permettant de le représenter. Les clients utilisent le Path pour manipuler les objets dans Vault, et selon le path fourni, la requête est envoyée vers le composant adéquat à l’aide du composant Path Routing. Voici un schéma de l’architecture :

Vault Secrets Engine Architecture
Composants interne de Vault

Nous allons commencer par explorer le Secrets Engine KV, ou Key-Value. Celui-ci permet de stocker des secrets sous forme de clé-valeur. C’est donc la forme de stockage de secrets la plus simple. Pour commencer nous allons lister les Secrets Engine activés :

1$ vault secrets list
1Path          Type         Accessor              Description
2----          ----         --------              -----------
3cubbyhole/    cubbyhole    cubbyhole_c8b7562d    per-token private secret storage
4identity/     identity     identity_2c80c285     identity store
5secret/       kv           kv_801acccc           key/value secret storage
6sys/          system       system_8c4fc66e       system endpoints used for control, policy and debugging

Comme nous pouvons le voir, nous avons déjà à notre disposition des Secrets Engines prêts à être utilisés. Pour cet article, nous allons les ignorer et repartir de zéro afin de mieux comprendre leur fonctionnement.

Chaque Secrets Engine dispose de plusieurs colonnes le décrivant. Les plus importants sont le Path, que nous avons vu précédemment, ainsi que le type qui permet tout simplement à Vault d’indiquer le type du Secrets Engine (parmi une liste prédéfinie).

Commençons par activer un Secrets Engine de type kv (version 2) sur un path /kv-example :

1$ vault secrets enable -path=kv-example kv-v2
1Success! Disabled the secrets engine (if it existed) at: kv-v2/

Une fois notre Secrets Engine activé, il ne reste plus qu’à le manipuler. Créons donc un secret, en le spécifiant :

1$ vault kv put -mount=kv-example test key1=totosecret1
 1==== Secret Path ====
 2kv-example/data/test
 3
 4======= Metadata =======
 5Key                Value
 6---                -----
 7created_time       2023-05-29T14:55:49.277661414Z
 8custom_metadata    <nil>
 9deletion_time      n/a
10destroyed          false
11version            1

Et voila, nous avons créé notre premier secret. Nous pouvons remarquer plusieurs choses. Premièrement, nous avons donné en paramètre (-mount) le path du Secrets Engine à utiliser. Le secret que nous avons créé se nomme “test”, ce qui nous donne le chemin kv-example/data/test pour notre secret. Ensuite, nous pouvons également remarquer que la date de création, ainsi que la date de suppression, sont tracées, et surtout nous pouvons voir que les secrets dans le Secrets Engine Kv sont versionnés. Ceci est une évolution dans la version 2 du Secrets Engine Kv. Ainsi, nous pouvons mettre à jour la valeur de notre secret de la façon suivante :

1$ vault kv put -mount=kv-example test key1=totosecret2
1======= Metadata =======
2Key                Value
3---                -----
4created_time       2023-05-29T15:06:52.971482883Z
5custom_metadata    <nil>
6deletion_time      n/a
7destroyed          false
8version            2

Nous voyons donc que la version de notre secret test a été incrémentée. Nous pouvons récupérer la dernière valeur du secret de la façon suivante :

1$ vault kv get -mount=kv-example test
 1==== Secret Path ====
 2kv-example/data/test
 3
 4======= Metadata =======
 5Key                Value
 6---                -----
 7created_time       2023-05-29T15:06:52.971482883Z
 8custom_metadata    <nil>
 9deletion_time      n/a
10destroyed          false
11version            2
12
13==== Data ====
14Key     Value
15---     -----
16key1    totosecret2

Et nous pouvons aussi récupérer une version précédente en la spécifiant :

1$ vault kv get -mount=kv-example -version=1 test
 1==== Secret Path ====
 2kv-example/data/test
 3
 4======= Metadata =======
 5Key                Value
 6---                -----
 7created_time       2023-05-29T14:55:49.277661414Z
 8custom_metadata    <nil>
 9deletion_time      n/a
10destroyed          false
11version            1
12
13==== Data ====
14Key     Value
15---     -----
16key1    totosecret1

Il est également possible de fournir plusieurs clés à un même secret. Nous pouvons aussi utiliser la commande patch, au lieu de la commande put. La commande patch permet de mettre à jour sans écraser l’existant (et dans ce cas ci, elle permet donc de conserver key1).

Voila pour le Secrets Engine KV, il existe d’autres fonctionnalités dans celui-ci, mais nous allons nous intéresser maintenant à une autre fonctionnalité dans Vault, faisant une grande partie de sa force.

6. Les secrets dynamiques

Les secrets que nous avons vus jusqu’ici sont statiques. Cela signifie que nous devons nous-mêmes les créer, et lorsque nous n’en avons plus besoin, nous devons les supprimer manuellement. Or il arrive souvent que nous devions créer des accès temporaires. Par exemple, un accès à une base de données à un(e) utilisateur/utilisatrice pour une action spécifique. Autre exemple, nous devons, depuis un pipeline de CI/CD, déployer des ressources dans AWS avec des access/secret keys temporaires, rendant notre pipeline plus sécurisé.

Vault répond à ce besoin avec les secrets dynamiques. L’idée n’est pas de créer soi-même les secrets, mais plutôt laisser Vault s’en occuper à la demande, et de le supprimer après un certain temps. Ainsi, si nous reprenons notre exemple d’accès temporaire à une base de données, disons PostgreSQL, lorsqu’un(e) utilisateur/utilisatrice demande des accès temporaires à la base de données à Vault, celui-ci se connecte à la base de données PostgreSQL, puis crée un utilisateur PostgreSQL avec un mot de passe généré automatiquement, et enfin le fourni au client demandant l’accès. Le secret généré possède une date d’expiration, lorsqu’elle est atteinte, Vault se connecte à la base de données et supprime l’utilisateur en question.

Voyons cela en pratique. Nous allons mettre en place l’exemple que nous avons décrit précédemment. Nous allons commencer par lancer une base de données PostgreSQL :

1$ docker run -p 5432:5432 -e POSTGRES_PASSWORD=toto -d postgres
2$ psql --port 5432 --host localhost -U postgres -d postgres
1Password for user postgres: 
2psql (15.2, server 15.3 (Debian 15.3-1.pgdg110+1))
3Type "help" for help.
4
5postgres=#

Nous avons une base de données PostgreSQL qui tourne. Nous allons maintenant configurer Vault pour y accéder et créer des secrets (i.e des utilisateurs PostgreSQL). Pour cela, il faut activer un Secrets Engine de type database :

1$ vault secrets enable -path=example-postgres database
2$ vault secrets list
1Path                 Type         Accessor              Description
2----                 ----         --------              -----------
3cubbyhole/           cubbyhole    cubbyhole_2634eda7    per-token private secret storage
4example-postgres/    database     database_dedda7dd     n/a
5identity/            identity     identity_ce6aa74b     identity store
6secret/              kv           kv_18107d28           key/value secret storage
7sys/                 system       system_1cb0e4b7       system endpoints used for control, policy and debugging

Nous allons maintenant configurer notre Secrets Engine afin qu’il puisse se connecter à notre base de données :

1$ vault write example-postgres/config/example-postgresql-database \
2    plugin_name="postgresql-database-plugin" \
3    allowed_roles="my-role" \
4    connection_url="postgresql://{{username}}:{{password}}@127.0.0.1:5432/postgres" \
5    username="postgres" \
6    password="toto"
1Success! Data written to: example-postgres/config/example-postgresql-database

Nous allons consulter notre configuration :

1$ vault read example-postgres/config/example-postgresql-database
1Key                                   Value
2---                                   -----
3allowed_roles                         [my-role]
4connection_details                    map[connection_url:postgresql://{{username}}:{{password}}@127.0.0.1:5432/postgres username:postgres]
5password_policy                       n/a
6plugin_name                           postgresql-database-plugin
7plugin_version                        n/a
8root_credentials_rotate_statements    []

Comme nous pouvons le voir, nous devons donner à Vault les identifiants pour se connecter à la base de données. Pour la syntaxe de connection_url, Vault utilise un template, permettant de remplacer les valeurs de username et password par les deux champs en dessous. Pour leurs valeurs, nous avons mis l’utilisateur postgres, ainsi que le mot de passe que nous avons attribué au démarrage (à des fins de démo bien entendu, nous opterons en pratique pour un utilisateur avec des droits un peu plus restreints).

Maintenant, nous devons également configurer les permissions pour les utilisateurs temporaires. Pour cela, nous devons créer un rôle (que nous avons nommé “my-role” au-dessus) et lui affecter une requête SQL qui lui permettra de donner les droits aux clients :

1$ vault write example-postgres/roles/my-role \
2    db_name="postgres" \
3    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
4        GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
5    default_ttl="1h" \
6    max_ttl="24h"
1Success! Data written to: example-postgres/roles/my-role

Consultons notre rôle :

1$ vault read example-postgres/roles/my-role
 1Key                      Value
 2---                      -----
 3creation_statements      [CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';         GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";]
 4credential_type          password
 5db_name                  example-postgresql-database
 6default_ttl              1h
 7max_ttl                  24h
 8renew_statements         []
 9revocation_statements    []
10rollback_statements      []

Et c’est tout ! Nous pouvons maintenant demander des identifiants temporaires avec le chemin SECRET_ENGINE/creds/ROLE :

1$ vault read example-postgres/creds/my-role
1Key                Value
2---                -----
3lease_id           example-postgres/creds/my-role/hlNhgJtqTo3ImViLAfftZad8
4lease_duration     1h
5lease_renewable    true
6password           vDyQSMePVVc8UM2K-ZB6
7username           v-root-my-role-M10U8ltqaA6DfWuEkcJ0-1686066386

Et le voici, nous avons donc généré notre premier utilisateur PostgreSQL temporaire avec Vault ! Nous pouvons également voir que cet utilisateur a une date d’expiration (lease_duration). Lorsque ce temps sera passé, Vault supprimera l’utilisateur automatiquement. Nous pouvons aussi le supprimer nous même avec le lease_id. Testons maintenant notre utilisateur :

1$ psql --port 5432 --host localhost -U v-root-my-role-M10U8ltqaA6DfWuEkcJ0-1686066386 -d postgres
1Password for user v-root-my-role-M10U8ltqaA6DfWuEkcJ0-1686066386: 
2psql (15.2, server 15.3 (Debian 15.3-1.pgdg110+1))
3Type "help" for help.
4
5postgres=>

Et voila, nous avons donc une méthode très pratique et surtout très sécurisée pour donner des accès temporaires à des utilisateurs/utilisatrices.

7. Authentification a Vault

Jusqu’ici, nous nous sommes authentifiés avec notre Root token fourni au démarrage. Bien entendu, l’utilisation du Root token est déconseillée en production. Nous pouvons créer d’autres tokens. Le token est le mode d’authentification de base dans Vault. Toutes les opérations dans celui-ci se font avec un token. Créons donc notre premier token :

1$ vault token create
1Key                  Value
2---                  -----
3token                hvs.DpEwyygm0k9C2p8Ve20fmeA8
4token_accessor       oz4KSyZHubx2cRMTIk0t92OZ
5token_duration       ∞
6token_renewable      false
7token_policies       ["root"]
8identity_policies    []
9policies             ["root"]

Et voila. Vault nous a généré un token. Nous pouvons l’utiliser de la même manière que le root token :

1$ export VAULT_TOKEN="hvs.DpEwyygm0k9C2p8Ve20fmeA8"

Nous pouvons facilement vérifier que nous sommes bien authentifiés avec le nouveau token avec la commande suivante :

1$ vault token lookup
 1Key                 Value
 2---                 -----
 3accessor            oz4KSyZHubx2cRMTIk0t92OZ
 4creation_time       1685976181
 5creation_ttl        0s
 6display_name        token
 7entity_id           n/a
 8expire_time         <nil>
 9explicit_max_ttl    0s
10id                  hvs.DpEwyygm0k9C2p8Ve20fmeA8
11issue_time          2023-06-05T19:43:01.643938592+02:00
12meta                <nil>
13num_uses            0
14orphan              false
15path                auth/token/create
16policies            [root]
17renewable           false
18ttl                 0s
19type                service

Nous retrouvons notre token dans le champ id. Nous pouvons aussi voir que nous pouvons paramétrer notre token. Nous pouvons notamment attribuer une expiration à notre token, très pratique si nous devons donner un accès temporaire. Nous pouvons aussi le rendre renouvelable ou non, ce qui peut-être pratique pour la rotation.

Cela est très bien, mais l’utilisation de tokens bruts comme nous l’avons fait n’est pas une bonne pratique. D’une part, pour les utiliser nous devons stocker ces tokens quelque part de manière sécurisée, ce qui est peut être difficile avant d’avoir accès à Vault. D’autre part, ces tokens ne sont associés à aucune identité. Il est donc difficile à partir d’un simple token de savoir quel(le) utilisateur/utilisatrice ou quelle application se cache derrière ce token.

Pour remédier à cela, Vault supporte un (très) grand nombre de méthodes d’authentification. Nous pouvons, par exemple, nous authentifier via un provider OIDC/JWT, LDAP, AWS, Github, Azure, Username/Mot de passe, Kubernetes, Certificats TLS. Choisir un mode d’authentification ou un autre va naturellement dépendre de l’organisation dans laquelle nous nous trouvons. Tous les modes d’authentification fournissent un token une fois l’authentification faite.

Les méthodes d’authentifications fonctionnent comme les Secrets Engines. Il faut au préalable les activer (avec la commande “vault auth enable”) sur un path. Selon le mode d’authentification, il faut également les configurer. Une fois fait, les clients peuvent utiliser le path pour s’authentifier sur le mode d’authentification adéquat. Voici une illustration du fonctionnement des méthodes d’authentification :

Vault Auth
Architecture des méthodes d'authentifications de Vault

8. Gestion des droits

Maintenant que nous avons vu comment s’authentifier avec Vault, nous devons voir comment attribuer des droits aux utilisateurs/applications authentifiés. Pour cela, Vault utilise des Policies, qui sont des objets dans Vault permettant de décrire un ensemble de droits sur des chemins (notamment des chemins de Secrets Engines) et qui sont attribués aux clients qui s’authentifient. Les droits sont décrits avec ce que nous appelons des “capabilities”, qui elles-mêmes sont appliquées à un chemin. Enfin, les policies (et la plupart des choses que nous faisons dans Vault), sont écrites en HCL ou en JSON. Voici un exemple de policy :

1path "kv-example/*" {
2  capabilities = ["read", "list"]
3}

Cette policy donne des droits de lecture sur le Secrets Engine KV kv-example que nous avons créé précédemment. Nous allons donc la créer, en commençant par le fichier policy.hcl :

1$ cat policy.hcl
1path "kv-example/*" {
2  capabilities = ["read", "list"]
3}

Après cela, nous pouvons créer la policy dans Vault :

1$ vault policy write example-policy policy.hcl
1Success! Uploaded policy: example-policy

Nous pouvons aussi lister les policies afin de voir celles que nous venons de créer :

1$ vault policy list
1default
2example-policy
3root

Et enfin, nous pouvons la consulter :

1$ vault policy read example-policy
1path "kv-example/*" {
2  capabilities = ["read", "list"]
3}

Maintenant que nous avons créé notre policy, nous devons voir comment l’attribuer à un client. Si nous jetons un coup d’oeil à la création du token que nous avons effectué plus haut, nous pouvons voir qu’il existe un champ “policies” qui vaut [“root”]. Cela signifie que Vault a attribué la policy “root” (avec des droits admin) à notre précédent token. Nous allons maintenant créer un nouveau token et lui attribuer la policy que nous venons de créer :

1$ vault token create -policy="example-policy" -no-default-policy
1Key                  Value
2---                  -----
3token                hvs.CAESIHvRMZ6xjhTeGR91AO2P3sEaWJlPU2mSuoRmBm8Lo3TDGh4KHGh2cy5oTHNVSW1zV1dQcDB5TktFRjM4QUJMMTE
4token_accessor       NlqyfReWS7CHULHaFLXyNx8w
5token_duration       768h
6token_renewable      true
7token_policies       ["example-policy"]
8identity_policies    []
9policies             ["example-policy"]

Et voila, nous avons un token avec des droits plus limités, testons cela :

1$ vault kv get -mount=kv-example test
 1==== Secret Path ====
 2kv-example/data/test
 3
 4======= Metadata =======
 5Key                Value
 6---                -----
 7created_time       2023-06-07T20:13:01.221829245Z
 8custom_metadata    <nil>
 9deletion_time      n/a
10destroyed          false
11version            3
12
13==== Data ====
14Key     Value
15---     -----
16key1    totosecret2
17key2    otherkey

Et si nous essayons une autre opération, par exemple, lister les Secrets Engines :

1$ vault secrets list 
1Error listing secrets engines: Error making API request.
2
3URL: GET http://127.0.0.1:8200/v1/sys/mounts
4Code: 403. Errors:
5
6* 1 error occurred:
7	* permission denied

Nous voyons que nous avons bien les droits accordés par la policy, c’est-à-dire lire des secrets dans le Secrets Engine kv-example, mais si nous souhaitons faire une opération qui ne se trouve pas dans cette policy, Vault nous rejette poliment.

9. Un petit mot sur l’interface graphique

Pour l’instant nous avons manipulé Vault uniquement avec la CLI. Pour celles et ceux qui souhaitent avoir quelque chose de plus visuel, Vault offre une Web UI. Il suffit tout simplement de l’activer au démarrage :

1$ echo "ui = true" > config.hcl
2$ vault server -config=config.hcl -dev

Une fois Vault démarré, nous pouvons y accéder (par défaut) sur http://localhost:8200. Nous devons tout d’abord nous authentifier. Nous pouvons pour cet article utiliser le root token fourni lors du démarrage. Une fois authentifié, nous retrouvons les différents éléments que nous avons vu avec la CLI :

Vault UI New Secrets Engine
Interface graphique de Vault

Nous pouvons créer un Secrets Engine et y ajouter des secrets. Nous pouvons également configurer des modes d’authentification, créer des policies ou encore utiliser quelques utilitaires pratique, comme un générateur de mot de passe. Cette UI est donc pratique pour manipuler les objets graphiquement, et est plutôt intuitive et simple à prendre en main.

10. Conclusion

Voilà qui conclut notre découverte des concepts de base de Vault. Il existe encore énormément de fonctionnalités à explorer. Que cela soit des Secrets Engines, des modes d’authentification, ou encore des intégrations à des paradigmes tel que les architectures Serverless ou la conteneurisation (notamment avec Kubernetes), les sujets ne manquent pas. Quoiqu’il en soit, Vault est un outil très complet, ce qui lui permet de s’adapter à un grand nombre d’organisations, de répondre à de nombreux besoins et de garder les secrets au chaud :).