Hermes - Partie 03 : Vault et NGINX, version durcie

En bref
  • Certificats émis par la PKI interne (AD CS) avec SAN pour prdlinage501.coolcorp.priv (Vault) et l'alias roger.coolcorp.priv (dashboard), CA racine ajoutée au magasin de confiance.
  • Vault 2.2.1 mono-nœud (Raft, TLS, swap désactivée), port 8200 filtré par firewalld, 5 clés de descellement (seuil 3) et journal d'audit activé.
  • Compte d'administration nominatif en userpass, token root révoqué, policy hermes-roger-read et AppRole avec TTL et bound_cidrs limités à la VM.
  • NGINX en frontal TLS du dashboard Hermes (127.0.0.1:9119), sans auth_basic, WebSockets, SELinux et redirection HTTP vers HTTPS.

Introduction

Dans la partie précédente, on a préparé la VM sous Rocky Linux 10 prdlinage501 : paquets, compte de service hermes, arborescence, SELinux, Podman rootless avec le mapping keep-id. On a également fait de premiers tests avec l'image conteneur d'Hermes. Sous proposition de l’IA, on a initié un dépôt Git pour versionner les configurations et sécuriser les éléments au fur et à mesure qu’on avance dans la réalisation.

Place maintenant aux deux briques qui tournent directement sur l'hôte : Vault, le coffre-fort qui stockera tous les secrets de Roger, et NGINX, le frontal HTTPS du dashboard d'Hermes.

Pour Vault, je reprends la logique de la partie 03 de la série OpenClaw, mais en corrigeant les points faibles que j'avais moi-même relevés : un accès AppRole limité à l'adresse de la VM, une durée de vie pour les identifiants, un journal d'audit, un vrai compte d'administration plutôt que le token root, et un port 8200 qui n'est plus ouvert à tout le LAN. N’hésitez pas à faire un tour sur l’ancien article sur OpenClaw pour en apprendre davantage sur Vault.

Côté NGINX, le changement est plus radical : il ne s'occupe plus de l'authentification, c'est Hermes qui s'en charge.

Avant tout cela, il nous faut des certificats et c'est par là que l'on commence.

Les certificats

Pourquoi ne pas se contenter de certificats auto-signés ? Parce qu'un certificat auto-signé oblige chaque client à désactiver la vérification ou à faire confiance à chaque certificat individuellement. Dans le premier cas, on perd toute protection contre l'interception. Dans le second, on multiplie les exceptions. Avec une PKI interne, il suffit de faire confiance une seule fois à l'autorité racine, sur chaque machine et dans chaque image, et tous les certificats qu'elle émet sont reconnus. Comme j'ai déjà une PKI Windows (AD CS) dans mon lab, autant s'en servir, comme j’ai toujours eu l’habitude de le faire.

Les entrées DNS

Deux noms vont pointer vers la VM : son nom d'hôte, prdlinage501.coolcorp.priv, qui existe déjà dans mon DNS depuis la série OpenClaw, et le nom du dashboard de Roger, roger.coolcorp.priv. Pour ce dernier, je crée un alias (enregistrement de type CNAME) sur mon serveur DNS Active Directory, qui renvoie vers prdlinage501.coolcorp.priv. L'avantage : si l'adresse de la VM change un jour, il n'y aura qu'un seul enregistrement à modifier…un classique.

Console DNS Windows, zone coolcorp.priv, création de l'alias CNAME roger vers prdlinage501.coolcorp.priv

Cliquez sur l'image pour l'agrandir.

On vérifie depuis la VM :

dig +short prdlinage501.coolcorp.priv
dig +short roger.coolcorp.priv

La première commande renvoie l'adresse de la VM. La seconde affiche d'abord la cible de l'alias, puis la même adresse.

Résolution de prdlinage501.coolcorp.priv et de l'alias roger.coolcorp.priv avec dig

Cliquez sur l'image pour l'agrandir.

La CA racine dans le magasin de confiance

Tous les certificats de la série sont émis par ma PKI interne (AD CS). Pour que la VM leur fasse confiance, on commence par y installer le certificat de l'autorité racine.

Pas besoin d'aller le chercher sur le serveur de certification : je l'ai récupéré directement depuis l'un de mes sites qui exploite déjà un certificat émis par ma PKI. Le fichier se nomme CA-COOLCORP.crt et il est au format texte (PEM, encodé en Base64), donc aucune conversion n'est nécessaire. Je le dépose dans un dossier ~/pki de mon compte d'administration (bvivi57), par un simple glisser-déposer dans le navigateur de fichiers de MobaXterm (scp ou WinSCP font aussi bien l'affaire).

