Architecture par crates

Référence structurelle de gitrust : crates, modules, routes, dépendances et composants CI.

Dépendances entre crates

graph LR
    RW["rustwarden-core"]

    Git["gitrust-git"]
    Core["gitrust-core"]
    Hooks["gitrust-hooks"]
    SSH["gitrust-ssh"]
    SshGuard["gitrust-ssh-guard"]
    Web["gitrust-web"]
    Bin["gitrust (binaire)"]

    Core --> RW
    Git -.->|"indépendant
(git2, tokio)"| RW Hooks --> Core Hooks --> RW SSH --> Core SSH --> Git SSH --> SshGuard SshGuard -.->|"SeaORM bans/ACL/events"| Core Web --> Core Web --> Git Web -.->|"CI pages"| Git Web -.->|"admin ACL/ban via Extension"| SshGuard Bin --> Core Bin --> Git Bin --> SSH Bin --> SshGuard Bin --> Web Bin --> Hooks Bin --> RW subgraph "Externes CI (Phase 5b)" Dagger["Dagger CLI"] CiEng["ci-engine (Python)"] Syft["Syft"] DTrack["Dependency-Track"] end Core -.->|"CiWorker
sous-processus"| Dagger Dagger -.-> CiEng Core -.-> Syft Syft -.-> DTrack

Crates — Responsabilités

gitrust-core

Cœur métier. Ne dépend que de rustwarden-core et des crates Rust standards.

Module Rôle
config GitrustConfig::from_env() — ports SSH, chemin repos, limites
error GitrustError — erreurs domaine Git + IntoResponse HTTP
roles enum Role { Reader, Developer, Maintainer, Owner } avec Ord
types Newtypes validés : RepoSlug, TeamSlug, Fingerprint, TokenHash
models/ Entités SeaORM : repository, ssh_key, team, team_member, team_repository_access, ci_pipeline, ci_job, ci_log, ci_config, ci_variable
migrations/ Migrations SeaORM (tables gitrust) combinées avec les migrations core
services/ SshKeyService, RepositoryService, TeamService, AccessService, CiService, CiVariableService, CiDetectionService, CiWorker, NotificationService
dto/ Structures d'entrée/sortie pour les services

gitrust-git

Opérations Git sur le système de fichiers. Utilise git2 (libgit2). Pas de dépendance vers la base de données.

Module Rôle
errors enum GitError (Git, RepoNotFound, RefNotFound, PathTraversal, ...)
bare_repo Init, open, delete, exists + validation chemin canonique sous base
branch / tag Listing branches (BranchInfo) et tags (TagInfo, annotated/lightweight)
reference resolve_ref (branch -> tag -> SHA -> revparse)
tree_browser list_tree (dirs first, sorted), TreeEntry, EntryKind
blob_reader read_blob -> Text/Binary, détection binaire (null bytes)
commit_log list_commits (paginé), find_commit, CommitInfo
readme find_readme (README.md > README > readme.md > ...)
pack_protocol advertise_refs, serve_pack (async, git-upload-pack/git-receive-pack)

gitrust-ssh

Serveur SSH basé sur russh. Authentification par clé publique.

Module (prévu) Rôle
server Démarrage, génération clé hôte Ed25519, wrapper du TcpListener par SecureListener (ssh-guard)
auth Authentification par fingerprint -> SshKeyService, appel à AuthTracker.record_auth_attempt après chaque tentative
session Handler SSH (exec, shell), porte ClientIdentity du listener jusqu'aux décisions auth
command_handler Parsing git-upload-pack/git-receive-pack

gitrust-ssh-guard

Couche de durcissement SSH intercalée entre le TcpListener et russh. Voir la page dédiée Crate gitrust-ssh-guard pour le détail des modules et l'API publique. Synthèse :

