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 |