SSH Config : simplifier ses connexions avec ~/.ssh/config

Credit : Logo officiel

SSH Config : simplifier ses connexions avec ~/.ssh/config

Dylan D. — Agent Support Technique Serveur SSH 1817 mots 10 min de lecture

Fini les commandes SSH a rallonge

Mercredi dernier, un collegue est passe derriere mon poste et m'a vu taper ssh -i ~/.ssh/id_ed25519_ionos -p 2222 -o ServerAliveInterval=60 deploy@203.0.113.50. Il m'a regarde 3 secondes en silence et m'a dit : "mais t'as pas un fichier ~/.ssh/config toi ?". J'avais honte. Le soir meme j'ai tout migre. Maintenant c'est :

ssh prod

Voila voila. Trois lettres au lieu de 80 caracteres, et zero risque de me planter dans le port ou la cle. Cet article, c'est tout ce que j'aurais aime savoir avant de bosser quotidiennement sur 30 serveurs differents. SSH config c'est 5 minutes de setup pour des heures gagnees sur l'annee.

Creer le fichier en respectant les permissions

mkdir -p ~/.ssh && chmod 700 ~/.ssh
touch ~/.ssh/config && chmod 600 ~/.ssh/config

Le chmod 600 est obligatoire : sans ca, OpenSSH refuse de lire le fichier. Erreur exacte que vous verrez : Bad owner or permissions on /home/dylan/.ssh/config. J'ai perdu 20 minutes la-dessus la premiere fois, alors notez-le.

Meme regle pour les cles privees individuelles : chmod 600 ~/.ssh/id_ed25519_*. Les cles publiques peuvent rester en 644.

Ordre d'evaluation

SSH lit ces fichiers dans l'ordre, et la premiere valeur gagne :

  1. ~/.ssh/config (utilisateur)
  2. /etc/ssh/ssh_config (systeme)

Dans le fichier, les blocs sont evalues du haut vers le bas. Les Host * doivent donc etre en bas sinon ils ecrasent vos blocs specifiques. Une erreur classique.

Syntaxe de base

Editez ~/.ssh/config :

# Serveur de production IONOS
Host prod
    HostName 203.0.113.50
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_ionos

# Serveur de staging
Host staging
    HostName 203.0.113.51
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_ionos

# Serveur de base de donnees (acces interne uniquement)
Host db
    HostName 10.0.1.100
    User dbadmin
    IdentityFile ~/.ssh/id_ed25519_db

ssh prod se connecte directement avec tous les bons parametres. Plus besoin de se rappeler du port, de la cle, du user. Les options indentees sous Host s'appliquent a ce bloc.

Tester sa configuration

Avant de vous reconnecter, validez la conf avec -G qui affiche la config effective sans se connecter :

ssh -G prod | head -20

Vous voyez le user resolu, le hostname, le port, les cles essayees... C'est inestimable pour debug pourquoi votre Host est ignore.

Wildcards et heritage d'options

La ou ca devient vraiment puissant : les wildcards permettent d'appliquer des options a plusieurs hosts d'un coup.

Host ionos-*
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_ionos
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host ionos-prod
    HostName 203.0.113.50

Host ionos-staging
    HostName 203.0.113.51

Host ionos-dev
    HostName 203.0.113.52

# Bloc commun a tout, doit etre en dernier
Host *
    AddKeysToAgent yes
    IdentitiesOnly yes
    Compression yes
    ServerAliveInterval 60
    HashKnownHosts yes

ServerAliveInterval 60 envoie un keepalive toutes les 60 secondes. Sans ca, les NAT et firewalls coupent souvent les sessions inactives apres 5-10 min. Particulierement utile derriere une box ADSL grand public.

Negation et patterns multiples

Host *.fr !backup.example.fr
    User deploy

Le !backup.example.fr exclut explicitement ce host du pattern *.fr. Pratique quand vous avez un mouton noir dans une famille de serveurs.

ProxyJump : rebondir via un bastion

Pour atteindre un serveur dans un reseau prive, il faut passer par un bastion. Avant on se battait avec ProxyCommand et c'etait l'enfer. Depuis OpenSSH 7.3, il y a ProxyJump :

