Dans mon terminal, accéder à un serveur tient en une seule ligne :
| |
Pas d’adresse IP à chercher, pas de serveur de rebond à saisir, aucune clé personnelle à copier sur la cible. Pourtant, sous la surface, une chaîne complète s’active : OpenSSH, Traefik, mon bastion Warpgate, Authentik et un fichier de configuration alimenté par WarpgateSH.
Dans le billet consacré à WarpgateSH, j’expliquais comment un script lourd à maintenir avait donné naissance à un outil dédié. Il me restait à présenter l’autre pan du système : le bastion lui-même, la gestion des clés SSH et son intégration dans mon système d’information — à l’échelle d’un homelab.
Je vais remonter cette commande étape par étape, depuis mon terminal jusqu’au serveur. Pour en reproduire le principe, je partirais simplement d’une VM et d’une machine de test.
Les exemples s’appuient sur mon infrastructure et le code de WarpgateSH au 19 septembre 2026 ; la version de Warpgate vérifiée le 10 septembre était la 0.27.5. Les valeurs
example.net,aliceetsrv-demosont fictives et réservées aux démonstrations.
Un bastion, pour quoi faire ?
Un bastion sert de point de passage contrôlé vers les ressources à administrer. Au lieu d’autoriser chaque poste à joindre directement chaque serveur, je centralise les flux sur un service unique. Il contrôle les identités, applique les droits et conserve des journaux d’accès.
Warpgate remplit cette fonction chez moi. Je lui déclare les cibles (les serveurs autorisés) et les rôles (qui a le droit d’y accéder), tout en conservant mon client SSH habituel.
À la différence d’un rebond classique via ProxyJump, la connexion SSH directe depuis mon poste s’arrête au bastion. Warpgate ouvre ensuite sa propre session vers la machine cible avec ses propres identifiants. Je n’ai donc jamais besoin d’y transférer mon agent SSH local.