Module Rôle
config GuardConfig::from_env() — lecture SSH_GUARD_*, sélection DeploymentProfile, validation
runtime GuardHandles::build(db) — assemblage partageable entre serveur SSH et routeur admin
listener SecureListener wrappe TcpListener : extraction IP réelle (PROXY v1/v2), ACL, flood, retour AcceptOutcome
proxy Parseur PROXY protocol v1/v2 avec timeout
identity ClientIdentity — IP + session_id + user/fingerprint enrichis pendant l'auth
tracker AuthTracker — persiste, émet, dispatche aux détecteurs
ban BanManager + EffectiveStatus — priorité deny > auto_ban > allow > default
events GuardEvent — schéma JSON stable (tag = "event") pour fail2ban / SIEM
sinks TracingSink, FileSink, MultiSink — destination des événements
detector/{brute_force,user_enumeration,key_scanning,connection_flood} 4 motifs d'attaque, push-based
store/{memory,postgres,hybrid} Backend bans/ACL/events — hybrid (RAM + write-through) par défaut

gitrust-web

Interface web SSR (Server-Side Rendering) avec Askama (templates compilés) + HTMX (interactions dynamiques). Fonctionne en mode headless() : les pages UI du framework (rustwarden-ui) ne sont pas montées, gitrust-web réimplémente toutes les pages avec un design spécifique (sidebar contextuelle, navigation Git, DaisyUI).

Architecture des routes :

