Guide

SSH tout ce qu'on utilise mal au quotidien

Config ~/.ssh/config, tunnels SSH, certificats, ProxyJump, sshd sécurisé - tout ce que vous utilisez à 10% de ses capacités, expliqué pour les DevOps.

Sommaire

En 1995, Tatu Ylönen était chercheur à l'université d'Helsinki quand un collègue s'est fait sniffer son mot de passe sur le réseau local. Tatu a décidé de régler le problème. Il a écrit SSH en quelques mois, l'a mis en ligne gratuitement, et s'est retrouvé avec 20 000 utilisateurs en deux semaines. Ce n'était pas prévu.

Aujourd'hui SSH tourne sur quasiment tous les serveurs Linux de la planète, protège des milliards de connexions par jour, et la plupart des gens l'utilisent exactement comme on utilisait Telnet avant - en tapant une commande, en croisant les doigts, et en se demandant pourquoi ça ne marche pas.

Ce guide ne va pas vous apprendre à faire ssh user@host. Vous savez déjà. On va parler de ce qu'on fait après.

La config ~/.ssh/config - le truc que personne ne fait

Levez la main si vous tapez ça régulièrement :

ssh -i ~/.ssh/ma_cle_privee -p 2222 -l cyril -L 5432:localhost:5432 mon.serveur.example.com

C'est illisible, impossible à mémoriser, et vous le retapez à chaque fois parce que vous avez oublié de le noter quelque part. Il existe une solution. Elle s'appelle ~/.ssh/config et elle existe depuis 1999.

# ~/.ssh/config

Host rudeops
    HostName mon.serveur.example.com
    User cyril
    Port 2222
    IdentityFile ~/.ssh/ma_cle_privee
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host bastion
    HostName bastion.corp.example.com
    User ops
    IdentityFile ~/.ssh/cle_corp

Host prod-db
    HostName 10.0.1.42
    User ubuntu
    ProxyJump bastion
    LocalForward 5432 localhost:5432

Maintenant :

ssh rudeops     # connexion complète en 10 caractères
ssh prod-db     # tunnel + saut de bastion automatique

ControlMaster et ControlPath - le multiplexage de connexions

SSH négocie TLS à chaque nouvelle connexion vers le même host. Sur un réseau lent ou une authentification à deux facteurs, c'est douloureux. Le multiplexage réutilise une connexion existante pour toutes les sessions suivantes vers le même host :

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

La première connexion vers un host ouvre le socket de contrôle. Toutes les suivantes - y compris scp, rsync, git push - réutilisent la même connexion chiffrée sans renégocier. ControlPersist 600 garde le socket ouvert 10 minutes après la fermeture du dernier terminal. Créez le dossier avant :

mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets

Le gain est immédiat sur des connexions répétées - git push sur une connexion lente passe de 3 secondes à 300ms.

IdentitiesOnly yes - forcer l'usage d'une clé spécifique

Host github-pro
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_github_pro
    IdentitiesOnly yes

Sans IdentitiesOnly yes, SSH essaie toutes les clés chargées dans l'agent avant celle spécifiée dans IdentityFile. Avec GitHub qui limite le nombre de tentatives d'authentification, ça peut provoquer des Permission denied mystérieux si vous avez beaucoup de clés dans l'agent. IdentitiesOnly yes force SSH à n'utiliser que la clé explicitement définie.

StrictHostKeyChecking - et quand le désactiver sans se tirer une balle dans le pied

# Pour un host dont l'empreinte change souvent (VM éphémère)
Host *.staging.example.com
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null

StrictHostKeyChecking no accepte n'importe quelle empreinte sans demander. UserKnownHostsFile /dev/null ne sauvegarde pas l'empreinte. À réserver aux environnements éphémères où les VMs sont recréées régulièrement et où un man-in-the-middle est improbable. En prod, jamais.

HashKnownHosts yes - hasher les entrées de known_hosts

Host *
    HashKnownHosts yes

Par défaut ~/.ssh/known_hosts stocke les noms d'hôtes en clair - un attaquant ayant accès à votre machine sait exactement à quels serveurs vous vous connectez. Avec HashKnownHosts yes, les noms sont hashés. Vous perdez la lisibilité humaine, vous gagnez en confidentialité.

Les permissions du fichier comptent

