OpenBao en HA sur Debian 12
OpenBao est un projet dérivé (fork) de HashiCorp Vault, conçu pour offrir une solution de gestion sécurisée des secrets et des données sensibles, adaptée aux besoins spécifiques des entreprises. Reposant sur l'architecture robuste et éprouvée de HashiCorp Vault, OpenBao reprend les principes fondamentaux de sécurité, de chiffrement des données et de gestion des accès tout en apportant des améliorations et des fonctionnalités propres.
Dans ce LAB nous allons en place un cluster OpenBao, que nous atteindrons au travers d'un Loadbalancer HAproxy.
Attention ! La partie HAproxy a pour seul but de montrer comment le configurer pour un backend OpenBao. En effet dans notre Lab, c'est le SPOF, nous vous recommandons d'utiliser soit un Loadbalancer d'un cloud provider qui est résilient à la panne, soit de mettre en place une solution de master/slave sur le HAproxy.
Prérequis:
- 3 serveurs sur un réseau que l'on nommera "OPENBAO NETWORK"
- 1 serveur sur les réseaux "OPENBAO NETWORK" et "CLIENT"
- Un accès root / sudoer à ceux-ci
L'architecture cible est la suivante:
Installation des .debs
Actuellement, il n'y a pas de repository apt pour Debian. Nous devons donc installer l'application via un .deb fourni par OpenBao.
Sur chaque serveurs exécuter les commandes suivantes:
# Télécharger dans le répertoire temporaire le .deb
cd /tmp && wget https://github.com/openbao/openbao/releases/download/v2.0.1/bao_2.0.1_linux_amd64.deb
# Installation du .deb d'OpenBao
dpkg -i /tmp/bao_2.0.1_linux_amd64.deb
# Activation au démarrage
systemctl enable openbao
# Vérification que le binaire est présent
bao -h
Il faut également vérifier que le swap est désactivé sur votre serveur afin d'éviter des problèmes de sécurité (les données sur le swap peuvent être en clair).
# Afficher les parametres du service
root@inframinds-lab-openbao01:/tmp# systemctl cat openbao
La ligne MemorySwapMax=0 doit apparaitre.
Génération de notre PKI
Nous pouvons utiliser OpenBao comme PKI, l'une des fonctionnalités de la PKI est la génération de certificats.
Afin d'avoir une architecture haute disponibilités et sécurisé d'OpenBao nous devons générer des certificats pour les API de celui-ci.
Or, se pose un problème ...
Comment lui donner un certificat si c'est lui-même qui est censé les générer. Cela donne un problème 🥚 et de 🐔
Habituellement, on doit avoir une Autorité de certification Hors-Ligne, cette Autorité de certification est utilisée pour générer des certificats intermédiaires qui eux sont en ligne (et donc utilisable dans OpenBao)
Nous allons donc:
- Créer un ROOT CA (qui sera notre Autorité hors ligne).
- Créer un CA Intermediaire pour générer les certificats pour initier notre cluster OpenBao.
- Créer un certificat pour nos serveurs OpenBao
Génération du CA Root
Comme il s'agit d'une PKI hors ligne, nous allons exécuter ces commandes sur notre poste local pour simuler un serveur hors ligne.
# Préparation de l'environnement
export CA_NAME=CA_INFRAMINDS_LABS
cd && mkdir $CA_NAME && cd $CA_NAME/
# Génération de la clé RSA (Les elliptic curve ne sont pas supporté ...)
openssl genrsa -out ${CA_NAME}.key 2048
# Autosignature du certificat
openssl req -x509 -new -nodes -key $CA_NAME.key -sha256 -days 3650 -out $CA_NAME.crt -subj '/CN=Inframinds Root CA/C=FR/ST=IDF/L=Paris/O=Inframinds' -addext "keyUsage= critical, cRLSign, digitalSignature, keyCertSign" -extensions v3_ca
Explication des paramètres suivant:
-days 3650 -> Expiration du certificat Root dans 10 ans
-sha256 -> Algorithme de digest
-addext -> Restriction sur l'usage de la clé
-extensions -> Utilisation du template CA (v3_ca)
Cela nous génère 2 fichiers CA_INFRAMINDS_LABS.key & CA_INFRAMINDS_LABS.crt que nous allons utiliser pour signer les intermédiaires
Génération du CA intermediaire pour l'infrastructure OpenBao
Toujours sur notre serveur Hors ligne (on notre laptop) 😄
export CA_NAME=CA_INFRAMINDS_LABS
# Génération de la clé
openssl genrsa -out ${CA_NAME}_inter.key 2048
# Génération du CSR (un formulaire à signer par le ROOT CA)
openssl req -new -nodes -text -out ${CA_NAME}_inter.csr -subj '/CN=Inframinds intermediate OpenBao CA/C=FR/ST=IDF/L=Paris/O=Inframinds' -addext "basicConstraints=critical,CA:TRUE" -addext "subjectKeyIdentifier=hash" -addext "keyUsage= critical, cRLSign, digitalSignature, keyCertSign" -key ${CA_NAME}_inter.key
# Signature du formulaire et génération du certificat intermédiaire
openssl x509 -req -in ${CA_NAME}_inter.csr -text -sha256 -days 1825 -CA $CA_NAME.crt -CAkey $CA_NAME.key -CAcreateserial -out ${CA_NAME}_inter.crt -copy_extensions copyall
Nous nous retrouvons donc avec ces 4 fichiers:
- CA_INFRAMINDS_LABS.key
- CA_INFRAMINDS_LABS.crt
- CA_INFRAMINDS_LABS_inter.crt
- CA_INFRAMINDS_LABS_inter.key
Les fichiers .key doivent rester secret et ne jamais être divulgué.
Génération du certificat final pour l'infrastructure OpenBao
Dans le but de faciliter le déploiement, nous allons créer un unique certificat pour nos 3 nodes, mais dans le monde de l'entreprise, il est recommandé d'avoir un certificat distinct pour chaque node.
export CA_NAME=CA_INFRAMINDS_LABS
# Génération du CSR et de la clé privé
openssl req -new -nodes -text -out openbao_server.csr -keyout openbao_server.key -subj "/CN=openbao.lab" -addext "subjectAltName = IP:192.168.100.2, IP:192.168.100.3, IP:192.168.100.4, IP:127.0.0.1" -addext "subjectKeyIdentifier=hash" -addext "keyUsage=critical, digitalSignature, keyEncipherment, keyAgreement" -addext "extendedKeyUsage= critical, serverAuth"
# Signature de la CSR par la CA intermediaire
openssl x509 -req -in openbao_server.csr -text -days 365 -CA ${CA_NAME}_inter.crt -CAkey ${CA_NAME}_inter.key -CAcreateserial -out openbao_server.crt -copy_extensions copyall
Ici, nous avons mis les IP de nos nodes OpenBao.
Nous utiliserons cela plutôt que le CN car nous n'avons pas de nom de domaine pour ce lab.
Nous avons donc un certificat final openbao_server.crt & openbao_server.key
Configuration d'OpenBao
OpenBao fourni un system de stockage partagé/répliqué natif utilisant raft (https://thesecretlivesofdata.com/raft/). C'est cette solution que nous allons utiliser.
Préparation des nodes OpenBao
Le dossier /opt/raft sera utilisé pour stocker nos data.
# A executer sur tout les nodes
mkdir -p /opt/raft
chown openbao:openbao /opt/raft
Il faut également ajouter sur nos nodes (et d'ailleurs sur tous les clients qui souhaiteront utiliser OpenBao, et les certificats générés via celui-ci) notre certificat ROOT CA. (CA_INFRAMINDS_LABS.crt)
# A executer sur tout les nodes
cp CA_INFRAMINDS_LABS.crt /usr/local/share/ca-certificates/
update-ca-certificates
La commande update-ca-certificates permet d'ajouter des certificats au bon emplacement pour être utilisé par le système.
Configuration du services
La version 2.0.0 n'inclue pas de WEB Ui celle ci est en refonte...
Sur chaque node Openbao vous devrez éditer le fichier de conf .hcl et faire les changements suivants:
- cluster_addr / api_addr / address: mettre l'IP de votre serveur
- retry_join: mettre l'IP des autres membres du cluster
ui = true # Pas utile pour le moment car il n'y a pas d'ui...
cluster_addr = "https://192.168.100.2:8201" # Votre addresse pour communiquer avec les membres du cluster
api_addr = "https://192.168.100.2:8200" # Votre addresse pour que les client puissent ce connecter ou que les autres membre vous envoie les requetes.
cluster_name = "lab-inframinds"
storage "raft" {
path = "/opt/raft"
node_id = "node1"
retry_join
{
leader_api_addr = "https://192.168.100.3:8200" # L'adresse du node2
}
retry_join
{
leader_api_addr = "https://192.168.100.4:8200" # L'addresse du node3
}
}
listener "tcp" {
address = "192.168.100.2:8200" # Votre addresse
tls_cert_file = "/opt/openbao/tls/openbao_server_full.crt"
tls_key_file = "/opt/openbao/tls/openbao_server.key"
}
Le fichier tls_key_file est celui généré via openSSL pour nos nodes Openbao.
Mais ... ⚠️ le fichier openbao_server_full.crt doit également contenir le certificat de l'intermédiaire en seconde position (CA_INFRAMINDS_LABS_inter.crt) pour valider la chaîne complète.
-----BEGIN CERTIFICATE-----
<openbao_server>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<CA_INFRAMINDS_LABS_inter>
-----END CERTIFICATE-----
Une fois les modifications effectuées sur tout les nodes vous pouvez démarrer le service sur ceux-ci
systemctl start openbao
Initialisation d'OpenBao
Sur un des nodes (qui deviendra notre premier master) lancer la commande d'initialisation (nous choisirons le node 1).
bao operator init -address https://192.168.100.2:8200
Cette commande va vous retourner:
- Un token root pour les premières opérations sur le service
- 5 Unseal key (cf: Shamir's Secret Sharing)
Sur chaque noeud il va falloir lancer la commande de unseal 3 fois avec à chaque fois 1 clé différente par itération (vous pouvez utiliser les 3 premières sur tous les noeuds).
# Sur le node 1
bao operator unseal -address https://192.168.100.2:8200
# Sur le node 2
bao operator unseal -address https://192.168.100.3:8200
# Sur le node 3
bao operator unseal -address https://192.168.100.4:8200
Normalement, à cette étape, votre cluster OpenBao est prêt. 🎉
Attention: A chaque redémarrage d'un service OpenBao il vous faudra le unseal.
Status d'OpenBao
Pour voir le statut du cluster raft:
BAO_TOKEN=<TOKEN_ROOT> bao operator raft list-peers -address https://192.168.100.2:8200
Node Address State Voter
---- ------- ----- -----
node1 192.168.100.2:8201 leader true
node2 192.168.100.3:8201 follower true
node3 192.168.100.4:8201 follower true
Pour voir le statut du cluster OpenBao:
BAO_TOKEN=<TOKEN_ROOT> bao operator members -address https://192.168.100.4:8200
Host Name API Address Cluster Address Active Node Version Upgrade Version Last Echo
--------- ----------- --------------- ----------- ------- --------------- ---------
inframinds-lab-openbao01 https://192.168.100.2:8200 https://192.168.100.2:8201 false 2.0.1 2.0.1 2024-10-17T13:27:51Z
inframinds-lab-openbao02 https://192.168.100.3:8200 https://192.168.100.3:8201 true 2.0.1 2.0.1 n/a
inframinds-lab-openbao03 https://192.168.100.4:8200 https://192.168.100.4:8201 false 2.0.1 2.0.1 2024-10-17T13:27:51Z
Pour voir si un noeud est seal (on voit bien le sealed a false), vous pouvez faire cette commande sur chaque node:
bao status -address https://192.168.100.4:8200
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 5
Threshold 3
Version 2.0.1
Build Date 2024-09-03T19:57:34Z
Storage Type raft
Cluster Name lab-inframinds
Cluster ID 1ddfc488-87dc-a3e4-b348-afddee90743f
HA Enabled true
HA Cluster https://192.168.100.3:8201
HA Mode standby
Active Node Address https://192.168.100.3:8200
Raft Committed Index 172
Raft Applied Index 172
Création de notre PKI intermédiaire dans OpenBao
Création de la PKI
Comme lors de la création de la Génération du CA intermédiaire pour l'infrastructure OpenBao, nous allons créer une CA intermédiaire pour les clients OpenBao.
Nous allons commencer par créer une PKI dans Openbao (sur 1 des 3 nodes)
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao secrets enable -path=labs_pki pki
#Success! Enabled the pki secrets engine at: labs_pki/
Ensuite, nous allons générer le CSR (demande de signature) et la clé de cette nouvelle PKI Openbao
# On install JQ pour parser le retour de la commande openbao
apt install jq
export BAO_ADDR=https://192.168.100.2
export BAO_TOKEN=<ROOT TOKEN>
bao write -format=json labs_pki/intermediate/generate/internal common_name="Bao Intermediate Authority" country="FR" state="IDF" locality="Paris" organization="Inframinds" ttl=43800h | jq -r '.data.csr' > pki_intermediate.csr
On va signer cette CSR avec notre PKI ROOT offline.
Avant de signer cela, il faut modifier les extensions openssl pour avoir les bons attribue lors de la signature. (/etc/ssl/openssl.cnf)
[ v3_ca ]
# Uncomment
keyUsage = cRLSign, keyCertSign, digitalSignature
Une fois cela fait, on peut signer notre CSR
openssl x509 -req -in pki_intermediate.csr -text -sha256 -days 1825 -CA CA_INFRAMINDS_LABS/CA_INFRAMINDS_LABS.crt -CAkey CA_INFRAMINDS_LABS/CA_INFRAMINDS_LABS.key -CAcreateserial -out bao_inter.crt -extensions v3_ca -extfile /etc/ssl/openssl.cnf
Cela nous génère un certificat nommé bao_inter.crt que nous allons importer dans le service OpenBao
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao write labs_pki/intermediate/set-signed certificate=@bao_inter.crt
# Output
WARNING! The following warnings were returned from Vault:
* This mount hasn't configured any authority information access (AIA)
fields; this may make it harder for systems to find missing certificates
in the chain or to validate revocation status of certificates. Consider
updating /config/urls or the newly generated issuer with this information.
Key Value
--- -----
existing_issuers <nil>
existing_keys <nil>
imported_issuers [943ca24e-9c9a-e263-8e6b-7ed394081f58]
imported_keys <nil>
mapping map[943ca24e-9c9a-e263-8e6b-7ed394081f58:a37db397-7d14-e57d-a751-c0b5a8145cdc]
Test de la PKI Openbao
Afin de pouvoir générer des certificats depuis cette nouvelle PKi, il faut commencer par créer un role (permission sur la PKI)
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao write labs_pki/roles/test \
allowed_domains=labs.inframinds.fr \
allow_subdomains=true max_ttl=8760h
Ici, nous autorisons uniquement les sous-domaine liés *.labs.inframinds.fr dans un rôle nommé test
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao write labs_pki/roles/test \
allowed_domains=labs.inframinds.fr \
allow_subdomains=true max_ttl=8760h
Voici un example:
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao write labs_pki/issue/test \
common_name=node1.labs.inframinds.fr
La commande retourne:
- private_key: Une clé privée générée
- issuing_ca: Le certificat intermédiaire ayant signé votre certificat
- certificate: Le certificat
- ca_chain: Les certificats de la chaîne (actuellement, il doit y avoir uniquement le même que le issuing_ca)
⚠️ Il faut penser à ajouter le certificat intermédiaire à la suite du certificat lors de son utilisation.
Si l'on essaie un autre domaine, nous avons une erreur ❌
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao write labs_pki/issue/test \
common_name=blah.example.com
Error writing data to labs_pki/issue/test: Error making API request.
URL: PUT https://192.168.100.5/v1/labs_pki/issue/test
Code: 400. Errors:
* common name blah.example.com not allowed by this role
HAproxy
Pour finir notre architecture cible 🎯, nous devons mettre en place un HAproxy qui servira de proxy et Loadbalancing entre nos noeuds OpenBao et nos clients.
Nous allons donc prendre un nouveau serveur qui sera à la fois sur le réseau d'OpenBao et à la fois joignable par les clients.
Prérequis
Avant de commencer, il faut installer notre Root CA sur ce serveur
# A executer sur tout les nodes
cp CA_INFRAMINDS_LABS.crt /usr/local/share/ca-certificates/
update-ca-certificates
Il faut aussi lui générer un certificat qui sera utilisé pour la communication avec les clients. Soit avec un domaine soit avec une IP (ou les deux 😄). Si on fait un domaine, il faudra bien sûr faire un enregistrement DNS.
Sur le node01 openbao.
export BAO_ADDR=https://192.168.100.2:8200
export BAO_TOKEN=<ROOT TOKEN>
bao write labs_pki/issue/test \
common_name="openbao.labs.inframinds.fr", ip_sans="10.0.0.5"
Créer un fichier sur le serveur HAproxy /etc/haproxy/certs/haproxy.pem (Si le dossier certs n'existe pas vous pouvez le créer.
Ce fichier doit contenir dans l'ordre:
- Le certificat
- Le certificat intermediaire (issuing_ca)
- La clé privée
-----BEGIN CERTIFICATE-----
<Certificat openbao.labs.inframinds.fr>
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
<Certificat intermédiaire Openbao PKI(pas celui de l'infra!>
-----END CERTIFICATE-----
-----BEGIN RSA PRIVATE KEY-----
<Clé privé openbao.labs.inframinds.fr>
-----END RSA PRIVATE KEY-----
Installation
Pour installer HAproxy, suivre les instructions officielle sur: https://haproxy.debian.net/#distribution=Debian&release=bookworm&version=3.0
Configuration
La configuration de HAproxy se trouve ici: /etc/haproxy/haproxy.cfg
global
log /dev/log local0
log /dev/log local1 notice
chroot /var/lib/haproxy
stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
user haproxy
group haproxy
daemon
# Default SSL material locations
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
frontend openbao
bind 10.0.0.5:80 # IP atteignable par les clients
bind 10.0.0.5:443 ssl crt /etc/haproxy/certs/haproxy.pem # Certificat généré dans les prérequis
http-request redirect scheme https unless { ssl_fc } # redirection SSL
mode http
default_backend openbao_back
backend openbao_back
mode http
option httpchk HEAD /v1/sys/health?standbyok=true # Controle de l'état des membres
option forwardfor
http-check expect status 200
server node1 192.168.100.2:8200 check ssl ca-file /etc/ssl/certs/ca-certificates.crt # Définition des backends en SSL avec le CA généré par ca-certificates
server node2 192.168.100.3:8200 check ssl ca-file /etc/ssl/certs/ca-certificates.crt
server node3 192.168.100.4:8200 check ssl ca-file /etc/ssl/certs/ca-certificates.crt
Vous pouvez maintenant activer et démarrer HAproxy
systemctl enable haproxy
systemctl start haproxy
Les logs sont disponible /var/log/haproxy.log
Vous pouvez voir l'état des backend via la commande:
echo "show stat" | sudo nc -U /run/haproxy/admin.sock | cut -d "," -f 1,2,8-10,18 | column -s, -t
# pxname svname stot bin bout status
openbao FRONTEND 0 0 0 OPEN
openbao_back node1 0 0 0 UP
openbao_back node2 0 0 0 UP
openbao_back node3 0 0 0 UP
openbao_back BACKEND 0 0 0 UP
Test
Depuis un client qui est sur le même réseau 10.0.0.0/24, essayer d'atteindre le service Openbao pour voir si ça fonctionne bien 😄
# On met la nouvelle adresse (celle du LB)
export BAO_ADDR=https://openbao.labs.inframinds.fr
export BAO_TOKEN=<ROOT TOKEN>
# On repete cette commande 3 fois
bao write labs_pki/issue/test common_name="test.labs.inframinds.fr"
bao write labs_pki/issue/test common_name="test.labs.inframinds.fr"
bao write labs_pki/issue/test common_name="test.labs.inframinds.fr"
Les requêtes doivent passer correctement ✅ , et l'on peut vérifier la répartition de charge sur HAproxy via la commande précédente.
Test sur la fonction KV
Une des autres fonctionnalités utiles d'Openbao est le KV (key/value). Cela permet de stocker de donnée de manière chiffrée (sur le disque) qui sont accessible, versionnable, et peuvent être restreinte par des rôles et policies. Vu que nous sommes dans un Lab, nous allons utiliser le token root pour faire les test de notre infrastructure (le sujet de l'article n'est pas comment utiliser OpenBao 😛)
Création du KV
export BAO_ADDR=https://openbao.labs.inframinds.fr
export BAO_TOKEN=<ROOT TOKEN>
bao secrets enable -path=lab_kv kv-v2
# Output
Success! Enabled the kv-v2 secrets engine at: lab_kv/
Pour écrire un secret:
# lab_kv est notre path et web-app notre objet pouvant contenir plusieur key/value
bao kv put lab_kv/web-app api-token="test"
Pour les lire via la CLI
bao kv get lab_kv/web-app
Sauvegarde & Restauration
Sauvegarde
Comme nous utilisons le système de stockage natif de Bao (raft) et que celui-ci est partagé/répliqué en se basant sur le master, nous devons réaliser la sauvegarde sur le node leader (⚠️ Sans passer par le Loadbalancer ! ). (CF: https://developer.hashicorp.com/vault/tutorials/standard-procedures/sop-backup )
# On met l'adresse du leader
export BAO_ADDR=https://192.168.100.3:8200
export BAO_TOKEN=<ROOT TOKEN>
bao operator raft snapshot save /opt/backups/bao-$(date +%F_%R).snap
Restauration
Sur un cluster fraîchement initié, vide, vous avez des nouvelles unseal key et un nouveau token root.
Ceux-ci vont être remplacés par les anciens lors de l'importation du snapshot.
bao operator raft snapshot restore -force backup.snap
L'option -force permet de prendre en compte le fait que les unseal key ne sont pas les mêmes. Une fois fait, il faudra unseal chaque node avec les "anciennes" unseal key.
bao operator unseal [unseal_key]