Comprendre l'architecture globale de gitrust

Ce que vous allez comprendre

  • Identifier les processus qui s'exécutent au sein d'une instance gitrust et leur rôle respectif
  • Analyser le chemin d'une requête HTTP et d'un push SSH du client jusqu'à la réponse
  • Évaluer les implications opérationnelles de cette architecture pour les décisions d'administration (sauvegarde, mise à l'échelle, durcissement)

Le problème concret

Vous administrez gitrust et vous devez décider : « Sur quel processus dois-je redémarrer quand je modifie la configuration SMTP ? Pourquoi la clé SSH hôte ne doit-elle jamais être régénérée ? Qu'est-ce qui se passe si PostgreSQL est indisponible pendant 2 minutes ? »

Ces questions n'ont de réponse claire que si vous comprenez l'architecture interne.


L'analogie

Pensez à gitrust comme à un immeuble de bureaux avec un seul accueil (le binaire gitrust) qui abrite plusieurs services distincts :

  • Le guichet HTTP reçoit les navigateurs et les appels API
  • Le guichet SSH reçoit les clients git
  • La salle des archives (PostgreSQL) conserve toutes les données
  • Les ateliers (workers CI, import) font du travail en arrière-plan
  • Le vestiaire (système de fichiers) garde les manteaux (les dépôts bare)

Un seul processus, mais plusieurs « couloirs » internes. Si l'immeuble ferme (redémarrage), tous les guichets ferment simultanément.


Le modèle

Composants au runtime

graph TB
    subgraph "Processus gitrust (binaire unique)"
        AX[Serveur HTTP axum
:4000] GUARD[ssh-guard
SecureListener + détecteurs] SSH[Serveur SSH Russh
:2222] CIW[Worker CI async
Dagger] IMW[Worker Import async
git clone] EMW[Worker Email async
SMTP queue] end subgraph "Dépendances externes" PG[(PostgreSQL
:5432)] FS[Système de fichiers
GIT_REPOS_BASE_PATH] SMTP[Serveur SMTP
sortant] DAGGER[Dagger Engine
Docker] end subgraph "Clients" BR[Navigateur / API] GIT[Client git SSH] CI[Push git → CI trigger] end BR -->|HTTP/HTTPS via nginx| AX GIT -->|SSH :2222| GUARD GUARD -->|"AcceptOutcome::Accepted"| SSH CI -->|git push → hook| CIW AX -->|SeaORM| PG AX -.->|"admin ACL/ban"| GUARD GUARD -->|Bans, ACL, events| PG SSH -->|Lit authorized_keys| PG SSH -->|git-receive-pack| FS AX -->|Lit/écrit dépôts| FS CIW -->|dagger call| DAGGER IMW -->|git clone| FS EMW -->|SMTP| SMTP

Flux d'une requête HTTP (ex. : afficher un dépôt)

sequenceDiagram
    participant B as Navigateur
    participant N as Nginx (TLS)
    participant AX as axum handler
    participant DB as PostgreSQL
    participant FS as Système de fichiers

    B->>N: GET https://gitrust.domain/alice/mon-depot
    N->>AX: proxy_pass http://127.0.0.1:4000/alice/mon-depot
    AX->>DB: SELECT * FROM repositories WHERE owner=alice AND slug=mon-depot
    DB-->>AX: Repository { id, disk_path, ... }
    AX->>FS: git log --oneline HEAD (libgit2)
    FS-->>AX: Liste des commits
    AX-->>N: HTML (template Askama rendu)
    N-->>B: HTTP 200 + HTML

Flux d'un push SSH (ex. : git push origin main)

sequenceDiagram
    participant G as git client
    participant SSH as Russh :2222
    participant DB as PostgreSQL
    participant FS as Dépôt bare (.git)
    participant H as gitrust-hooks

    G->>SSH: Connexion SSH, propose clé publique
    SSH->>DB: SELECT id FROM ssh_keys WHERE fingerprint=SHA256:...
    DB-->>SSH: user_id = alice
    SSH-->>G: Authentifié
    G->>SSH: git-receive-pack /alice/mon-depot.git
    SSH->>DB: Vérifie permission write sur le dépôt
    DB-->>SSH: OK (Developer ou Owner)
    SSH->>FS: Reçoit les objets Git, met à jour les refs
    FS-->>SSH: Succès
    SSH->>H: Déclenche post-receive hook
    H->>DB: Enregistre l'événement push
    H-->>SSH: Fin
    SSH-->>G: remote: OK

