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
ssh: connect to host SERVEUR port 2222: Connection refused
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)
git@SERVEUR: Permission denied (publickey). fatal: Could not read from remote repository.
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 :
chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519 chmod 644 ~/.ssh/id_ed25519.pub
Erreur 4 : Could not resolve hostname
ssh: Could not resolve hostname SERVEUR: Name or service not known
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
ssh: connect to host SERVEUR port 2222: Operation timed out
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
# .env SSH_GUARD_ENABLED=false sudo systemctl restart gitrust
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"
GitRust