vault-credential-broker

1 août 2023

Après vous avoir donné notre avis sur les outils Vault et Boundary de Hashicorp dans le Tech Radar, nous nous penchons maintenant sur Vault Credential Broker. C'est une implémentation de Vault en tant que Credential Broker pour Boundary. Cet article contient nos retours d'expérience sur le sujet.

Avantages d’un Credential Broker

Un credential broker est un service qui facilite la gestion et l'échange sécurisé des credentials entre différents systèmes dans une infrastructure informatique. Il centralise et sécurise la gestion des credentials (noms d'utilisateur, mots de passe, clés d'API, certificats, etc.), et garantit un accès autorisé et contrôlé à ces ressources.

Les applications et les services peuvent alors demander les identifiants nécessaires au credential broker de manière sécurisée, sans avoir à les stocker ou à les gérer localement.

Boundary et Vault

Pour implémenter Vault Credential Broker, il faut d’abord comprendre le fonctionnement de Boundary et de Vault.

Boundary

Nous avons déjà creusé l’implémentation et l’utilisation de Boundary dans un précédent article. Revenons en détail sur son fonctionnement.

Boundary permet de créer des sessions entre un utilisateur et un service. C’est une alternative aux bastions et VPN, qui permet d’avoir une granularité fine limitant l’accès à vos ressources. Si vous donnez à vos utilisateurs l’accès à un bastion, ils auront accès à toutes les ressources accessibles par le bastion. Avec Boundary, vous pouvez configurer avec précision quels utilisateurs ont accès à quelles ressources.

Pour expliquer rapidement le fonctionnement de Boundary, il utilise deux composants principaux :

  • un controller. Il authentifie les utilisateurs et gère les différents workers sur l’ensemble du cluster.
  • des workers. Ils permettent de créer des sessions pour des utilisateurs vers des targets (cibles vers lesquelles les utilisateurs veulent se connecter).

Le schéma ci-dessous explique plus en détail le flow de création d’un tunnel vers une target. boundary-client-target

  1. Un utilisateur s’authentifie auprès du controller et demande à se connecter à une target.
  2. Le controller utilise une base de données dans laquelle il stocke, entre autres, les droits des différents utilisateurs. Il vérifie donc si l’utilisateur a le droit d’accéder à la target demandée. Si l’utilisateur a le droit, il crée un sessionID et le stocke dans la base de données.
  3. Si l’utilisateur a le droit d’accéder à la target, le controller va répondre au client avec plusieurs informations, dont :
    • le sessionID
    • les credentials pour se connecter à la target
    • les informations nécessaires pour ouvrir une connexion avec un des workers. Le worker est choisi par le controller via une répartition de charge entre les workers.
  4. Avec les informations obtenues, l’utilisateur essaye de commencer une connexion avec le worker. Il lui envoie son sessionID et les credentials pour se connecter à la target.
  5. Le worker reçoit le sessionID et fait une requête au controller pour savoir si le sessionID est valide.
  6. Le controller vérifie la validité du sessionID dans sa base de données et répond positivement.
  7. Le worker se connecte à la target avec les credentials fournis par le client.
  8. Et voilà : la session entre l’utilisateur et la target est créée via le worker !

Vault

Zoomons maintenant sur le fonctionnement de Vault.

C’est un outil utilisé pour la gestion des secrets. Il offre une granularité fine qui permet de limiter l'accès au sein de vos ressources. Par exemple, vous pouvez avoir une base de données avec plusieurs rôles ayant des droits différents sur les tables.

Vault vous permet d'accéder uniquement aux informations d'identification d'un seul rôle. Vous pouvez ainsi limiter les droits de vos utilisateurs sur la base de données.

Il utilise un coffre-fort, dans lequel vous pouvez stocker de manière sécurisée vos secrets. La gestion de Vault étant path-based, vos secrets sont stockés dans des “chemins”.

La gestion des accès aux secrets est faite avec des policies (politiques). Les policies sont des règles qui donnent des accès en lecture/écriture/etc à certains chemins. Voici un exemple de policy :

# Permet de lire le contenu du chemin postgres/creds/analyst
path "postgres/creds/analyst" {
  capabilities = ["read"]
}

Dans cet exemple, on stocke dans le chemin postgres/creds/analyst des identifiants pour accéder à une base de données postgres avec un rôle analyst. Cette policy donne le droit d’accéder à ces identifiants en lecture.

Lorsqu'un utilisateur ou une application souhaite interagir avec Vault pour lire ou modifier le contenu d’un chemin, il doit d’abord s’authentifier pour obtenir un token d’authentification temporaire. Il est possible de configurer plusieurs méthodes d’authentification pour accéder à Vault, mais il faut mapper une policy à chaque méthode d’authentification.