Implications opérationnelles

Un seul processus, une seule unité d'arrêt

gitrust est un binaire unique. Redémarrer le service (systemctl restart gitrust) interrompt simultanément : - Le serveur HTTP (quelques secondes d'indisponibilité web) - Le serveur SSH (les push en cours sont coupés) - Les workers CI/Import (les tâches en cours peuvent être interrompues et reprises au prochain démarrage)

Conséquence : planifiez les redémarrages (mise à jour, changement de .env) en dehors des heures de pointe.

PostgreSQL est le point central

Toutes les décisions d'authentification, d'autorisation et de persistance passent par PostgreSQL. Si PostgreSQL est indisponible : - Les connexions HTTP retournent 503 - Les connexions SSH sont refusées (impossible de valider la clé publique) - Les workers mettent leurs tâches en file d'attente locale

Conséquence : la sauvegarde et la haute disponibilité de PostgreSQL sont prioritaires sur tout le reste.

Le système de fichiers et la base de données doivent être cohérents

Un dépôt existe à deux endroits : en base (repositories table) et sur disque (disk_path). Une restauration partielle (base seule, ou disque seul) laisse l'instance dans un état incohérent.

Conséquence : toujours sauvegarder et restaurer la base ET les dépôts ensembles (voir Sauvegarder et restaurer).

ssh-guard intercale un sas devant le serveur SSH

Toutes les connexions sur :2222 passent d'abord par SecureListener (ssh-guard). Cette couche extrait l'IP réelle (PROXY protocol si nginx stream est devant), consulte ACL et bans actifs, applique un cap TCP par IP, puis ne remet le TcpStream à russh que si tout est en règle. Un événement JSON stable est émis pour chaque décision (connection_accepted, connection_dropped, auth_failed, ip_banned, …) — pratique pour fail2ban et SIEM.

Conséquence : modifier SSH_GUARD_* dans .env change immédiatement le comportement défensif après redémarrage. Les ACL admin (allow/deny par CIDR) sont en revanche modifiables à chaud via l'UI : la table ssh_guard_acl est partagée par référence entre le routeur HTTP et le listener SSH. Voir Configurer ssh-guard et ssh-guard : détection d'attaques SSH.

La clé SSH hôte identifie l'instance

La clé Ed25519 dans SSH_HOST_KEY_PATH est l'identité SSH de votre serveur. Tous vos utilisateurs ont ce fingerprint dans leur ~/.ssh/known_hosts. La régénérer provoque une alerte REMOTE HOST IDENTIFICATION HAS CHANGED sur toutes les machines de vos utilisateurs.

Conséquence : sauvegardez la clé SSH hôte et ne la régénérez jamais sauf en cas de compromission avérée.


Alternatives et compromis

Pourquoi un seul binaire plutôt que des microservices ?

gitrust a choisi l'architecture monolithe modulaire (crates Rust séparées, un binaire) pour : - Simplicité opérationnelle : un seul processus à surveiller, une seule unité de déploiement - Performances : pas de sérialisation/désérialisation inter-services sur le chemin critique - Cohérence transactionnelle : les transactions PostgreSQL couvrent plusieurs domaines (auth + repository + audit) sans coordinateur distribué

Le compromis : mise à l'échelle horizontale plus contrainte (voir Modèle de déploiement).

Pourquoi Russh plutôt que sshd ?

L'intégration du serveur SSH dans le processus gitrust permet : - Authentification via la base de données (pas de authorized_keys fichier) - Autorisation au niveau dépôt (pas juste au niveau système) - Logs d'audit unifiés

Le compromis : gitrust gère sa propre implémentation SSH — les mises à jour de sécurité SSH dépendent des releases gitrust.


Vérifier votre compréhension

  1. Un utilisateur ne peut plus faire git push mais peut se connecter à l'interface web. Quel composant est en cause ? Quelles informations cherchez-vous en premier dans les logs ?

  2. Vous devez augmenter la mémoire allouée aux workers CI sans affecter le serveur HTTP. Est-ce possible avec l'architecture actuelle ? Que faudrait-il changer ?


Pour aller plus loin