chmod 600 ~/.ssh/config
chmod 700 ~/.ssh/

Si les permissions sont mauvaises, SSH ignore silencieusement votre config et vous vous demandez pourquoi rien ne fonctionne.

Les clés - au-delà de la génération basique

Ed25519, pas RSA

# Ce qu'on faisait
ssh-keygen -t rsa -b 4096

# Ce qu'on fait maintenant
ssh-keygen -t ed25519 -C "cyril@rudeops.com"

Ed25519 est plus court, plus rapide, plus sûr que RSA 4096. Les clés font 68 caractères au lieu de plusieurs centaines. Si votre serveur est trop vieux pour supporter Ed25519, il est trop vieux pour être en prod.

La passphrase - ne pas la sauter

Quand ssh-keygen vous demande une passphrase, ne tapez pas Entrée. Une clé privée sans passphrase, c'est un mot de passe en clair sur votre disque. Si quelqu'un accède à votre machine - vol, malware, collègue curieux - il a accès à tout ce que cette clé déverrouille.

ssh-agent et ssh-add

eval "$(ssh-agent -s)"
ssh-add -t 4h ~/.ssh/id_ed25519
ssh-add -l

L'agent garde vos clés déchiffrées en mémoire. Vous tapez votre passphrase une fois, vous ne la retapez plus jusqu'au prochain redémarrage ou expiration.

Les certificats SSH - la PKI pour SSH

Les clés SSH sont un modèle pair à pair - vous copiez votre clé publique sur chaque serveur. Ça fonctionne à petite échelle, ça devient ingérable à partir de quelques dizaines de serveurs et d'utilisateurs.

Les certificats SSH sont une alternative : une autorité de certification (CA) signe les clés des utilisateurs et des serveurs. Les serveurs font confiance à la CA, pas aux clés individuellement. Quand un utilisateur arrive avec une clé signée par la CA, l'accès est accordé sans avoir jamais copié sa clé publique sur le serveur.

# Créer une CA SSH
ssh-keygen -t ed25519 -f ~/.ssh/ca -C "RudeOps SSH CA"

# Signer la clé d'un utilisateur (valide 1 semaine, identité "cyril")
ssh-keygen -s ~/.ssh/ca \
           -I "cyril@rudeops.com" \
           -n "cyril,ubuntu" \
           -V +1w \
           ~/.ssh/id_ed25519.pub

Côté serveur, dans sshd_config :

TrustedUserCAKeys /etc/ssh/ca.pub

Maintenant tous les utilisateurs dont la clé est signée par la CA peuvent se connecter, sans authorized_keys individuel. Pour révoquer l'accès d'un utilisateur : vous ne renouvelez pas son certificat. Plus besoin de retirer sa clé de dizaines de serveurs.

Les certificats ont une date d'expiration - une clé compromise est automatiquement invalide après l'expiration. C'est la vraie différence avec les clés traditionnelles qui restent valides jusqu'à suppression manuelle.

YubiKey et SSH - les clés matérielles

Une YubiKey stocke votre clé privée dans un hardware sécurisé. La clé ne quitte jamais le device - les opérations cryptographiques se font sur la YubiKey elle-même. Vol de laptop, malware, accès non autorisé à votre home directory : votre clé SSH reste inaccessible.

# Lister les slots disponibles
ssh-keygen -D /usr/lib/x86_64-linux-gnu/libykcs11.so -e

# Utiliser la YubiKey avec ssh-agent
ssh-add -s /usr/lib/x86_64-linux-gnu/libykcs11.so

Depuis OpenSSH 8.2, le support FIDO2 est natif :

# Générer une clé résidente sur la YubiKey
ssh-keygen -t ed25519-sk -O resident -f ~/.ssh/id_yubikey

La clé privée réside sur la YubiKey. Une pression physique sur le bouton est requise pour chaque authentification. Vous ne pouvez pas vous faire phisher une clé que vous devez toucher physiquement.

Les tunnels SSH - local, remote, dynamic

Local forwarding - accéder à un service distant comme s'il était local

ssh -N -f -L 5432:localhost:5432 user@serveur.example.com

Accéder à une base de données de prod sans l'exposer :

# Tunnel vers PostgreSQL
ssh -N -f -L 5432:localhost:5432 prod-db

# Puis dans un autre terminal
psql -h localhost -p 5432 -U cyril -d mabase