Par exemple, la documentation Vault donne cet exemple de mapping si on veut configurer LDAP en tant que méthode d’authentification :

  • Members of the OU group "dev" map to the Vault policy named "readonly-dev".
  • Members of the OU group "ops" map to the Vault policies "admin" and "auditor".

Dans cet exemple, le flow d’authentification ressemblerait à ceci pour un développeur utilisant une authentification LDAP : vault

Une fois qu’il est authentifié à Vault, l’utilisateur peut interagir avec Vault en lui présentant son token d'authentification. Vault vérifie les policies associées à ce token pour déterminer si chaque opération est autorisée ou non.

Vault peut aussi être utilisé pour générer des secrets dynamiques et court terme. Pour cela, il suffit de créer un backend, connecté à une target. Cela permet à Vault de créer des sets de credentials temporaires sur cette target. Un utilisateur authentifié peut alors demander la création d’un secret dynamique pour accéder à la target.

dynamic-secret 

Fonctionnement de Vault Credential Broker

Vault Credential Broker est une implémentation de Vault en tant que Credential Broker au sein de Boundary. Cela permet à un client de s’authentifier auprès d’un service avec des credentials temporaires !

Le schéma suivant explique le flow de connexion d’un client Boundary vers une target. Ce schéma commence à devenir complexe, mais il reprend uniquement le schéma de fonctionnement de Boundary vu précédemment. On y ajoute Vault (en bleu) pour générer un secret dynamique pour que l’utilisateur puisse accéder à la target.

vcb

Contrairement au cas d’utilisation de Boundary sans Vault, les informations de connexion fournis au client sont maintenant un secret dynamique émis par Vault. Ce secret a l’avantage d’être temporaire et généré à partir de policies qui restreignent les droits du client sur la target.

Vault Credential Broker au sein d’une infrastructure cloud

Implémentation avec une RDS

On a déployé Vault Credential Broker dans un cluster EKS en utilisant Terraform et Helm. Si vous êtes curieux, le code est disponible sur Github.

Pour implémenter Vault Credential Broker, on a suivi un tutoriel de la documentation Hashicorp. Dans ce tutoriel, nous avons une base de données PostgreSQL avec deux rôles : un rôle analyst qui nécessite un accès à une table en écriture pour créer des rapports mensuels, et un rôle dba (database administrator) qui doit avoir tous les droits sur la base de données pour la gérer.

L’objectif de ce tutoriel est de créer deux targets Boundary qui soient accessibles via des identifiants temporaires émis par Vault : une target avec un accès à la base de données avec le rôle analyst, et une autre avec un accès avec le rôle dba.

Ce que nous avons fait à quand même un peu différé du tutoriel, parce que Vault et Boundary y sont lancés en mode dev, et on a voulu tester en mode prod. On a dû faire pas mal de configurations en plus pour y arriver.

Quand on lance Boundary en prod, il n’y a aucune ressource par défaut et il faut donc créer pas mal de choses avant d’avoir son premier utilisateur. On a suivi un tutoriel qui explique comment créer toutes les ressources nécessaires.

Au final, on a déployé :

  • un controller Boundary avec une base de données PostgreSQL
  • un worker Boundary
  • Vault
  • une target : une base de données PostgreSQL dans une RDS, avec les rôles analyst et dba configurés

L’architecture obtenue est la suivante :

archi

Il y a 3 endpoints :

  • boundary controller api : l’API sur laquelle les clients communiquent avec le controller
  • boundary controller cluster : l’endpoint sur lequel les workers communiquent avec le controller
  • boundary worker : l’endpoint sur lequel les clients communiquent avec le worker pour ouvrir des sessions avec la target

Si vous vous demandez pourquoi on a exposé publiquement l’endpoint boundary controller cluster, alors que le controller et le worker auraient pu communiquer à l’intérieur du cluster, c’était juste pour tester la configuration du cas où le worker et le controller ne seraient pas dans le même cluster. Ici ce n’est pas vraiment utile.

Zoomons sur la configuration de Vault et Boundary. Côté Vault, on crée un “secrets engine” pour le connecter à la base de données PostgreSQL et y créer des secrets dynamiques.

resource "vault_database_secret_backend_connection" "postgres" {
  backend       = vault_mount.db.path
  name          = "postgres"
  allowed_roles = ["dba", "analyst"]
  plugin_name   = "postgresql-database-plugin"

  postgresql {
    connection_url = "postgresql://${data.terraform_remote_state.main.outputs.rds.this.username}:${data.terraform_remote_state.main.outputs.rds.this.password}@${data.terraform_remote_state.main.outputs.rds.this.address}:5432/postgres"
    username       = "vault"
    password       = "vault-password"
  }
}