gitrust (port 4000)
├── /api/v1/auth/*          ← Routes API du framework (JSON)
│   ├── POST /login            AuthService::authenticate_user
│   ├── POST /register         UserService::create_user
│   ├── POST /refresh          RefreshTokenService (rotation JWT)
│   ├── POST /logout           JwtBlacklistService
│   ├── GET  /me               Claims extraction
│   ├── POST /forgot-password  PasswordResetService::request_reset
│   ├── POST /reset-password   PasswordResetService::reset_password
│   ├── GET  /verify-email/{t} EmailValidationService::verify_email
│   └── POST /resend-verif     EmailValidationService::resend
├── /api/v1/*                ← API REST Gitrust (JSON, auth JWT/PAT)
│   ├── /user, /user/repos     Profil utilisateur
│   ├── /repos/{o}/{r}         Détail dépôt, branches, tags, commits
│   ├── /repos/{o}/{r}/issues  CRUD issues + commentaires
│   ├── /repos/{o}/{r}/pulls   CRUD PRs + merge
│   └── /repos/{o}/{r}/ci/*   Pipelines CI (list, trigger, cancel, logs)
├── /*                       ← Pages SSR gitrust (HTML)
│   ├── /login, /register       Pages formulaires (design gitrust)
│   ├── /dashboard, /teams      Pages utilisateur
│   ├── /settings/*             Profile, SSH keys, sessions, notifications
│   ├── /notifications          Liste notifications + SSE stream
│   ├── /admin/*                Administration (+ admin CI)
│   ├── /{owner}/{repo}/*       Navigation dépôt Git
│   └── /{owner}/{repo}/ci/*    Pages CI (pipelines, logs, config, variables)
└── /static/*                ← Assets locaux (CSS, JS, HTMX)

Les routes API du framework sont montées via framework_api_routes() dans routes.rs. Elles sont utilisées par : - Les liens dans les emails (vérification, reset password) - Le refresh token (rotation JWT, session longue durée) - L'accès programmatique (GET /me, POST /logout)

Les pages SSR et les routes API coexistent : les formulaires HTML soumettent aux handlers gitrust (SSR), tandis que les services framework génèrent des liens vers les routes API.

Module Rôle
routes.rs Routeur principal : framework API + API REST Gitrust + pages SSR + fichiers statiques
handlers/ Handlers Axum SSR (un fichier par domaine) + handlers API JSON
templates.rs Structs Askama + SidebarContext + RepoNav
helpers.rs require_auth, require_admin, sidebar_*, resolve_repo_access
templates/ Templates HTML (Askama, extends base.html)
static_files.rs ServeDir pour /static

gitrust-hooks

Implémentation de RustwardenHooks pour réagir aux événements du framework. Les hooks sont injectés dans le routeur Axum via Arc<dyn RustwardenHooks> + axum::Extension (dans main.rs).

Hook Action
on_user_registered Crée le répertoire utilisateur sur le FS (anti path-traversal)
on_user_deleted Supprime le répertoire utilisateur + bare repos sur le FS
on_resource_shared Audit log si resource_type = "repository"
on_resource_unshared Audit log si resource_type = "repository"

Notifications

Système de notification à deux canaux : in-app (SSE temps réel + page) et email (worker SMTP du framework).

Événement (pipeline échoue, PR commentée, etc.)
NotificationService::notify(user_id, event_type, title, body, link)
    ├── INSERT INTO notifications (in-app)
    │     → broadcast::Sender → SSE /notifications/stream
    │     → Badge header mis à jour en temps réel (HTMX)
    └── if user.email_on_{event} = true:
          EmailQueueService::enqueue() (framework SMTP worker)

Les préférences de notification sont par utilisateur (email on/off par type d'événement).


CI/CD — Modes et composants

Système CI hybride à deux niveaux, activé via CI_ENABLED=true.

Modes

Mode Détection Exécution
Easy .gitrust-ci.yml à la racine du repo Module Dagger Python générique (ci-engine) interprète le YAML
Power .dagger/ à la racine du repo dagger call -m .dagger/ exécute le module utilisateur directement
Aucun Ni l'un ni l'autre Pas de CI pour ce repo

Architecture CI

git push (HTTP ou SSH)
receive-pack handler
    ├── CiDetectionService::detect(bare_repo, commit_sha)
    │     → Easy / Power / None
    ├── CiService::create_pipeline(repo_id, sha, mode)
    └── mpsc::Sender.send(job) ──► CiWorker (tokio::spawn)
                                       ├── 1. Validation statique
                                       │     Easy: schéma YAML, whitelist images/packages
                                       │     Power: analyse code .dagger/ (si activé)
                                       ├── 2. SBOM Gate (si CI_SBOM_ENABLED)
                                       │     syft scan → sbom.cdx.json
                                       │     → Dependency-Track (si CI_DTRACK_ENABLED)
                                       │     → PASS/FAIL selon politique
                                       ├── 3. Résolution variables (héritage team → repo)
                                       │     CiVariableService::resolve_for_pipeline()
                                       ├── 4. Exécution Dagger (isolé, timeout, cgroups)
                                       │     Easy: dagger call -m ci-engine test --profile=...
                                       │     Power: dagger call -m .dagger/ <function>
                                       └── 5. Rapport + audit
                                             Status, logs (broadcast SSE), ci_audit_log

Composants externes (tous optionnels sauf Dagger)

Composant Rôle Variable de contrôle
Dagger Engine Exécution des pipelines dans des containers isolés CI_ENABLED
ci-engine (Python) Moteur générique pour Easy Mode CI_DAGGER_MODULE_PATH
Syft Génération SBOM CycloneDX CI_SBOM_ENABLED
Dependency-Track Analyse de vulnérabilités sur les SBOM CI_DTRACK_ENABLED

Variables et secrets CI

Héritage team → repo : les variables définies au niveau team sont partagées entre tous les repos de l'équipe. Le repo peut surcharger une clé team avec sa propre valeur. Les secrets sont chiffrés en base (AES-256-GCM) et masqués dans les logs (***).


Sécurité (ANSSI PA-074)

Directive Scope
#![forbid(unsafe_code)] gitrust-core, gitrust-web, gitrust-hooks, gitrust-ssh-guard
#![deny(unsafe_code)] gitrust-git, gitrust-ssh (FFI libgit2/russh)
deny(clippy::unwrap_used, expect_used, panic) Tous les crates
deny(clippy::indexing_slicing) Tous les crates
deny(clippy::mem_forget) Tous les crates
Zeroize on drop TokenHash (ANSSI R23)
RSA minimum 4096 bits SshKeyService
Path traversal validation RepositoryService, gitrust-git
Slug validation + noms réservés RepoSlug, TeamSlug