Dépanner les problèmes SSH (clés, ports, fingerprints)¶
À qui s'adresse cette page¶
Administrateurs confrontés à des erreurs lors de l'authentification SSH ou des opérations git clone/git push via SSH sur gitrust.
Architecture SSH de gitrust¶
Gitrust intègre son propre serveur SSH via la bibliothèque Russh — ce n'est pas le démon sshd du système. Il écoute par défaut sur le port 2222. Depuis l'introduction de la crate gitrust-ssh-guard, toute connexion TCP passe d'abord par un sas (SecureListener) qui peut la dropper avant le handshake SSH (ban, flood, en-tête PROXY invalide). Voir Configurer ssh-guard et ssh-guard : détection d'attaques SSH.
sequenceDiagram
participant U as Client git (utilisateur)
participant G as ssh-guard (SecureListener)
participant R as Serveur gitrust (Russh :2222)
participant DB as PostgreSQL
U->>G: Connexion TCP (port 2222)
G->>G: ACL / ban / flood
alt Drop
G-->>U: Connexion fermée
else Accept
G->>R: TcpStream + ClientIdentity
R->>U: Présente clé hôte Ed25519
U->>U: Vérifie fingerprint (known_hosts)
U->>R: Envoie clé publique SSH
R->>DB: Cherche fingerprint dans ssh_keys
DB-->>R: Trouvé (user_id)
R->>G: AuthTracker.record_auth_attempt
R-->>U: Authentifié
U->>R: git-upload-pack / git-receive-pack
end
Erreur 1 : Connection refused sur le port 2222¶
Diagnostic :
# Depuis le serveur : vérifier que gitrust écoute
ss -tlnp | grep 2222
# Attendu : LISTEN 0 ... *:2222 ... gitrust
# Depuis l'extérieur
nc -zv SERVEUR 2222
Causes et corrections :
| Cause | Correction |
|---|---|
| gitrust n'est pas démarré | sudo systemctl start gitrust |
SSH_PORT mal configuré dans .env |
Vérifier SSH_PORT=2222 et redémarrer |
| Firewall bloque le port | sudo ufw allow 2222/tcp ou règle iptables équivalente |
SSH_LISTEN_ADDR=127.0.0.1 |
Le serveur SSH n'est accessible que localement — changer en 0.0.0.0 si accès externe souhaité |
Erreur 2 : WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED¶
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING A MITM ATTACK!
Cause : la clé SSH hôte du serveur a changé (migration vers un nouveau serveur, régénération de la clé, restauration depuis une sauvegarde différente).
Vérifier le nouveau fingerprint :
# Sur le serveur
sudo -u gitrust ssh-keygen -l -f /opt/gitrust/data/ssh_host_ed25519_key.pub
# Exemple : SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX gitrust-host-key (ED25519)
Si ce fingerprint correspond à un changement légitime (migration, etc.), demandez aux utilisateurs de supprimer l'ancienne entrée de leur known_hosts :
# Sur la machine du client git
ssh-keygen -R "[SERVEUR]:2222"
# Puis re-cloner ou accepter le nouveau fingerprint
Si vous ne reconnaissez pas ce changement, il peut indiquer une compromission. Investiguez avant de continuer.
Erreur 3 : Permission denied (publickey)¶
Diagnostic pas à pas :
# 1. Tester la connexion avec verbosité maximale
ssh -vvv -p 2222 git@SERVEUR
# 2. Vérifier quelle clé est proposée par le client
# Dans la sortie -vvv, chercher :
# debug1: Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:...
# debug1: Authentications that can continue: publickey
# 3. Obtenir le fingerprint de votre clé locale
ssh-keygen -l -f ~/.ssh/id_ed25519.pub
# SHA256:YYYYYYYYYYYYYYYYYYYYYYYYYYYYYY (ED25519)
Comparez ce fingerprint avec celui enregistré dans gitrust pour votre compte :
- Connectez-vous à l'interface web gitrust
- Accédez à Paramètres → Clés SSH
- Vérifiez que le fingerprint correspond
Causes courantes :
| Cause | Correction |
|---|---|
| Aucune clé SSH enregistrée dans gitrust | Ajouter la clé publique dans les paramètres du compte |
| La clé proposée par le client n'est pas celle enregistrée | Vérifier ~/.ssh/config et l'agent SSH |
| L'utilisateur est désactivé dans gitrust | L'administrateur doit réactiver le compte via /admin/users |
| La clé enregistrée a été révoquée | Enregistrer une nouvelle clé publique |
Vérifier les permissions des fichiers de clés côté client :
Erreur 4 : Could not resolve hostname¶
Cause : le nom de domaine dans l'URL de clone ne correspond pas à SSH_PUBLIC_HOST ou n'est pas résolvable.
Vérifier la configuration :
# Dans .env
grep SSH_PUBLIC_HOST /opt/gitrust/.env
# Doit correspondre au FQDN ou à l'IP utilisé dans les URLs de clone
# Tester la résolution DNS
nslookup VOTRE_SSH_PUBLIC_HOST
Erreur 5 : Timeout ou connexion très lente¶
ou connexion qui s'établit mais met 30+ secondes.
Causes et corrections :
| Cause | Correction |
|---|---|
| Résolution DNS inverse lente | Ajouter UseDNS no dans la config SSH hôte (non applicable à Russh — configurable via SSH_LISTEN_ADDR côté gitrust) |
| Firewall qui filtre silencieusement (DROP vs REJECT) | nc -zv SERVEUR 2222 pour distinguer refused (REJECT) de timeout (DROP) |
| Trop de connexions simultanées | Vérifier les logs gitrust pour des erreurs de pool de threads |
Erreur 6 : la connexion est droppée par ssh-guard¶
Symptôme côté client : la connexion TCP est acceptée puis fermée immédiatement, sans aucun message d'authentification SSH. Ce comportement est typique d'un drop ssh-guard (ban actif, flood, en-tête PROXY refusé).
Diagnostic :
# Si SSH_GUARD_LOG_TARGET=both ou file
sudo tail -100 /var/log/gitrust-ssh-guard.json \
| jq 'select(.event=="connection_dropped")'
# Sinon (target=stderr) — passer par journald
sudo journalctl -u gitrust --since "5 min ago" --no-pager \
| grep '"event":"connection_dropped"' | tail -10
reason retourné |
Cause | Correction |
|---|---|---|
banned |
IP couverte par un ban actif (auto ou denylist admin) | Lever le ban via la table ssh_guard_bans ou attendre l'expiration. Voir Configurer ssh-guard §10.4. |
flood_limit |
Trop de connexions/sec depuis cette IP | Allowlister l'IP si c'est un partenaire CI, ou monter SSH_GUARD_CONN_FLOOD_PER_SEC. |
untrusted_proxy |
En-tête PROXY reçu d'une IP non listée | Vérifier SSH_GUARD_TRUSTED_PROXIES ; si nginx tourne sur le même hôte, le défaut 127.0.0.1/32,::1/128 du profil nginx doit suffire. |
proxy_header_invalid ou proxy_header_missing |
nginx/HAProxy n'envoie pas le header attendu | Vérifier proxy_protocol on; dans nginx (ou send-proxy-v2 dans HAProxy). En transition, basculer temporairement SSH_GUARD_PROXY_PROTOCOL_STRICT=false. |
Diagnostic ssh-guard rapide¶
Lire le flux d'événements en direct¶
# Dernier événement par IP (ssh-guard log target = stderr/journald)
sudo journalctl -u gitrust -f | grep '"event":"' | jq -c '.'
# Idem si SSH_GUARD_LOG_TARGET=file ou both
sudo tail -f /var/log/gitrust-ssh-guard.json | jq -c '.'
Mode dry-run pour reproduire un blocage sans persister¶
Si vous suspectez qu'un seuil est trop agressif, passez SSH_GUARD_DRY_RUN=true dans .env puis redémarrez. Les détecteurs continuent à émettre les ip_banned (signal pour fail2ban / observabilité) sans que ssh-guard pose réellement le ban. Vous pouvez observer le motif sur 24-48 h avant de décider.
Vérifier que la vraie IP est bien extraite (profils nginx/haproxy)¶
# Faire un git ls-remote depuis une IP externe connue, puis :
sudo journalctl -u gitrust --since "1 min ago" --no-pager \
| grep '"event":"connection_accepted"' | tail -1 | jq .ip
# Doit retourner l'IP externe, PAS 127.0.0.1
Désactiver temporairement ssh-guard¶
ssh-guard devient un pass-through complet (aucune détection, aucun ban). À utiliser seulement pour confirmer qu'un problème vient bien de la couche guard ; remettre true immédiatement après.
Pour le détail des événements, voir Événements ssh-guard (JSON).
Consulter les logs SSH¶
# Logs du service gitrust (contient les événements SSH)
sudo journalctl -u gitrust -f | grep -i "ssh\|auth\|key\|fingerprint"
# Filtrer les erreurs d'authentification
sudo journalctl -u gitrust --since "1 hour ago" --no-pager \
| grep -i "auth.*failed\|permission denied\|invalid key"