La base n'est pas exposée sur internet. Le tunnel SSH chiffré fait le pont. Votre DBA vous remercie, votre RSSI aussi.

Accéder à un dashboard Grafana interne :

ssh -N -f -L 3000:localhost:3000 user@serveur-monitoring

Ouvrez http://localhost:3000 dans votre navigateur. Vous êtes sur le Grafana du serveur de monitoring, sans l'exposer sur internet, sans VPN.

Débugger un service dans un VPC AWS sans bastion manager :

# ~/.ssh/config
Host aws-bastion
    HostName 1.2.3.4
    User ec2-user
    IdentityFile ~/.ssh/aws.pem

Host rds-prod
    HostName rds-prod.abc123.eu-west-1.rds.amazonaws.com
    User postgres
    ProxyJump aws-bastion
    LocalForward 5432 rds-prod.abc123.eu-west-1.rds.amazonaws.com:5432

# Connexion directe
ssh -N rds-prod

Accéder à l'API server Kubernetes sans exposer le kubeconfig :

ssh -N -f -L 6443:localhost:6443 user@master-node

# Dans votre kubeconfig, pointez vers localhost:6443
kubectl --server=https://localhost:6443 get pods

Remote forwarding - exposer un service local

ssh -R 8080:localhost:3000 user@serveur.example.com

Cas d'usage : recevoir des webhooks GitHub en local pendant le développement. Votre app tourne sur localhost:3000. Le serveur distant écoute sur 8080. Vous configurez votre webhook GitHub vers http://votre-serveur.com:8080, les events arrivent dans votre app locale.

Pour que le port soit accessible depuis l'extérieur du serveur :

# Dans sshd_config côté serveur
GatewayPorts yes

Dynamic forwarding - proxy SOCKS

ssh -D 1080 -N -f user@serveur.example.com

Configurez votre navigateur pour utiliser localhost:1080 comme proxy SOCKS5. Tout le trafic du navigateur passe par le serveur SSH.

Cas d'usage DevOps : accéder à une interface web interne (Kibana, Kubernetes dashboard, Consul UI) sans exposer de port et sans VPN. Le proxy SOCKS route sélectivement le trafic - uniquement ce que vous lui demandez, contrairement à un VPN qui route tout.

ProxyJump - sauter de bastion en bastion

# Un saut
ssh -J ops@bastion ubuntu@serveur-interne

# Plusieurs sauts
ssh -J ops@bastion1,ops@bastion2 ubuntu@serveur-final

Dans ~/.ssh/config :

Host bastion
    HostName bastion.corp.example.com
    User ops
    ForwardAgent yes

Host serveur-interne
    HostName 10.0.1.42
    User ubuntu
    ProxyJump bastion

Host serveur-profond
    HostName 10.0.2.100
    User ubuntu
    ProxyJump bastion,serveur-interne

Agent forwarding - avec discernement. ForwardAgent yes permet d'utiliser votre clé locale depuis le bastion pour les connexions suivantes. Pratique, mais à restreindre aux bastions de confiance - si le bastion est compromis, l'attaquant peut utiliser votre agent pendant que votre session est ouverte.

Une alternative plus sûre : ProxyJump sans ForwardAgent. SSH crée un canal direct chiffré entre votre machine et la destination finale, en passant par le bastion comme relais sans lui donner accès à votre clé.

Git via SSH - plusieurs comptes sur la même machine

ssh-keygen -t ed25519 -f ~/.ssh/id_github_perso -C "cyril@gmail.com"
ssh-keygen -t ed25519 -f ~/.ssh/id_github_pro -C "cyril@entreprise.com"

Dans ~/.ssh/config :

Host github-perso
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_github_perso
    IdentitiesOnly yes

Host github-pro
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_github_pro
    IdentitiesOnly yes
git clone git@github-perso:cyril/mon-projet.git
git clone git@github-pro:entreprise/projet.git

# Vérifier
ssh -T git@github-perso   # Hi cyril!
ssh -T git@github-pro     # Hi cyril-pro!

scp vs rsync via SSH

scp - copie simple

# Copier un fichier local vers un serveur
scp fichier.txt user@serveur:/tmp/

# Copier depuis un serveur
scp user@serveur:/var/log/app.log ./

# Copier un dossier récursivement
scp -r ./dossier user@serveur:/opt/

