architecture-crates.md 12864 octets

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<br/>(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<br/>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.

ModuleRôle
configGitrustConfig::from_env() — ports SSH, chemin repos, limites
errorGitrustError — erreurs domaine Git + IntoResponse HTTP
rolesenum Role { Reader, Developer, Maintainer, Owner } avec Ord
typesNewtypes 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.

ModuleRôle
errorsenum GitError (Git, RepoNotFound, RefNotFound, PathTraversal, ...)
bare_repoInit, open, delete, exists + validation chemin canonique sous base
branch / tagListing branches (BranchInfo) et tags (TagInfo, annotated/lightweight)
referenceresolve_ref (branch -> tag -> SHA -> revparse)
tree_browserlist_tree (dirs first, sorted), TreeEntry, EntryKind
blob_readerread_blob -> Text/Binary, détection binaire (null bytes)
commit_loglist_commits (paginé), find_commit, CommitInfo
readmefind_readme (README.md > README > readme.md > ...)
pack_protocoladvertise_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
serverDémarrage, génération clé hôte Ed25519, wrapper du TcpListener par SecureListener (ssh-guard)
authAuthentification par fingerprint -> SshKeyService, appel à AuthTracker.record_auth_attempt après chaque tentative
sessionHandler SSH (exec, shell), porte ClientIdentity du listener jusqu'aux décisions auth
command_handlerParsing 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 :

ModuleRôle
configGuardConfig::from_env() — lecture SSH_GUARD_*, sélection DeploymentProfile, validation
runtimeGuardHandles::build(db) — assemblage partageable entre serveur SSH et routeur admin
listenerSecureListener wrappe TcpListener : extraction IP réelle (PROXY v1/v2), ACL, flood, retour AcceptOutcome
proxyParseur PROXY protocol v1/v2 avec timeout
identityClientIdentity — IP + session_id + user/fingerprint enrichis pendant l'auth
trackerAuthTracker — persiste, émet, dispatche aux détecteurs
banBanManager + EffectiveStatus — priorité deny > auto_ban > allow > default
eventsGuardEvent — schéma JSON stable (tag = "event") pour fail2ban / SIEM
sinksTracingSink, 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.

ModuleRôle
routes.rsRouteur principal : framework API + API REST Gitrust + pages SSR + fichiers statiques
handlers/Handlers Axum SSR (un fichier par domaine) + handlers API JSON
templates.rsStructs Askama + SidebarContext + RepoNav
helpers.rsrequire_auth, require_admin, sidebar_*, resolve_repo_access
templates/Templates HTML (Askama, extends base.html)
static_files.rsServeDir 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).

HookAction
on_user_registeredCrée le répertoire utilisateur sur le FS (anti path-traversal)
on_user_deletedSupprime le répertoire utilisateur + bare repos sur le FS
on_resource_sharedAudit log si resource_type = "repository"
on_resource_unsharedAudit 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

ModeDétectionExécution
Easy.gitrust-ci.yml à la racine du repoModule Dagger Python générique (ci-engine) interprète le YAML
Power.dagger/ à la racine du repodagger call -m .dagger/ exécute le module utilisateur directement
AucunNi l'un ni l'autrePas 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)

ComposantRôleVariable de contrôle
Dagger EngineExécution des pipelines dans des containers isolésCI_ENABLED
ci-engine (Python)Moteur générique pour Easy ModeCI_DAGGER_MODULE_PATH
SyftGénération SBOM CycloneDXCI_SBOM_ENABLED
Dependency-TrackAnalyse de vulnérabilités sur les SBOMCI_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)

DirectiveScope
#![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 dropTokenHash (ANSSI R23)
RSA minimum 4096 bitsSshKeyService
Path traversal validationRepositoryService, gitrust-git
Slug validation + noms réservésRepoSlug, TeamSlug