Architecture by crates

gitrust structural reference: crates, modules, routes, dependencies and CI components.

Dependencies between 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 — Responsibilities

gitrust-core

Core business. Depends only on rustwarden-core and standard Rust crates.

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

Git operations on the file system. Use git2 (libgit2). No dependency on the database.

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

SSH server based on russh. Public key authentication.

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

SSR (Server-Side Rendering) web interface with Askama (compiled templates) + HTMX (dynamic interactions). Works in headless() mode: the framework UI pages (rustwarden-ui) are not mounted, gitrust-web reimplements all pages with a specific design (contextual sidebar, Git navigation, DaisyUI).

Road architecture:

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 : - Links in emails (verification, reset password) - The refresh token (JWT rotation, long-term session) - Programmatic access (GET /me, POST /logout)

SSR pages and API routes coexist: HTML forms submit to gitrust handlers (SSR), while framework services generate links to API routes.

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

Implemented RustwardenHooks to react to framework events. The hooks are injected into the Axum router via Arc<dyn RustwardenHooks> + axum::Extension (in 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

Two-channel notification system: in-app (real-time SSE + page) and email (framework SMTP worker).

É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)

Notification preferences are per user (email on/off by event type).


CI/CD — Modes and components

Hybrid two-level CI system, enabled via CI_ENABLED=true.

Fashions

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

CI architecture

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

External components (all optional except 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

CI variables and secrets

Inheritance team → repo: variables defined at team level are shared between all repos of the team. The repo can overload a team key with its own value. The secrets are base encrypted (AES-256-GCM) and hidden in the logs (***).


Security (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