scp est simple, disponible partout, parfait pour copier un fichier ponctuel. Ses limitations : il ne reprend pas les transferts interrompus, il copie tout même si rien n'a changé, et il est plus lent que rsync sur de gros volumes.

rsync via SSH - la vraie solution

# Synchroniser un dossier local vers un serveur
rsync -avz --progress ./deploy/ user@serveur:/opt/app/

# Avec suppression des fichiers supprimés localement
rsync -avz --delete ./deploy/ user@serveur:/opt/app/

# Simulation sans modification (dry-run)
rsync -avzn ./deploy/ user@serveur:/opt/app/

# Reprendre un transfert interrompu
rsync -avz --partial --progress fichier.tar.gz user@serveur:/tmp/

-a : mode archive (récursif + permissions + timestamps). -v : verbose. -z : compression pendant le transfert. --progress : progression par fichier.

rsync ne transfère que les différences. Sur un dossier de 10GB où 3 fichiers ont changé, il transfère 3 fichiers, pas 10GB. C'est la raison principale de l'utiliser à la place de scp.

Spécifier la clé SSH pour rsync :

rsync -avz -e "ssh -i ~/.ssh/ma_cle -p 2222" ./deploy/ user@serveur:/opt/app/

Ou via ~/.ssh/config - rsync utilise votre config SSH automatiquement :

rsync -avz ./deploy/ rudeops:/opt/app/

Déploiement sans downtime :

# Déployer dans un dossier temporaire
rsync -avz --delete ./dist/ user@serveur:/opt/app-new/

# Basculer atomiquement via symlink
ssh user@serveur "ln -sfn /opt/app-new /opt/app && systemctl reload nginx"

Le rechargement nginx pointe vers le nouveau dossier sans interruption de service. L'ancien dossier reste disponible pour rollback rapide.

SSH dans les pipelines CI/CD

ssh-keyscan - pré-peupler known_hosts

Dans un pipeline CI/CD, SSH refuse de se connecter à un host inconnu car il ne peut pas afficher de prompt interactif. La solution naïve (StrictHostKeyChecking no) désactive la vérification. La bonne solution :

# Récupérer l'empreinte du serveur et l'ajouter à known_hosts
ssh-keyscan -H mon.serveur.example.com >> ~/.ssh/known_hosts

# Ou pour un port non standard
ssh-keyscan -H -p 2222 mon.serveur.example.com >> ~/.ssh/known_hosts

Dans un pipeline GitHub Actions :

- name: Setup SSH
  run: |
    mkdir -p ~/.ssh
    echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
    chmod 600 ~/.ssh/id_ed25519
    ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts

- name: Deploy
  run: rsync -avz ./dist/ deploy@${{ secrets.DEPLOY_HOST }}:/opt/app/

L'empreinte est récupérée dynamiquement et vérifiée à chaque connexion. Pas de StrictHostKeyChecking no, pas de faille.

Ansible via SSH

Ansible utilise SSH pour toutes ses connexions. La config ~/.ssh/config s'applique automatiquement. Pour optimiser les déploiements Ansible sur de nombreux hosts :

# ansible.cfg
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPath=/tmp/ansible-ssh-%h-%p-%r -o ControlPersist=60s
pipelining = True

ControlMaster + ControlPersist : une seule connexion SSH par host pour toute la durée du playbook. pipelining : plusieurs tâches Ansible s'exécutent dans la même connexion SSH sans en ouvrir une nouvelle à chaque fois. Sur un playbook de 50 tâches vers 20 hosts, le gain de temps est significatif.

Terraform via SSH

Terraform utilise SSH pour les provisioners et les connexions aux resources. Configuration dans le bloc connection :

resource "null_resource" "deploy" {
  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file("~/.ssh/id_ed25519")
    host        = aws_instance.web.public_ip

    # Via bastion
    bastion_host        = aws_instance.bastion.public_ip
    bastion_user        = "ec2-user"
    bastion_private_key = file("~/.ssh/aws.pem")
  }
}

Terraform gère le ProxyJump nativement via bastion_host. Pas besoin de configurer SSH manuellement.

Sécuriser sshd

La config est dans /etc/ssh/sshd_config. Après chaque modification : systemctl reload sshd - pas restart.

# Désactiver le login root
PermitRootLogin no