Host bastion
    HostName 203.0.113.50
    User admin
    Port 22
    IdentityFile ~/.ssh/id_ed25519_bastion

Host db-interne
    HostName 10.0.1.100
    User dbadmin
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519_db

Host app-interne
    HostName 10.0.1.200
    User deploy
    ProxyJump bastion

ssh db-interne se connecte automatiquement au bastion puis rebondit vers la base. C'est l'equivalent de l'ancienne syntaxe ProxyCommand ssh -W %h:%p bastion mais en lisible.

Double rebond

En reseau d'entreprise complexe, il arrive d'avoir besoin de deux sauts :

Host deep-server
    HostName 192.168.1.10
    User admin
    ProxyJump bastion,app-interne

SSH passe par bastion puis app-interne puis arrive sur deep-server. C'est aussi simple que ca. La cle utilisee pour chaque saut est celle du bloc correspondant.

Transfert de fichier via bastion

Avec ProxyJump configure, scp et rsync fonctionnent automatiquement :

scp rapport.pdf db-interne:/tmp/
rsync -avz ./code/ app-interne:/var/www/

Pas besoin de gerer le rebond manuellement, c'est lu depuis le config.

IdentityFile : une cle par contexte

Gros conseil que j'aurais aime avoir plus tot : une cle par contexte. Ca evite les embrouilles quand vous bossez sur 10 projets differents avec des employeurs differents.

ssh-keygen -t ed25519 -C "prod-ionos" -f ~/.ssh/id_ed25519_ionos
ssh-keygen -t ed25519 -C "github" -f ~/.ssh/id_ed25519_github
ssh-keygen -t ed25519 -C "db-admin" -f ~/.ssh/id_ed25519_db
ssh-keygen -t ed25519 -C "client-acme" -f ~/.ssh/id_ed25519_acme

Toujours Ed25519. RSA 2048 c'est obsolete, RSA 4096 c'est lent et plus volumineux. Ed25519 c'est court, rapide, et plus securise.

Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519_github
    IdentitiesOnly yes

Host gitlab-acme
    HostName gitlab.acme.com
    User git
    IdentityFile ~/.ssh/id_ed25519_acme
    IdentitiesOnly yes

IdentitiesOnly yes est crucial : sans ca, SSH balance toutes les cles chargees dans ssh-agent une par une au serveur. Resultat : vous declenchez du rate limiting GitHub ou pire, un ban fail2ban sur un serveur strict apres 6 cles essayees. Demandez-moi comment je le sais.

Pourquoi pas une seule cle pour tout

Deux raisons concretes :

Multiplexing : reutiliser les connexions

Le multiplexing maintient une connexion TCP ouverte et la reutilise pour les sessions suivantes. Premiere connexion = 1 a 3 secondes. Suivantes = quasi instantanees.

Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

Creez le dossier sockets :

mkdir -p ~/.ssh/sockets

Les options en detail :

Verifier et controler les sessions actives

ssh -O check prod
# Master running (pid=12345)

ssh -O exit prod
# Exit request sent

Utile quand vous avez une connexion bloquee dans un etat bizarre. Plutot que de killer le PID a la main, demandez a SSH de fermer proprement.

Quand desactiver le multiplexing

En cas de NAT instable, desactivez-le pour ce host specifique :

Host serveur-instable
    ControlMaster no

Tunnels SSH dans le config

Les tunnels se configurent aussi dans le fichier, plus besoin de taper une ligne ssh -L 3307:...:3306 a chaque fois :

Host tunnel-db
    HostName 203.0.113.50
    User deploy
    LocalForward 3307 10.0.1.100:3306
    LocalForward 5433 10.0.1.101:5432
    RequestTTY no
    ExitOnForwardFailure yes

ssh -N tunnel-db et hop, MySQL accessible en local sur 127.0.0.1:3307, PostgreSQL sur 127.0.0.1:5433. -N lance le tunnel sans ouvrir de shell. ExitOnForwardFailure yes evite que la connexion semble reussir alors que le port est deja pris localement.

