Arquitectura por cajas¶
Referencia estructural de gitrust: cajas, módulos, rutas, dependencias y componentes de CI.
Dependencias entre cajas¶
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
Cajas - Responsabilidades¶
núcleo gitrust¶
Negocio principal. Depende únicamente de las cajas Rustwarden-core y Rust estándar.
| 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¶
Operaciones de Git en el sistema de archivos. Utilice git2 (libgit2). Sin dependencia de la base de datos.
| 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¶
Servidor SSH basado en russh. Autenticación de clave pública.
| 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¶
Interfaz web SSR (Server-Side Rendering) con Askama (plantillas compiladas) + HTMX (interacciones dinámicas). Funciona en modo headless(): las páginas de la interfaz de usuario del marco (rustwarden-ui) no están montadas, gitrust-web reimplementa todas las páginas con un diseño específico (barra lateral contextual, navegación Git, DaisyUI).
Arquitectura vial:
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 :
- Enlaces en correos electrónicos (verificación, restablecer contraseña)
- El token de actualización (rotación JWT, sesión a largo plazo)
- Acceso programático (GET /me, POST /logout)
Las páginas SSR y las rutas API coexisten: los formularios HTML se envían a los controladores gitrust (SSR), mientras que los servicios del marco generan enlaces a las rutas 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 |
ganchos-gitrust¶
Se implementó RustwardenHooks para reaccionar a los eventos del marco. Los ganchos se inyectan en el enrutador Axum a través de Arc<dyn RustwardenHooks> + axum::Extension (en 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" |
Notificaciones¶
Sistema de notificación de dos canales: en la aplicación (página SSE + en tiempo real) y correo electrónico (trabajador SMTP del marco).
É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)
Las preferencias de notificación son por usuario (correo electrónico activado/desactivado por tipo de evento).
CI/CD: modos y componentes¶
Sistema CI híbrido de dos niveles, habilitado a través de CI_ENABLED=true.
Modas¶
| 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 |
Arquitectura de 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
Componentes externos (todos opcionales excepto 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 y secretos de CI¶
Herencia equipo → repositorio: las variables definidas a nivel de equipo se comparten entre todos los repositorios del equipo. El repositorio puede sobrecargar una clave de equipo con su propio valor. Los secretos están cifrados de base (AES-256-GCM) y ocultos en los registros (***).
Seguridad (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 |