On ajoute ensuite deux “secret backend role”, un pour le rôle analyst et un pour le rôle dba. Ils permettent de spécifier comment créer les identifiants dynamiques pour chacun des rôles.

resource "vault_database_secret_backend_role" "dba" {
  backend             = vault_mount.db.path
  name                = "dba"
  db_name             = vault_database_secret_backend_connection.postgres.name
  creation_statements = ["CREATE ROLE \"Boundary et Vault : déploiement de Vault Credential Broker\" WITH LOGIN PASSWORD '' VALID UNTIL '' inherit; grant northwind_dba to \"Boundary et Vault : déploiement de Vault Credential Broker\";"]
}

resource "vault_database_secret_backend_role" "analyst" {
  backend             = vault_mount.db.path
  name                = "analyst"
  db_name             = vault_database_secret_backend_connection.postgres.name
  creation_statements = ["CREATE ROLE \"Boundary et Vault : déploiement de Vault Credential Broker\" WITH LOGIN PASSWORD '' VALID UNTIL '' inherit; grant northwind_analyst to \"Boundary et Vault : déploiement de Vault Credential Broker\";"]
}

Pour utiliser Vault en tant que Credential Broker pour Boundary, on crée un token Vault. Ce token permettra à Boundary de demander la création de secrets dynamiques à Vault.

resource "vault_token" "boundary" {

  no_default_policy = true
  policies          = ["boundary-controller", "northwind-database"]

  renewable = true
  period    = "20m"
  ttl       = "24h"
  no_parent = true

  metadata = {
    "purpose" = "boundary"
  }
}

Ce token utilise deux policies. La policy boundary-controller, qui permet à Boundary d'accéder aux informations de son token, mais aussi de le renouveler ou de le révoquer. La policy northwind-database, qui permet d’obtenir les informations nécessaires pour se connecter à la base de données avec les rôles analyst et dba.

Côté Boundary, on crée un “credential store” pour stocker des identifiants de manière sécurisée. Ici le credential store est créé pour gérer les secrets de Vault. On indique donc l’adresse de Vault et le token qu’on a créé précédemment.

# Vault credential store

resource "boundary_credential_store_vault" "example" {
  name        = "vault"
  description = "Vault credential store"
  address     = "http://vault.vault.svc.cluster.local:8200"
  token       = data.terraform_remote_state.vault.outputs.vault_token.client_token
  scope_id    = boundary_scope.project.id
}

On crée ensuite deux “credential librairies”, une pour le rôle analyst et une pour le rôle dba. Elles fournissent des identifiants pour les sessions et gèrent la création, le renouvellement et la révocation des secrets dynamiques. Pour chaque credential library, on spécifie le chemin Vault des identifiants pour se connecter à la base de données avec le rôle associé.

# Credential libraries

resource "boundary_credential_library_vault" "dba" {
  name                = "dba"
  description         = "Northwind DBA credential library"
  credential_store_id = boundary_credential_store_vault.example.id
  path                = "postgres/creds/dba"
}

resource "boundary_credential_library_vault" "analyst" {
  name                = "analyst"
  description         = "Northwind DBA credential analyst"
  credential_store_id = boundary_credential_store_vault.example.id
  path                = "postgres/creds/analyst"
}
  

Finalement, on peut créer les deux targets, en précisant pour chacune quelle credential library utiliser.

# Targets

resource "boundary_target" "northwind_analyst" {
  scope_id     = boundary_scope.project.id
  name         = "Northwind Analyst Database"
  type         = "tcp"
  default_port = "5432"
  session_connection_limit = 1
  host_source_ids = [boundary_host_set.example.id]
  brokered_credential_source_ids = [
    boundary_credential_library_vault.analyst.id
  ]
}

resource "boundary_target" "northwind_dba" {
  scope_id     = boundary_scope.project.id
  name         = "Northwind DBA Database"
  type         = "tcp"
  default_port = "5432"
  session_connection_limit = 1
  host_source_ids = [boundary_host_set.example.id]
  brokered_credential_source_ids = [
    boundary_credential_library_vault.dba.id
  ]
}

Fonctionnement

Une fois configurée, l’utilisation est simple !

On s’authentifie en ligne de commandes à Boundary grâce à la commande boundary authenticate. Pour cela il faut indiquer l’adresse de l’API du controller. On récupère un token qui nous authentifie auprès de Boundary.

boundary-authenticate