Pour l'inverse (tunnel inverse exposant un port local cote serveur), utilisez RemoteForward. Pour un proxy SOCKS dynamique, DynamicForward 1080. Voici un exemple complet melangeant les trois :

Host work-vpn
    HostName vpn.acme.com
    User dylan
    DynamicForward 1080
    LocalForward 3307 db.internal:3306
    RemoteForward 8888 localhost:8000
    ServerAliveInterval 30

En lancant ssh -N work-vpn, vous obtenez : un proxy SOCKS sur 1080, MySQL en local sur 3307, et votre serveur de dev local accessible cote distant sur le port 8888. Une seule connexion, trois fonctions.

Variables et inclusion

Peu de gens connaissent la directive Include, qui permet de splitter sa config en plusieurs fichiers :

# ~/.ssh/config
Include ~/.ssh/config.d/*.conf
Include ~/.ssh/clients/*.conf

Host *
    AddKeysToAgent yes
    IdentitiesOnly yes

Un fichier par client ou par projet, c'est imbattable cote organisation. J'ai par exemple ~/.ssh/clients/acme.conf que je peux supprimer en une commande quand le contrat se termine.

Les variables utilisables

Dans ControlPath, IdentityFile et autres options de chemin, vous pouvez utiliser :

Exemple pour des sockets nommes courts :

Host *
    ControlPath ~/.ssh/sockets/%C

Match : configuration conditionnelle

Depuis OpenSSH 6.5, la directive Match permet d'appliquer des regles selon des conditions, pas seulement le pattern de host :

Match host *.prod.acme.com user dylan
    ForwardAgent yes
    PermitLocalCommand yes

Match exec "test -f ~/.work-mode"
    ProxyJump bastion-corporate

Le second exemple est puissant : si le fichier ~/.work-mode existe (que je touch quand j'arrive au bureau), tous mes SSH passent automatiquement par le bastion d'entreprise. Sinon, connexion directe. Ca evite de dupliquer la conf.

Erreurs courantes et leur fix

"Bad owner or permissions on .ssh/config"

Le fichier ou le repertoire a les mauvaises permissions :

chmod 700 ~/.ssh
chmod 600 ~/.ssh/config ~/.ssh/id_ed25519*
chmod 644 ~/.ssh/*.pub ~/.ssh/known_hosts

"Too many authentication failures"

Vous avez trop de cles dans ssh-agent et SSH les essaie toutes avant la bonne. Solution :

Host *
    IdentitiesOnly yes

Et specifiez IdentityFile dans chaque bloc Host.

Le Host * ecrase mes regles

Vous avez mis le bloc Host * en haut. Replacez-le tout en bas du fichier. SSH applique les options au premier match du haut vers le bas.

ssh-add perd ses cles a chaque reboot

Sous macOS, ajoutez dans votre config :

Host *
    UseKeychain yes
    AddKeysToAgent yes

Sous Linux, configurez gnome-keyring ou utilisez keychain (paquet du meme nom).

Connexion qui se coupe apres quelques minutes

NAT ou firewall qui drop les connexions inactives. Ajoutez globalement :

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    TCPKeepAlive yes

Pour aller plus loin

Le fichier qui change la vie

Depuis que j'ai migre tous mes hosts dans ~/.ssh/config, je tape literalement deux fois moins de caracteres dans une journee de travail. Le fichier vit dans mon depot dotfiles personnel versionne avec git (sans les IdentityFile evidemment, qui restent locaux), je le synchronise sur mon laptop, mon poste fixe et mon serveur de dev via un script make sync. Investissez 30 minutes a le mettre en place avec wildcards, ProxyJump, multiplexing et Include, et ce sera l'investissement temps avec le meilleur retour de votre semaine.

Un dernier conseil : commentez chaque bloc Host avec une ligne du genre # Client Acme - prod web - revoke avant juin 2027. Le futur vous, qui devra revisiter ce fichier dans deux ans, dira merci au present vous. Et le jour ou vous devez expliquer a un junior comment se connecter a 12 serveurs differents, vous lui donnez le fichier et c'est plie en 30 secondes.

# Articles similaires

Sur les memes sujets et plus loin