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 :

  1. Connectez-vous à l'interface web gitrust
  2. Accédez à ParamètresClés SSH
  3. 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"

Pour aller plus loin