Pour se connecter à une target, il faut d’abord récupérer son id. Cette partie n’est pas très user-friendly en CLI, car elle se fait en 3 commandes, mais ce n’est pas trop compliqué.

  • On commence par lister les organisations avec la commande boundary scopes list pour récupérer l’id de l’organisation.
  • On liste les projets au sein de l’organisation avec la commande boundary scopes list -scope-id $ORG_ID pour récupérer l’id du projet.
  • On liste les targets au sein du projet avec la commande boundary targets list -scope-id $PROJECT_ID et on récupère l’id de la target qui nous intéresse.

connect-target

Une fois qu’on a l’id de la target, il suffit d’utiliser la commande boundary connect en précisant l’id de la target. Il faut aussi préciser la commande à exécuter sur la machine distante. La CLI de Boundary gère directement certains exécutables. Par exemple, pour se connecter à une base de données PostgreSQL, on peut utiliser la commande boundary connect postgres.

boundary-connect-postgres

Pour les exécutables qui ne sont pas gérés par Boundary, il faut installer l’exécutable sur la machine locale et utiliser la commande boundary connect -exec <executable>. C’est un petit bémol par rapport à un tunnel ssh.

Si on ouvre une session par target et qu’on fait une requête à la base de données pour lister les utilisateurs, on observe les utilisateurs qui ont été créés dynamiquement : v-token-to-analyst-xxxx et v-token-to-dba-xxxx. Ces utilisateurs ont des mots de passes temporaires et sont respectivement membres des rôles analyst et dba.

role-analyst-dba

Cas d’utilisation

Utilisation avec une base de données

On a vu dans l’exemple de fonctionnement ci-dessus qu’on peut utiliser Vault Credential Broker pour se connecter à une base de données PostgreSQL, en ligne de commandes, avec des identifiants temporaires.

C’est pratique pour des développeurs qui veulent faire du debug, mais est-ce qu’on peut également l’utiliser pour se connecter à la base de données depuis n’importe quel service ? Par exemple depuis un logiciel d’administration, comme pgadmin ? Après vérification, la réponse est oui !

Il suffit d’utiliser la commande boundary connect sans préciser d’exécutable. Boundary ouvre alors une socket locale et vous renvoie le port sur lequel elle est ouverte, ainsi que les identifiants temporaires.

boundery-connect

Par exemple, on a essayé de se connecter à pgadmin avec ces identifiants :

pgadmin

Et ça fonctionne !

postgres

Des développeurs pourraient aussi utiliser cette technique pour utiliser la base de données avec une application en développement.

Autres targets

Dans notre exemple, on a configuré une target qui est une base de données PostgreSQL. Il est aussi possible de configurer d’autres types de targets. Il y a d’autres cas d’utilisation qui sont intéressants :

  • se connecter à d’autres types de bases de données avec des credentials dynamiques. Entre autre : MongoDB, MYSQL, Oracle, Redis
  • se connecter à un cluster Kubernetes, avec des tokens de service accounts générés dynamiquement
  • se connecter en SSH à une machine, avec un One-Time SSH Password généré dynamiquement

Plus généralement, vous pouvez utiliser Vault Credential Broker avec une target si Vault dispose d’un Secrets Engine qui peut être utilisé avec votre ressource. Vous pouvez trouver la liste des Secrets Engine qui existent dans la documentation.

Avantages et désavantages

Avantages :

✅ Plus de sécurité dans l’accès à vos ressources.

✅ Une fois déployée, la gestion est très facile.

✅ La connexion au target pour les clients est facile.

Désavantages :

❌ L'implémentation nécessite une montée en compétences importante pour comprendre les concepts et configurations nécessaires, en particulier pour Boundary. Cela rend l'expérience d'implémentation complexe lorsqu’on ne connaît pas l’outil.

❌ Configurer Boundary en prod est assez long. Il y a pas mal de documentation à faire pour savoir quelles ressources créer. Pour information, on avait commencé à créer toutes les ressources en lignes de commandes et c’était vraiment très long. On a fini par utiliser le provider Boundary de Terraform et ça facilite la vie !

❌ La CLI peut encore être améliorée ! Pour l’instant, la récupération de l’id des targets se fait en 3 commandes. Il faut aussi entrer l’adresse de l’API Boundary dans chaque commande, ce qui est assez pénible.

Conclusion

Vault Credential Broker est un bon outil pour sécuriser votre infrastructure en ayant une granularité fine dans la gestion des accès à vos ressources. Par contre, il faut pas mal de temps pour le prendre en main et le configurer. C’est donc un outil qu’on ne recommanderait pas pour un petit projet, à moins de l’avoir déjà utilisé et de le maîtriser.

Par contre, il peut être très intéressant à déployer sur un projet où il est possible de consacrer du temps à sa configuration.