mkdir -p ~/pki && cd ~/pki
ls
cat CA-COOLCORP.crt

Le contenu doit commencer par -----BEGIN CERTIFICATE-----. Si vous obtenez des caractères illisibles, votre fichier est au format binaire (DER) : convertissez-le avec openssl x509 -inform der -in <fichier_in> -out <fichier_out>.crt.

Début du contenu PEM du certificat CA-COOLCORP.crt

Cliquez sur l'image pour l'agrandir.

On vérifie qu'il s'agit bien de l'autorité racine (le sujet et l'émetteur doivent être identiques), puis on l'ajoute au magasin de confiance de Rocky Linux :

openssl x509 -in CA-COOLCORP.crt -noout -subject -issuer -enddate
sudo cp CA-COOLCORP.crt /etc/pki/ca-trust/source/anchors/CA-COOLCORP.crt
sudo update-ca-trust
trust list | grep -i -A2 coolcorp
Vérification du sujet et de l'émetteur de la CA racine puis ajout au magasin de confiance

Cliquez sur l'image pour l'agrandir.

On en dépose aussi une copie pour les conteneurs du compte hermes. Elle servira au sidecar Vault Agent, aux tests et à la construction de l'image de Roger :

sudo install -o hermes -g hermes -m 644 ~/pki/CA-COOLCORP.crt /var/lib/hermes/vault/tls/ca.crt
sudo restorecon -v /var/lib/hermes/vault/tls/ca.crt
Copie de la CA dans /var/lib/hermes/vault/tls/ca.crt pour les conteneurs

Cliquez sur l'image pour l'agrandir.

Si votre PKI comporte une autorité intermédiaire (ce qui normalement le cas en entreprise), il faudra faire de même avec son certificat et, plus loin, concaténer le certificat serveur et l'intermédiaire dans le fichier .crt utilisé par Vault et NGINX.

Les demandes de certificat

Il nous faut deux certificats serveur : un pour Vault (prdlinage501.coolcorp.priv) et un pour NGINX (roger.coolcorp.priv). Les navigateurs et les outils modernes ne regardent plus le CN, uniquement l'extension « Subject Alternative Name » (SAN). On prépare donc la demande avec un petit fichier de configuration OpenSSL qui inclut les SAN.

Pour Vault :

cd ~/pki
cat > prdlinage501.cnf <<'EOF'
[req]
default_bits       = 4096
prompt             = no
distinguished_name = dn
req_extensions     = ext

[dn]
CN = prdlinage501.coolcorp.priv
O  = COOLCORP
C  = FR

[ext]
subjectAltName = DNS:prdlinage501.coolcorp.priv, DNS:prdlinage501, IP:192.168.10.41
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
EOF

openssl req -new -newkey rsa:4096 -nodes -config prdlinage501.cnf \
  -keyout prdlinage501.key -out prdlinage501.csr
openssl req -in prdlinage501.csr -noout -text | grep -A1 "Subject Alternative Name"
Fichier de configuration OpenSSL prdlinage501.cnf avec les SAN Génération de la clé et de la demande de certificat prdlinage501.csr et contrôle des SAN

Cliquez sur l'image pour l'agrandir.

Et pour le dashboard de Roger, on garde la même conf mais on remplace juste le nom associé au certificat (l’IA nous donne la petit commande sed qui va bien) :

sed -e 's/prdlinage501.coolcorp.priv/roger.coolcorp.priv/' \
    -e 's/^subjectAltName = .*/subjectAltName = DNS:roger.coolcorp.priv/' \
    prdlinage501.cnf > roger.cnf
openssl req -new -newkey rsa:4096 -nodes -config roger.cnf \
  -keyout roger.key -out roger.csr
