architecture-globale.md 8744 octets

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<br/>:4000]
        GUARD[ssh-guard<br/>SecureListener + détecteurs]
        SSH[Serveur SSH Russh<br/>:2222]
        CIW[Worker CI async<br/>Dagger]
        IMW[Worker Import async<br/>git clone]
        EMW[Worker Email async<br/>SMTP queue]
    end

    subgraph "Dépendances externes"
        PG[(PostgreSQL<br/>:5432)]
        FS[Système de fichiers<br/>GIT_REPOS_BASE_PATH]
        SMTP[Serveur SMTP<br/>sortant]
        DAGGER[Dagger Engine<br/>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