# Désactiver l'authentification par mot de passe
PasswordAuthentication no
PubkeyAuthentication yes

# Restreindre les utilisateurs
AllowUsers cyril deploy

# Limiter les tentatives
MaxAuthTries 3
LoginGraceTime 30

# Changer le port
Port 2222

# Désactiver les protocoles legacy
Protocol 2

# Restreindre les algorithmes de chiffrement
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512

Les algorithmes de chiffrement - pourquoi ça compte : les defaults OpenSSH sont raisonnables mais incluent des algorithmes plus anciens pour la compatibilité. Sur un serveur où vous contrôlez les clients, restreindre aux algorithmes modernes (ChaCha20, AES-GCM, Curve25519) élimine les vecteurs d'attaque sur les algorithmes plus faibles.

Vérifier votre configuration :

# Tester la config sans recharger
sshd -t

# Voir les algorithmes actifs
ssh -Q cipher
ssh -Q mac
ssh -Q kex

# Auditer votre serveur depuis l'extérieur
ssh-audit mon.serveur.example.com

ssh-audit analyse votre configuration et vous signale les algorithmes faibles, les clés hôtes problématiques et les bonnes pratiques manquantes. À lancer avant de mettre un serveur en prod.

Débugger SSH

ssh -v user@host    # verbose
ssh -vv user@host   # plus verbose
ssh -vvv user@host  # tout

Permission denied (publickey) - dans l'ordre : clé publique dans authorized_keys du serveur, permissions 700 sur ~/.ssh/ et 600 sur authorized_keys, ssh-add -l liste votre clé, PubkeyAuthentication yes dans sshd_config.

Connection refused - sshd ne tourne pas ou pas sur le port attendu. systemctl status sshd côté serveur.

Host key verification failed - empreinte changée. Si c'est normal : ssh-keygen -R mon.serveur.example.com.

Connection timed out - problème réseau ou firewall avant SSH. nc -zv mon.serveur.example.com 22 pour isoler.

Too many authentication failures - trop de clés proposées par l'agent. Ajoutez IdentitiesOnly yes dans ~/.ssh/config pour le host concerné.

Lire les logs côté serveur :

journalctl -u sshd -f
tail -f /var/log/auth.log

Les logs côté serveur donnent souvent l'explication exacte que le client cache pudiquement.

À retenir

CommandePour quoi
ssh -v hostDébugger une connexion
ssh -N -f -L 5432:localhost:5432 hostTunnel local vers une DB
ssh -N -f -D 1080 hostProxy SOCKS en arrière-plan
ssh -J bastion user@interneSauter via un bastion
ssh-add -t 4h ~/.ssh/id_ed25519Ajouter une clé pour 4h
ssh-keygen -R hostSupprimer une empreinte connue
ssh-keyscan -H host >> ~/.ssh/known_hostsPré-peupler known_hosts en CI
rsync -avz --delete src/ user@host:/dst/Synchroniser un dossier
systemctl reload sshdRecharger la config sans couper
sshd -tValider la config avant reload

FAQ

Quelle différence entre scp et rsync ?

scp copie tout à chaque fois. rsync ne transfère que les différences. Sur de petits fichiers ponctuels, scp suffit. Sur de gros volumes ou des synchronisations répétées, rsync est indispensable.

Comment savoir quelle clé SSH est utilisée ?

ssh -v user@host - cherchez Offering public key: dans la sortie.

Changer le port SSH améliore-t-il la sécurité ?

Pas contre une attaque ciblée. Oui contre le bruit de fond des bots qui scannent le port 22. Vos logs seront plus propres.

Comment copier sa clé publique sur un serveur ?

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@serveur - gère les permissions automatiquement.

Mon tunnel SSH se coupe après quelques minutes d'inactivité.

ServerAliveInterval 60 et ServerAliveCountMax 3 dans ~/.ssh/config.

Comment utiliser SSH dans un pipeline CI sans StrictHostKeyChecking no ?

ssh-keyscan -H votre.serveur.com >> ~/.ssh/known_hosts avant la première connexion.

Pour aller plus loin

  • ssh-audit - auditer votre configuration SSH
  • SSH Mastery de Michael W. Lucas - le livre le plus complet sur le sujet
  • man ssh_config - toutes les directives avec exemples
  • man sshd_config - toutes les directives serveur