openssl req -in roger.csr -noout -text | grep -A1 "Subject Alternative Name"
chmod 600 ~/pki/*.key
Génération de la demande de certificat roger.csr et contrôle du SAN roger.coolcorp.priv Restriction des droits des clés privées en 600

Cliquez sur l'image pour l'agrandir.

Émission depuis AD CS

On copie les deux fichiers .csr sur un poste Windows qui a accès à l'autorité (perso j’utilise le serveur de la CA elle-même, mais on évite dans la vrai vie), et on les soumet avec certreq en précisant le modèle de certificat. J'utilise mon modèle de serveur web habituel, TPL-SRV-WEB-DEFAULT, configuré pour accepter le nom fourni dans la demande.

C'est indispensable : si le modèle construit le sujet à partir d'Active Directory, les SAN de la demande sont ignorés.

Copie des fichiers CSR vers le serveur de l'autorité de certification

Cliquez sur l'image pour l'agrandir.

certreq -submit -attrib "CertificateTemplate:TPL-SRV-WEB-DEFAULT" prdlinage501.csr prdlinage501.cer
certreq -submit -attrib "CertificateTemplate:TPL-SRV-WEB-DEFAULT" roger.csr roger.cer

certreq demande de choisir l'autorité, puis enregistre les certificats émis. Attention, l'attribut attend le nom du modèle, pas son nom complet affiché dans la console.

Soumission des demandes avec certreq et choix de l'autorité de certification CA-COOLCORP

Cliquez sur l'image pour l'agrandir.

Je ne vais pas rentrer davantage dans le fonctionnement des certificats avec un écosystème Microsoft AD CS, ce n’est pas le but de l’article. Si vous avez d’autres mécanismes de PKI, vous pouvez tout aussi bien les utiliser, l’important reste d’avoir des certificats reconnu sur votre architecture.

De retour sur la VM, on vérifie les deux certificats. certreq produit en général du Base64 (PEM):

cd ~/pki
for c in prdlinage501 roger; do
  openssl x509 -in $c.cer -out $c.crt
  openssl x509 -in $c.crt -noout -subject -issuer -enddate -ext subjectAltName
  openssl verify -CAfile CA-COOLCORP.crt $c.crt
done
Les certificats émis prdlinage501.cer et roger.cer de retour sur la VM Conversion et vérification des deux certificats avec openssl verify

Cliquez sur l'image pour l'agrandir.

On attend deux OK pour openssl verify.

Vault

Rappel : à quoi sert Vault

Pour ceux qui découvrent la série, un petit rappel. Vault, édité par HashiCorp, est un coffre-fort à secrets. Plutôt que de laisser des mots de passe, des tokens et des clés d'API dans des fichiers de configuration, on les dépose dans Vault, qui les chiffre et ne les remet qu'à ceux qui ont le droit de les lire.

J'aime bien l'analogie du coffre de banque. La salle des coffres est fermée (Vault est « scellé ») tant que plusieurs détenteurs de clés ne sont pas venus l'ouvrir ensemble. Chaque client a son propre casier (un chemin dans le moteur KV(Key/Value)) et une carte d'accès qui ne donne accès qu'à celui-ci (une policy). Et le registre à l'entrée note chaque passage (le journal d'audit).

Pour Roger, on utilisera trois mécanismes :

  • le moteur KV en version 2, qui stocke des paires clé/valeur et conserve l'historique des versions de chaque secret : une erreur de saisie se rattrape.
  • une policy, qui décrit précisément les chemins qu'une identité a le droit de lire.
  • AppRole, une méthode d'authentification pensée pour les machines. Le role_id joue le rôle d'un identifiant, le secret_id celui d'un mot de passe, et les deux ensemble permettent d'obtenir un token temporaire.

C'est le sidecar Vault Agent, que l'on démarrera dans la partie 06, qui utilisera cet AppRole. Roger lui-même ne parlera jamais directement à Vault.

Schéma d'intégration avec Vault : le sidecar vault-agent-roger présente le role_id et le secret_id, Vault vérifie le secret_id et l'IP source puis retourne un token d'une heure, le sidecar demande les secrets avec ce token, Vault vérifie le token et la policy hermes-roger-read, journalise la requête et renvoie la valeur, écrite dans le fichier .env lu par Roger

Cliquez sur l'image pour l'agrandir.

Encore une fois n’hésitez pas à lire mon la partie 03 de mes articles sur OpenClaw ou j’explique davantage les principes derrières vault.

Installation

Vault s'installe depuis le dépôt officiel de HashiCorp. Je fige la version 2.2.1, la plus récente au moment de l'écriture (la série OpenClaw utilisait la 2.0.1) :

sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y vault-2.2.1
vault version
Ajout du dépôt HashiCorp et installation de Vault 2.2.1 Fin de l'installation de Vault et génération du certificat auto-signé dans /opt/vault/tls Résultat de la commande vault version : Vault v2.2.1

Cliquez sur l'image pour l'agrandir.

Le paquet crée l'utilisateur vault, le service systemd et les dossiers /etc/vault.d et /opt/vault. Il génère aussi un certificat auto-signé (tls.crt et tls.key) dans /opt/vault/tls. On ne s'en servira pas : on dépose juste à côté le certificat émis par notre PKI.

On installe les fichiers TLS et on prépare le dossier du journal d'audit :

sudo install -o root -g vault -m 644 ~/pki/prdlinage501.crt /opt/vault/tls/vault.crt
sudo install -o root -g vault -m 640 ~/pki/prdlinage501.key /opt/vault/tls/vault.key
sudo mkdir -p /opt/vault/logs && sudo chown vault:vault /opt/vault/logs && sudo chmod 750 /opt/vault/logs
sudo ls -l /opt/vault/ /opt/vault/tls/

On doit retrouver vault.crt et vault.key dans /opt/vault/tls, ainsi que le dossier logs. Ne passez pas cette vérification : sans ces fichiers, Vault refusera de démarrer avec l'erreur error loading TLS cert: open /opt/vault/tls/vault.crt: no such file or directory.

Erreur error loading TLS cert dans le journal de Vault lorsque vault.crt est absent de /opt/vault/tls Contenu de /opt/vault et /opt/vault/tls avec vault.crt, vault.key et le dossier logs

Cliquez sur l'image pour l'agrandir.

Configuration

On remplace le fichier de configuration par défaut. On passe sur une configuration à un seul node. Vault étant prévu pour s’exécuter en cluster via des mécanisme de résilience (protocole Raft), dans notre cas il faut partir sur un seul serveur, en écoute TLS sur le port 8200, avec l’interface web activée :

sudo tee /etc/vault.d/vault.hcl >/dev/null <<'EOF'
# /etc/vault.d/vault.hcl - Vault 2.2.1 mono-nœud (prdlinage501)
ui            = true
cluster_name  = "coolcorp-vault"
# Recommandé avec le stockage intégré ; la swap est désactivée sur la VM
disable_mlock = true

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "prdlinage501"
}

listener "tcp" {
  address         = "0.0.0.0:8200"
  tls_cert_file   = "/opt/vault/tls/vault.crt"
  tls_key_file    = "/opt/vault/tls/vault.key"
  tls_min_version = "tls12"
}

api_addr     = "https://prdlinage501.coolcorp.priv:8200"
cluster_addr = "https://prdlinage501.coolcorp.priv:8201"
EOF
sudo chown root:vault /etc/vault.d/vault.hcl && sudo chmod 640 /etc/vault.d/vault.hcl
Écriture du fichier /etc/vault.d/vault.hcl mono-nœud avec stockage Raft et listener TLS Droits root:vault et 640 sur vault.hcl

Cliquez sur l'image pour l'agrandir.

Comme dans la série OpenClaw, on désactive la swap : avec disable_mlock, Vault ne peut plus empêcher le système d'écrire sa mémoire, et donc des secrets, sur le disque.

sudo swapoff -a
sudo sed -i '/\sswap\s/ s/^/#/' /etc/fstab
free -h | grep -i swap
Désactivation de la swap et contrôle avec free -h

Cliquez sur l'image pour l'agrandir.

Pour revenir sur la configuration à un seul node, en entreprise il est plutôt conseillé d’avoir plusieurs instance vault qui peuvent se secourir les unes les autres en cas d’incident. Attention néanmoins, cela implique un maintient en condition opérationnelle plus important, il y a des conditions à remplir pour que la résilience fonctionne correctement à commencer par un minimum de trois installations. C’est pourquoi, dans mon cas, je reste sur une seule instance.

Un port 8200 moins ouvert

Dans la série précédente avec OpenClaw, le port 8200 était ouvert à tout le LAN. Pourtant, ses seuls clients sont le sidecar Vault Agent, qui tourne sur la VM elle-même, et mon poste d'administration pour l'interface web. On restreint donc l'accès avec deux règles firewalld, en remplaçant <IP_POSTE_ADMIN> par l'adresse du poste ayant à accès à la GUI :

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.41/32" port port="8200" protocol="tcp" accept'
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="<IP_POSTE_ADMIN>/32" port port="8200" protocol="tcp" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rules
Ajout des règles firewalld limitant le port 8200 à la VM et au poste d'administration Liste des rich rules firewalld sur le port 8200

Cliquez sur l'image pour l'agrandir.

Petite incertitude à signaler : l'adresse source sous laquelle Vault verra les requêtes des conteneurs rootless dépend du mode réseau de Podman. Je m'attends à ce qu'il s'agisse de l'adresse de la VM, mais on le vérifiera formellement dans la partie 06 grâce au journal d'audit. Si ce n'est pas le cas, on élargira la règle.

Démarrage, initialisation et descellement

sudo systemctl enable --now vault
echo 'export VAULT_ADDR=https://prdlinage501.coolcorp.priv:8200' >> ~/.bashrc
source ~/.bashrc
vault status
Service vault actif et démarré avec systemctl status vault status : Vault non initialisé et scellé

Cliquez sur l'image pour l'agrandir.

On initialise Vault avec cinq clés de descellement, dont trois nécessaires pour l'ouvrir :

vault operator init -key-shares=5 -key-threshold=3
Attention, cette sortie est ultra critique : elle contient les cinq clés de descellement et le token root initial. Rangez-les dans votre gestionnaire de mots de passe, idéalement à des endroits différents, et jamais sur la VM. Sans trois de ces clés, vos secrets sont perdus. Point.
vault operator init avec les clés de descellement et le token root masqués

Cliquez sur l'image pour l'agrandir.

Pourquoi cinq clés, dont trois nécessaires ? Vault chiffre toutes ses données avec une clé maîtresse, qui n'est jamais stockée en clair. Cette clé est découpée en cinq morceaux selon l'algorithme de Shamir, et trois morceaux suffisent à la reconstituer. En entreprise, on confie chaque morceau à une personne différente : aucune personne seule ne peut ouvrir le coffre, et la perte d'une ou deux clés n'est pas fatale. Dans un homelab, je suis le seul détenteur.

On descelle ensuite Vault en saisissant trois des cinq clés, une par commande :

vault operator unseal
vault operator unseal
vault operator unseal
vault status | grep -E 'Sealed|Storage Type|HA Enabled'

On attend Sealed false et Storage Type raft.

Descellement de Vault en trois étapes avec vault operator unseal

Cliquez sur l'image pour l'agrandir.

Vault ne se descelle pas tout seul. À chaque redémarrage de la VM, il faudra refaire cette opération, faute de quoi Roger n'aura plus accès à ses secrets. Il existe des mécanismes de descellement automatique (« auto-unseal »), mais ils reposent sur un service externe : un KMS cloud, un HSM ou une seconde instance Vault. Pour un homelab, je reste sur le descellement manuel, et on mettra en place dans la partie 07 une alerte Telegram si Vault est scellé.

Activation des fonctionnalités

On se connecte temporairement avec le token root et on active le moteur de secrets KV en version 2, les méthodes d'authentification AppRole (pour les agents) et userpass (pour moi), ainsi que le journal d'audit :

vault login            # saisir le token root
vault secrets enable -path=secret -version=2 kv
vault auth enable approle
vault auth enable userpass
vault audit enable file file_path=/opt/vault/logs/audit.log
vault secrets list && vault auth list && vault audit list
Connexion temporaire avec le token root (masqué) Activation du moteur KV v2, des méthodes approle et userpass et du journal d'audit

Cliquez sur l'image pour l'agrandir.

Le journal d'audit est une nouveauté par rapport à la série OpenClaw. Chaque requête faite à Vault y est consignée, avec l'identité et l'adresse source, les valeurs sensibles étant hachées. C'est lui qui nous dira qui a lu quoi, et quand.

Chaque ligne du fichier est un document JSON. On y trouve notamment le type d'événement (requête ou réponse), le chemin demandé, l'identité authentifiée (par exemple approle pour le sidecar de Roger) et l'adresse IP d'origine. Les valeurs des secrets n'apparaissent jamais en clair : Vault les remplace par une empreinte. On s'en servira dans la partie 06 pour vérifier l'adresse sous laquelle Vault voit les conteneurs, et dans la partie 07 pour l'audit.

Une précaution : ce journal grossit avec le temps. Sur un homelab, le volume reste modeste, mais pensez à prévoir une rotation avec logrotate si vous laissez tourner la plateforme plusieurs mois. Attention, si Vault ne peut plus écrire dans son unique journal d'audit, par exemple parce que le disque est plein, il refuse toutes les requêtes. C'est voulu : pas de trace, pas d'accès.

Un compte d'administration plutôt que le token root

Dans la série précédente, je travaillais avec le token root du début à la fin. C'est pratique, mais c'est exactement ce qu'il ne faut pas faire. On crée donc une policy d'administration et un utilisateur vault bvivi57. Les policies sont versionnées dans le dépôt Git (cloné dans la partie 02) :

cd ~/roger.coolcorp.priv
cat > vault/policies/admin-coolcorp.hcl <<'EOF'
# Administration complète de Vault (compte humain uniquement)
path "*" {
  capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
EOF
vault policy write admin-coolcorp vault/policies/admin-coolcorp.hcl
Création et chargement de la policy admin-coolcorp

Cliquez sur l'image pour l'agrandir.

set +o history
read -rsp "Mot de passe Vault pour bvivi57 : " VPASS && echo
vault write auth/userpass/users/bvivi57 password="$VPASS" policies="admin-coolcorp" token_ttl=8h token_max_ttl=24h
unset VPASS
set -o history
Saisie masquée du mot de passe Vault de bvivi57 Création de l'utilisateur userpass bvivi57 avec la policy admin-coolcorp Suppression de la variable VPASS et réactivation de l'historique

Cliquez sur l'image pour l'agrandir.

La commande set +o history évite que le mot de passe se retrouve dans l'historique du shell. On reprendra ce réflexe à chaque manipulation de secret.

On bascule sur ce nouveau compte, puis on révoque le token root :

read -rsp "Token root : " ROOT_TOKEN && echo
vault login -method=userpass username=bvivi57
vault token revoke "$ROOT_TOKEN"
unset ROOT_TOKEN
vault token lookup | grep -E 'display_name|policies'
Connexion à Vault avec le compte userpass bvivi57 (token masqué) Révocation du token root et contrôle de l'identité courante

Cliquez sur l'image pour l'agrandir.

Pas d'inquiétude : en cas de besoin, un nouveau token root peut être régénéré avec les clés de descellement (vault operator generate-root).

La policy et l'AppRole de Roger

On arrive à la partie qui concerne directement Roger. Tous ses secrets vivront sous secret/hermes/roger/. Sa policy lui donne le droit de les lire, et rien d'autre :

cat > vault/policies/hermes-roger-read.hcl <<'EOF'
# Lecture seule sur la branche de Roger (toutes ses futures dépendances y vivent)
path "secret/data/hermes/roger/*" {
  capabilities = ["read"]
}
path "secret/metadata/hermes/roger/*" {
  capabilities = ["list", "read"]
}
# Renouvellement et consultation de son propre token par Vault Agent
path "auth/token/renew-self" {
  capabilities = ["update"]
}
path "auth/token/lookup-self" {
  capabilities = ["read"]
}
EOF
vault policy write hermes-roger-read vault/policies/hermes-roger-read.hcl
Création de la policy hermes-roger-read en lecture seule Chargement de la policy hermes-roger-read dans Vault

Cliquez sur l'image pour l'agrandir.

Puis le rôle AppRole, utilisé par le sidecar Vault Agent pour s'authentifier. C'est ici que se trouve l'essentiel du durcissement par rapport à OpenClaw :

vault write auth/approle/role/hermes-roger \
  token_policies="hermes-roger-read" \
  token_ttl=1h \
  token_max_ttl=24h \
  secret_id_ttl=8760h \
  secret_id_num_uses=0 \
  secret_id_bound_cidrs="192.168.10.41/32,127.0.0.1/32" \
  token_bound_cidrs="192.168.10.41/32,127.0.0.1/32"
vault read auth/approle/role/hermes-roger

Quelques explications :

  • token_ttl et token_max_ttl : le token obtenu par l'agent vit une heure et ne peut pas être prolongé au-delà de 24 heures ; Vault Agent se réauthentifie automatiquement.
  • secret_id_ttl=8760h : l'identifiant secret expire au bout d'un an, alors qu'il était éternel dans la série OpenClaw ; on verra son renouvellement dans la partie 07.
  • secret_id_bound_cidrs et token_bound_cidrs : le secret_id et les tokens ne sont utilisables que depuis la VM. Volé, le couple role_id/secret_id ne servirait à rien depuis une autre machine.
vault read auth/approle/role/hermes-roger montrant les TTL et les bound_cidrs

Cliquez sur l'image pour l'agrandir.

Pour aller plus loin, Vault permet un niveau de protection supérieur : un secret_id à usage unique (secret_id_num_uses=1), transmis « emballé » (response wrapping) et déballé par l'agent au démarrage. Le secret n'existe alors en clair nulle part sur le disque, ou seulement quelques secondes. Je ne l'ai pas retenu ici : chaque redémarrage du sidecar demanderait de régénérer un secret, ce qui impose une automatisation supplémentaire que je trouve disproportionnée pour un homelab. Mais en entreprise, c'est une piste sérieuse.

Dernier point sur ces durées : elles ont un effet direct sur l'exploitation. Un token d'une heure, c'est un token qui ne vaut plus rien s'il est volé et non utilisé rapidement. Un secret_id d'un an, c'est un rendez-vous annuel à ne pas manquer, faute de quoi le sidecar ne pourra plus s'authentifier et Roger perdra ses secrets. On mettra en place un rappel dans la partie 07.

Le role_id n'est pas un secret en soi : c'est l'équivalent d'un nom d'utilisateur. On le dépose dès maintenant dans l'arborescence de hermes, en 600, puisque grâce à keep-id le conteneur le lira avec l'identité du compte hermes :

vault read -field=role_id auth/approle/role/hermes-roger/role-id | \
  sudo install -o hermes -g hermes -m 600 /dev/stdin /var/lib/hermes/vault/roger-role-id
sudo restorecon -v /var/lib/hermes/vault/roger-role-id
sudo ls -lZ /var/lib/hermes/vault/
Dépôt du role_id dans /var/lib/hermes/vault/roger-role-id en 600 avec le contexte container_file_t

Cliquez sur l'image pour l'agrandir.

Le secret_id, lui, sera généré dans la partie 05, une fois tous les secrets de Roger enregistrés, ce qui permettra de tester la chaîne complète d'un coup.

L'interface web

L'interface de Vault est accessible depuis le poste d'administration sur https://prdlinage501.coolcorp.priv:8200/ui. On s'y connecte avec la méthode « Userpass », le compte bvivi57. Pour l'instant, le moteur secret/ est vide, ce qui est normal.

Connexion à l'interface web de Vault avec la méthode Userpass Moteur secret/ KV version 2 encore vide dans l'interface web de Vault

Cliquez sur l'image pour l'agrandir.

NGINX

Installation et certificat

On passe au frontal. Le principe est le même que dans la série OpenClaw : NGINX termine le TLS sur roger.coolcorp.priv et transmet les requêtes au dashboard d'Hermes, qui écoutera uniquement sur 127.0.0.1:9119. La différence, c'est l'absence d'auth_basic : le dashboard d'Hermes impose sa propre authentification dès qu'on l'expose. Une double authentification n'apporterait qu'une double invite de connexion.

sudo dnf install -y nginx
sudo install -m 644 ~/pki/roger.crt /etc/pki/tls/certs/roger.coolcorp.priv.crt
sudo install -m 600 ~/pki/roger.key /etc/pki/tls/private/roger.coolcorp.priv.key
sudo restorecon -v /etc/pki/tls/certs/roger.coolcorp.priv.crt /etc/pki/tls/private/roger.coolcorp.priv.key
Installation de NGINX 1.26 depuis les dépôts Rocky Linux Installation du certificat et de la clé de roger.coolcorp.priv

Cliquez sur l'image pour l'agrandir.

Configuration

sudo tee /etc/nginx/conf.d/roger.conf >/dev/null <<'EOF'
# /etc/nginx/conf.d/roger.conf - TLS + FQDN devant le dashboard Hermes de Roger.
# L'authentification est celle du dashboard (pas d'auth_basic : éviter la double invite).
server {
    listen 443 ssl;
    http2 on;
    server_name roger.coolcorp.priv;

    ssl_certificate     /etc/pki/tls/certs/roger.coolcorp.priv.crt;
    ssl_certificate_key /etc/pki/tls/private/roger.coolcorp.priv.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Restreindre au LAN d'administration
    allow 192.168.10.0/24;
    deny all;

    location / {
        proxy_pass http://127.0.0.1:9119;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        # Le chat intégré au dashboard utilise des WebSockets
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600;
        proxy_buffering off;
    }
}

server {
    listen 80;
    server_name roger.coolcorp.priv;
    return 301 https://$host$request_uri;
}
EOF
Fichier /etc/nginx/conf.d/roger.conf : TLS, restriction LAN, proxy vers 127.0.0.1:9119 et WebSockets

Cliquez sur l'image pour l'agrandir.

Quelques explications sur cette configuration :

  • la directive allow 192.168.10.0/24 limite l'accès au dashboard à mon réseau local ; tout le reste reçoit une erreur 403 avant même d'atteindre Hermes.
  • les en-têtes X-Forwarded-* et X-Real-IP transmettent à Hermes l'adresse réelle du navigateur et le fait que la connexion d'origine était chiffrée. Le dashboard en a besoin pour construire ses liens et sécuriser ses cookies.
  • les en-têtes Upgrade et Connection permettent le passage des WebSockets, utilisés par le chat intégré au dashboard.
  • le long délai de lecture (proxy_read_timeout) évite que NGINX coupe une conversation pendant que le modèle réfléchit.
  • le second bloc redirige simplement le HTTP vers le HTTPS.

SELinux et firewall

Par défaut, SELinux interdit à NGINX d'ouvrir des connexions réseau vers un port arbitraire. On déclare le port 9119 comme port HTTP et on active le booléen correspondant :

sudo semanage port -a -t http_port_t -p tcp 9119
sudo setsebool -P httpd_can_network_connect 1
sudo semanage port -l | grep -w 9119
Déclaration du port 9119 en http_port_t et activation du booléen httpd_can_network_connect

Cliquez sur l'image pour l'agrandir.

Si semanage port -a répond que le port est déjà défini, remplacez -a par -m.

On ouvre HTTP (pour la redirection) et HTTPS, on valide la configuration et on démarre :

sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
sudo nginx -t
sudo systemctl enable --now nginx
Ouverture HTTP/HTTPS dans firewalld, test de la configuration NGINX et démarrage du service

Cliquez sur l'image pour l'agrandir.

Vérification

Hermes ne tourne pas encore, donc on s'attend à une erreur 502. Mais c'est une bonne erreur : elle prouve que le TLS, le nom et le proxy fonctionnent.

curl -sI https://roger.coolcorp.priv | head -1
curl -sI http://roger.coolcorp.priv | grep -i -E '^HTTP|^location'
Test curl : 502 en HTTPS et redirection 301 depuis HTTP

Cliquez sur l'image pour l'agrandir.

Depuis le navigateur du poste d'administration, la page https://roger.coolcorp.priv doit afficher le cadenas sans avertissement (si la CA racine est bien déployée sur votre poste), avec une page « 502 Bad Gateway ».

Détail du certificat de roger.coolcorp.priv émis par CA-COOLCORP dans le navigateur Page 502 Bad Gateway de NGINX sur https://roger.coolcorp.priv

Cliquez sur l'image pour l'agrandir.

Ce qui a changé par rapport à OpenClaw

Pour ceux qui ont suivi la série précédente, voici le résumé des différences de cet article :

  • Version : Vault 2.2.1 au lieu de 2.0.1, sans changement de principe.
  • Accès réseau : le port 8200 n'est plus ouvert à tout le LAN, seulement à la VM et au poste d'administration.
  • Administration : un compte nominatif bvivi57 avec sa propre policy, et un token root révoqué après usage.
  • Traçabilité : un journal d'audit activé dès l'installation.
  • AppRole : des identifiants à durée de vie limitée et utilisables uniquement depuis la VM, avec des fichiers en 600 au lieu de 644.
  • NGINX : plus d'authentification basique ni de mode trusted-proxy, l'authentification est déléguée au dashboard d'Hermes.
  • Versionnement : les policies et les fichiers de configuration rejoignent le dépôt Git.

Aucune de ces améliorations n'est révolutionnaire prise isolément. Mises bout à bout, elles réduisent pourtant nettement la surface d'attaque. C'est souvent comme ça en sécurité : pas de solution miracle, mais une accumulation de petites précautions.

Le point avant de continuer

  • vault status indique Initialized true et Sealed false, et les clés de descellement sont rangées hors de la VM.
  • Le moteur secret/ (KV v2), les méthodes approle et userpass et le journal d'audit sont activés.
  • La connexion avec le compte bvivi57 fonctionne et le token root est révoqué.
  • La policy hermes-roger-read et le rôle hermes-roger existent, avec les bound_cidrs.
  • Le fichier /var/lib/hermes/vault/roger-role-id est en 600, propriétaire hermes, type SELinux container_file_t.
  • La CA est dans le magasin de confiance de la VM et dans /var/lib/hermes/vault/tls/ca.crt.
  • https://roger.coolcorp.priv répond 502 avec un certificat valide.

On archive

On termine en rapatriant les configurations dans le dépôt Git. Les policies sont déjà dans le dépôt ; le script récupère vault.hcl et roger.conf :

cd ~/roger.coolcorp.priv
sudo ./scripts/collect.sh
git status
git add -A && git commit -m "Partie 03 : Vault 2.2.1, policies et NGINX" && git push
Collecte des configurations, commit et push de la partie 03 (URL masquée)

Cliquez sur l'image pour l'agrandir.

Les fichiers du dossier ~/pki (clés privées comprises) restent sur la VM, hors du dépôt. Une fois les certificats installés, vous pouvez supprimer les clés privées de ce dossier (shred -u ~/pki/*.key), puisqu'elles ont été copiées à leur emplacement définitif.

Conclusion

Vault est en place, durci par rapport à la version de la série OpenClaw : AppRole lié à l'adresse de la VM, identifiants à durée de vie limitée, journal d'audit, compte d'administration nominatif et port 8200 filtré. NGINX attend le dashboard de Roger derrière un certificat de la PKI interne.

Il reste une limite assumée : le descellement manuel après chaque redémarrage. C'est le prix d'un Vault autonome sans service externe, et on en tiendra compte dans l'exploitation.

Dans le prochain article, on s'occupe des cerveaux de Roger : LM Studio sur le Mac, exposé proprement à la VM, et la clé GLM de Z.ai. Leurs premiers secrets viendront remplir notre coffre tout neuf. L’article arrive bientôt.