ELI5 (Explain Like I'm 5) — SSH Proxy Transparent
Le concept en 1 phrase
Vous vous connectez à une "porte d'entrée", qui vous envoie automatiquement à une vraie machine derrière, sans que vous fassiez quoi que ce soit.
Analogie du restaurant
VOUS (Client Windows)
↓ "Je veux manger!"
HÔTE (Gateway)
↓ "On a 2 cuisines (dest1, dest2), je t'envoie à une au hasard"
CUISINE 1 ou 2 (Destination)
↓ "Voilà ton plat!"
VOUS (Client Windows)
↑ "Super! Je savais pas que j'allais à la cuisine 2!"
Les 3 conteneurs
1. GATEWAY (La porte d'entrée)
- Écoute SSH sur le port 2222
- Votre clé SSH (
lab_rsa) y est connue
- Dès qu'une commande arrive, elle dit: "Je te la proxifie vers une destination"
- Utilise sa propre clé (
gateway_rsa) pour se connecter aux destinations
2. DEST1 & DEST2 (Les vraies machines)
- Reçoivent les connexions de la gateway
- Acceptent la clé de la gateway (
gateway_rsa.pub)
- Exécutent les commandes normalement
- Renvoient les résultats
Les 3 clés SSH
Clé 1: lab_rsa (Windows → Gateway)
C:\Users\user\.ssh\lab_rsa (clé privée)
↓ prouve que c'est toi
La gateway connait la clé publique
Clé 2: gateway_rsa (Gateway → Destinations)
Gateway a la clé privée
↓ s'authentifie auprès de dest1/dest2
Les destinations connaissent la clé publique
Les 6 fichiers importants
1. docker-compose.yaml
"J'ai 3 conteneurs:
- gateway (port 2222 → 22)
- dest1 (172.30.0.11)
- dest2 (172.30.0.12)
Tous dans un réseau privé"
2. gateway/Dockerfile
"Build gateway:
1. Compile sshproxy (outil proxy)
2. Installe sshd (daemon SSH)
3. Ajoute compte testuser
4. Copie clés SSH
5. Configure sshd"
3. gateway/sshd_config ← CRUCIAL
"Toute connexion SSH lancera d'abord:
ForceCommand /usr/sbin/sshproxy-wrapper"
↓ C'est ça qui rend tout transparent!
4. gateway/sshproxy.yaml ← CRUCIAL
"Destinations: [172.30.0.11, 172.30.0.12]
Choix: aléatoire (50% chance chaque)
Commande SSH: ssh -tt -i /path/to/gateway_rsa user@dest
-tt = alloue un pseudo-terminal (pour shell interactif)"
5. gateway/sshproxy-wrapper.sh
"Script simple:
Si commande: lance sshproxy
Si shell: lance sshproxy
(= exactement pareil, sshproxy gère)"
6. dest/sshd_config
"SSH normal. Pas de ForceCommand.
Accepte juste la clé gateway_rsa.pub"
Flux d'une connexion (version simple)
1. Vous: ssh -p 2222 localhost 'hostname'
2. Gateway sshd dit:
"C'est quelle clé? lab_rsa.pub? Oui je la connais!"
3. Gateway sshd exécute:
/usr/sbin/sshproxy-wrapper
4. Wrapper dit:
"T'a un commande? Oui. OK j'appelle sshproxy"
5. sshproxy dit:
"Je dois envoyer ça où?
Destinations: [dest1, dest2]
Je tire au dé... dest2! 🎲"
6. sshproxy exécute:
ssh -tt -i gateway_rsa testuser@dest2 'hostname'
7. dest2 sshd dit:
"C'est quelle clé? gateway_rsa? Oui je la connais!"
8. dest2 exécute:
hostname
9. dest2 retourne:
dest2
10. Vous voyez:
dest2
Exit status 0 ✓
Points clés à comprendre
✅ Pourquoi ça fonctionne
| Point |
Raison |
| ForceCommand |
Intercepte CHAQUE SSH → la proxifie |
| -tt dans config |
Permet les shells interactifs sur destination |
| 2 clés SSH |
Première couche (client→gateway), deuxième couche (gateway→dest) |
| Destinations fixes |
IPs statiques (172.30.0.x) permettent à sshproxy de les cibler |
| Random selection |
Chaque connexion choisit aléatoirement → charge balancée |
❌ Pièges évités
| Piège |
Solution |
| Permission denied sur gateway_rsa |
chown testuser /etc/sshproxy/gateway_rsa |
| Shell interactif qui freeze |
-tt flag dans sshproxy.yaml |
| "Cannot contact etcd" error |
etcd: endpoints: [] dans config (mode stateless OK) |
| Exit status 255 |
↑ Les deux erreurs précédentes |
Configuration minimale: checklist
☐ docker-compose.yaml — 3 conteneurs + réseau
☐ gateway/Dockerfile — compile sshproxy + installe sshd
☐ gateway/sshd_config — ForceCommand /usr/sbin/sshproxy-wrapper
☐ gateway/sshproxy.yaml — destinations + "-tt" flag
☐ gateway/sshproxy-wrapper.sh — lance sshproxy
☐ dest/Dockerfile — installe sshd uniquement
☐ dest/sshd_config — SSH normal
☐ keys/lab_rsa — clé privée Windows (utilisateur garde)
☐ keys/lab_rsa.pub — publique dans gateway
☐ keys/gateway_rsa — privée gateway (dans image)
☐ keys/gateway_rsa.pub — publique dans destinations
Les 3 "magies" principales
Magie 1: ForceCommand
Ligne du sshd_config:
ForceCommand /usr/sbin/sshproxy-wrapper
Effet:
✓ Toute connexion SSH exécute d'abord ce wrapper
✓ C'est ce qui rend le proxy transparent
Magie 2: -tt flag
Dans sshproxy.yaml:
ssh:
args:
- "-tt"
Effet:
✓ Force allocation PTY sur destination
✓ Permet d'avoir un shell interactif
✓ Sans ça: freeze ou exit 255
Magie 3: 2 niveaux de clés
Level 1 (Client→Gateway): lab_rsa
↓ (vous vous authentifiez)
Level 2 (Gateway→Dest): gateway_rsa
↓ (gateway s'authentifie)
Destination
↓ (exécute commande)
Résultat
Commande test minimale
# Lance tout
docker compose up -d
# Teste
ssh -p 2222 -i keys/lab_rsa testuser@localhost 'hostname'
# Devrait afficher dest1 ou dest2 aléatoirement
# Exit status 0
# Et vu que c'est random, si tu testes 5 fois:
for i in {1..5}; do ssh -p 2222 -i keys/lab_rsa testuser@localhost hostname; done
# Tu verras un mélange de dest1 et dest2
Pourquoi "transparent"?
Vous avez l'impression de faire:
ssh dest1 'commande'
En réalité vous faites:
ssh gateway 'commande'
Et la gateway redonne la main à la destination de façon invisible.
Vous ne voyez PAS l'intermédiaire → transparent!
Prochaines étapes (optionnel)
- etcd: Ajouter persistence (sessions sauvegardées)
- OpenLDAP: Centralize users (au lieu de comptes locaux)
- Audit: Logger chaque commande exécutée
- ACL: Restricter qui peut aller où
- Healthchecks: Vérifier que les destinations sont vivantes