Vue logique : le réseau et le proxy de transport sont volontairement omis pour faire ressortir les deux authentifications.
Cette étanchéité constitue le point clé du montage : mon identité sur le bastion et le compte Linux de la cible sont deux entités totalement distinctes.
Clé privée, clé publique : qui garde quoi ?
Une paire de clés SSH repose sur deux éléments liés mathématiquement :
- la clé privée, gardée secrète par le client qui prouve son identité ;
- la clé publique, déposée sur le serveur qui vérifie cette identité.
Lors de la connexion, le client signe le défi d’authentification avec sa clé privée. Le serveur valide cette signature via la clé publique correspondante, sans que la clé privée ne transite sur le réseau.
Sur un serveur OpenSSH standard, les clés autorisées sont listées dans ~/.ssh/authorized_keys au niveau du compte utilisateur Linux (alice, root, etc.).
Pour illustrer la génération manuelle, je prends cette commande :
| |
Cette commande produit deux fichiers (décrits dans le manuel ssh-keygen) :
| |
Chez moi, les clés passent par l’agent Bitwarden
Je ne définis pas de mot de passe directement sur le fichier de clé : je délègue sa protection au coffre fort de Bitwarden et son utilisation à son agent SSH. OpenSSH sollicite l’agent pour signer l’authentification sans lire de fichier privé sur mon disque. Le guide SSH Bitwarden détaille sa mise en œuvre.
Dans mon inventaire Ansible, la connexion pointe vers le socket de l’agent :
| |
Une précision importante : stocker une clé non chiffrée dans Bitwarden protège sa copie dans le coffre, mais ne sécurise pas le fichier initial resté sur le disque s’il n’a pas été supprimé.
Pour accéder au bastion via SSH, j’enregistre ma clé publique dans mon profil Warpgate. Mon agent Bitwarden produit la signature ; le bastion la vérifie avec ma clé publique. Une fois authentifié, Warpgate initie la connexion vers la cible finale en utilisant sa propre clé privée. J’autorise donc la clé publique du bastion dans le fichier authorized_keys du serveur, et non ma clé personnelle.
Ce flux par clé sert principalement aux comptes de service. Pour mes sessions d’administration courantes, je valide mon identité dans le navigateur via Authentik.
Ed25519, RSA, ECDSA : trois algorithmes distincts
Ces appellations désignent des algorithmes utilisés pour les signatures d’authentification SSH :
| Type | Ce que j’en retiens |
|---|---|
| Ed25519 | C’est mon choix pour les systèmes récents et le type de clé déployé par mon socle Ansible. |
| RSA | Je le réserverais aux besoins de rétrocompatibilité, avec des signatures SHA-2 comme l’expliquent les notes OpenSSH 8.8. |
| ECDSA | Variante basée sur des courbes elliptiques standardisées, disponible nativement dans OpenSSH. |
ed25519-sk / ecdsa-sk | Déclinaisons exploitant une clé de sécurité matérielle (FIDO2/YubiKey). |
Pour une nouvelle installation, j’écarte les algorithmes obsolètes comme DSA.
Et la clé du serveur ?
SSH contrôle également l’authenticité de l’hôte distant pour éviter les interceptions.
authorized_keyscontrôle qui peut entrer ;known_hostsenregistre les serveurs de confiance.
Le poste client valide l’empreinte du bastion, puis le bastion valide celle du serveur cible. À la première connexion, je recommande de comparer l’empreinte proposée avec celle relevée depuis la console de la VM. Pour afficher cette dernière sur la cible, j’utiliserais cette commande :
| |
Note d’usage : ne pas comparer cette empreinte revient à accepter aveuglément l’identité du serveur. C’est un point d’amélioration que je prévois d’automatiser entièrement dans mon lab.
Déploiement dans le homelab
Mon bastion tourne sur une VM Proxmox dédiée (dmz-bastion-01), provisionnée via Terraform (2 vCPU, 2 Gio de RAM, 30 Gio de stockage).
Ansible déploie Docker et configure l’environnement. Voici un extrait de mon fichier Docker Compose :
| |
Le port 2222 reçoit les connexions SSH et le port 8888 héberge l’interface d’administration HTTPS. Le dossier ./data conserve l’état persistant de l’application.
Traefik en frontal : un nom, deux parcours
Traefik écoute sur bastion.int.homeblack.fr et redirige les requêtes vers le bastion :
| |
Traefik achemine le trafic vers Warpgate ; Warpgate gère les accès aux cibles. Traefik ignore tout des machines finales et se contente de relayer les flux.
Reverse proxy HTTPS pour le portail
Le portail web passe par un reverse proxy standard. L’inventaire Ansible définit le routage :
| |
Les liens d’approbation et le retour Authentik passent par le navigateur, via l’accès HTTPS du portail.
Relais TCP dédié pour le flux SSH
SSH ne traitant pas le protocole HTTP, Traefik utilise un routeur TCP. La configuration statique déclare d’abord le point d’entrée :
| |
La configuration dynamique (chargée via le provider file) définit ensuite le routage :
| |
La règle HostSNI(`*`) accepte tous les flux TCP bruts sans interception TLS (détails dans la doc Traefik TCP). Le chiffrement SSH reste géré de bout en bout entre le client et Warpgate.
Conservation des IP sources avec PROXY Protocol
Lorsqu’un proxy transfère une connexion, le serveur final ne voit que l’IP du proxy.
L’activation du PROXY protocol v2 permet à Traefik de transmettre l’IP d’origine du client dans un en-tête ajouté au début du flux TCP. Côté Warpgate (ansible/inventory/group_vars/bastion.yml) :
| |
Segmentation réseau
Dans mon architecture, Traefik réside sur le réseau TRUST et Warpgate sur le réseau DMZ. Le flux traverse le pare-feu inter-vlan. Pour une nouvelle installation, je privilégierais un reverse proxy dans le même segment réseau que le service, afin d’éviter ce passage entre zones.
J’intègre aussi le filtrage des accès directs depuis les postes à la mise en place du bastion, avec l’objectif de faire passer les sessions d’administration par Warpgate.
Comment les serveurs valident le bastion
Une fois authentifié sur Warpgate, ce dernier initie la connexion SSH vers le serveur cible. La cible doit donc connaître la clé publique du bastion.
Mon rôle Ansible ssh-keys déploie automatiquement la clé publique du bastion (warpgate-target) dans le fichier authorized_keys du compte système distant :
| |
Côté Warpgate, les paramètres de la cible (IP, port, utilisateur Linux) sont injectés via Terraform.
Pour me connecter à Nextcloud, la commande sous-jacente générée équivaut à :
| |
Le paramètre -l transmet l’instruction : « Connecte l’utilisateur Warpgate gregory.narcin à la cible dmz-nextcloud-01 ». Après validation des droits, Warpgate ouvre la session SSH vers la VM avec son propre compte d’administration.
SSO avec Authentik et accès automatisés
J’utilise Authentik comme fournisseur d’identité via OIDC, OpenID Connect, pour l’authentification unique (SSO).
Pour une connexion humaine, l’option In-browser auth affiche une URL et un code de validation dans le terminal. J’ouvre le lien, compare le code affiché et approuve la demande après authentification auprès d’Authentik. OpenSSH attend cette validation via la méthode keyboard-interactive.
Pour raccorder Warpgate à Authentik, je configure une application cliente et son URL de redirection, par exemple https://bastion.example.net/@warpgate/api/sso/return. Je sélectionne ensuite In-browser auth dans la politique d’authentification SSH de mon compte, comme l’explique le guide SSO Warpgate.
Je gère la correspondance des rôles avec Ansible :
| |
Les appartenances aux groupes Authentik sont ainsi converties en rôles d’accès dans Warpgate.
Pour mes automatisations Ansible et mes pipelines CI/CD, je garde des comptes de service avec des clés SSH dédiées, comme svc-ansible : je ne veux pas qu’un traitement attende une validation dans mon navigateur.
Déclaration des cibles avec Terraform
Je déclare mes cibles Warpgate avec Terraform pour éviter de les ajouter manuellement dans l’interface :
| |
Mon parcours de création suit cet ordre : VM Terraform → Cible Warpgate Terraform → Socle Ansible → Synchronisation du client.
J’exclus le bastion lui-même de cette liste et conserve un accès d’urgence indépendant pour pouvoir intervenir si Warpgate tombe en panne.
Automatisation des alias avec WarpgateSH
WarpgateSH interroge l’API de Warpgate pour générer automatiquement la configuration SSH locale et les alias associés.
Après avoir installé la CLI (guide de démarrage), je configure un profil ; voici les commandes avec les valeurs de démonstration :
| |
L’outil ajoute un appel d’inclusion dans ~/.ssh/config :
| |
Le fichier généré (~/.ssh/warpgatesh/config) contient les entrées de ce type :
| |
Explication des directives :
Host: définit l’alias court et l’alias qualifié.HostName: pointe vers le bastion.User: passe le sélecteurutilisateur:cible.PreferredAuthentications: impose l’authentification interactive pour déclencher la validation SSO par navigateur.
Je peux ensuite utiliser la commande raccourcie :
| |
Diagnostic et retours d’expérience
J’ai rencontré des incohérences entre mes alias SSH : certains forçaient le mode keyboard-interactive, tandis que d’autres autorisaient aussi les clés publiques.
Pour comprendre ce qu’OpenSSH utilise réellement, je consulte sa configuration effective avec ssh -G, sans ouvrir de connexion :
| |
Je peux ainsi repérer les conflits avec d’éventuelles directives Host * globales.
Pour valider un montage similaire, je vérifierais quatre points :
- L’accès aux cibles autorisées.
- Le refus d’accès à une cible non attribuée.
- Le compte Linux obtenu à l’arrivée, avec
whoami. - La présence de la session dans les traces d’audit de Warpgate.
Bilan
Avec ce montage, je centralise mes accès SSH sans intervenir sur chaque serveur à l’arrivée ou au départ d’un utilisateur. Je garde mes accès automatisés séparés grâce aux comptes de service dédiés.
En contrepartie, mon bastion devient un composant critique de l’infrastructure. Je dois sauvegarder régulièrement sa clé privée d’administration et ses données d’audit, et en restreindre strictement les accès. C’est bien ce qui se passe en coulisses, et même un peu plus… mais si je raconte tout ici, ce billet va finir avec un sommaire en trois volumes.
Selon moi, le plus simple est d’avancer par étapes : je commencerais par valider le flux SSH sur une seule cible, puis j’intégrerais le SSO OIDC avant d’automatiser la gestion des cibles et des alias sur mon poste.
