Hooks gitrust — Référence¶
Ce document décrit les hooks exposés par crates/gitrust-hooks/, quand ils sont appelés, leur payload, leurs effets de bord, et comment les tester localement.
Vue d'ensemble¶
gitrust-hooks implémente le trait RustwardenHooks de rustwarden-core. Ce trait est un mécanisme de lifecycle : le framework appelle les hooks aux moments clés du cycle de vie des ressources (inscription utilisateur, suppression, partage).
// crates/gitrust-hooks/src/lib.rs
#[async_trait]
impl RustwardenHooks for GitrustHooks {
async fn on_user_registered(...) -> anyhow::Result<()>;
async fn on_resource_shared(...) -> anyhow::Result<()>;
async fn on_user_deleted(...) -> anyhow::Result<()>;
async fn on_resource_unshared(...) -> anyhow::Result<()>;
}
Les hooks Git serveur (receive-pack, upload-pack) sont implémentés directement dans gitrust-web/src/handlers/git_http.rs et dans le serveur SSH (gitrust-ssh).
Hooks de lifecycle (RustwardenHooks)¶
on_user_registered¶
Déclencheur : après la création réussie d'un compte utilisateur (via /register ou l'API /api/v1/auth/register).
Payload :
async fn on_user_registered(
db: &DatabaseConnection,
user_id: Uuid,
username: &str,
) -> anyhow::Result<()>
Effet de bord : crée le répertoire de dépôts de l'utilisateur sur le système de fichiers :
Validation de sécurité : le username est validé contre la traversal de chemin (.., /, \) avant toute opération disque. Un username suspect déclenche un log WARN et retourne une erreur — le compte n'est pas créé.
Idempotence : si le répertoire existe déjà, l'appel réussit silencieusement.
on_user_deleted¶
Déclencheur : juste avant la suppression d'un utilisateur depuis le panel admin.
Payload :
async fn on_user_deleted(
db: &DatabaseConnection,
user_id: Uuid,
username: &str,
) -> anyhow::Result<()>
Effets de bord :
1. Supprime le répertoire disque {REPOS_BASE_PATH}/{username}/ et tous les bare repos qu'il contient.
2. Enregistre un événement user_deleted dans audit_logs (niveau WARN).
Note : la cascade DB (suppression des lignes repositories) est gérée par la contrainte FK ON DELETE CASCADE. Ce hook gère uniquement le système de fichiers.
En cas d'erreur disque (permissions, NFS indisponible), l'erreur est loggée en WARN mais ne bloque pas la suppression en base.
on_resource_shared¶
Déclencheur : après un appel réussi à ResourceService::share() — quand un dépôt est partagé avec un collaborateur.
Payload :
async fn on_resource_shared(
db: &DatabaseConnection,
resource_type: &str,
resource_id: Uuid,
shared_with_user_id: Uuid,
permission_level: &str,
shared_by_user_id: Uuid,
) -> anyhow::Result<()>
Effet de bord : si resource_type == "repository", enregistre l'événement REPO_SHARED dans audit_logs avec les détails du partage.
on_resource_unshared¶
Déclencheur : après un appel réussi à ResourceService::revoke_share() ou revoke_user_access().
Payload :
async fn on_resource_unshared(
db: &DatabaseConnection,
resource_type: &str,
resource_id: Uuid,
user_id: Uuid,
) -> anyhow::Result<()>
Effet de bord : si resource_type == "repository", enregistre l'événement REPO_UNSHARED dans audit_logs.
Hooks Git serveur (receive-pack / upload-pack)¶
Ces hooks ne sont pas dans gitrust-hooks mais dans les handlers HTTP et SSH.
Receive-pack (après un git push)¶
Déclencheur : POST /{owner}/{repo}/git-receive-pack (HTTP) ou connexion SSH suivie d'un git-receive-pack.
Séquence d'effets :
sequenceDiagram
participant Client as git push
participant Handler as receive_pack handler
participant DB as Base de données
participant CI as CI Worker
participant WH as Webhook Dispatcher
Client->>Handler: objets packés
Handler->>DB: RepositoryService::mark_non_empty() si premier push
Handler->>DB: CiService::create_pipeline() si .gitrust-ci.yml détecté
Handler->>CI: mpsc::send(CiTask)
Handler->>DB: WebhookService::list_by_repo_and_event("push")
Handler->>WH: tokio::spawn → deliver(webhook, "push", payload)
Handler-->>Client: 200 OK (pack-protocol)
Payload webhook push :
{
"repository": {
"id": "...",
"slug": "myrepo",
"owner": "alice"
},
"pusher": { "username": "alice" },
"ref": "refs/heads/main",
"before": "0000000000000000000000000000000000000000",
"after": "abc123...",
"commits": [
{
"sha": "abc123...",
"message": "feat: ajouter la signature HMAC",
"author": { "name": "Alice", "email": "alice@example.com" }
}
]
}
Upload-pack (après un git fetch / git clone)¶
Déclencheur : GET /{owner}/{repo}/info/refs?service=git-upload-pack + POST /{owner}/{repo}/git-upload-pack.
Effets de bord : aucun effet de bord métier. Vérification des permissions d'accès (public → lecture libre ; privé → authentification requise).
Hooks SBOM (post-push)¶
Après chaque push sur un dépôt avec SBOM_ENABLED=true, un job SBOM est déclenché via SbomService :
SbomService::trigger_scan(db, repo_id, commit_sha)crée un job dansci_jobs.- Le worker CI exécute
syftsur le répertoire de travail. - Le résultat est uploadé vers Dependency-Track via HTTP.
- Les findings critiques peuvent bloquer la CI selon la configuration
sbom_block_on_violation.
Tester les hooks localement¶
Tester on_user_registered¶
// tests/hooks_test.rs
#[tokio::test]
async fn on_user_registered_creates_dir() {
let tmp = tempfile::TempDir::new().unwrap();
let hooks = GitrustHooks::new(tmp.path().to_path_buf());
let db = sea_orm::Database::connect("sqlite::memory:").await.unwrap();
hooks
.on_user_registered(&db, Uuid::new_v4(), "alice")
.await
.unwrap();
assert!(tmp.path().join("alice").exists());
}
#[tokio::test]
async fn on_user_registered_rejects_traversal() {
let tmp = tempfile::TempDir::new().unwrap();
let hooks = GitrustHooks::new(tmp.path().to_path_buf());
let db = sea_orm::Database::connect("sqlite::memory:").await.unwrap();
assert!(hooks
.on_user_registered(&db, Uuid::new_v4(), "../etc/passwd")
.await
.is_err());
}
Simuler un push en local¶
Pour tester le cycle complet receive-pack → CI → webhook sans client réel :
# 1. Démarrer gitrust localement
cargo run
# 2. Créer un dépôt via l'UI, cloner en SSH ou HTTP
git clone http://localhost:4000/alice/myrepo.git
# 3. Ajouter un fichier et pusher
cd myrepo
echo "hello" > README.md
git add README.md && git commit -m "test push"
git push origin main
# 4. Vérifier les effets de bord dans les logs
# → Log "Created pipeline" si .gitrust-ci.yml présent
# → Log "Delivered webhook" si webhook configuré
Ajouter un nouveau hook¶
Pour réagir à un nouvel événement de lifecycle :
- Définissez la méthode dans le trait
RustwardenHooks(dansrustwarden-core/src/hooks.rs). - Implémentez la méthode dans
GitrustHooks(dansgitrust-hooks/src/lib.rs). - Appelez le hook depuis le service rustwarden-core approprié.
- Écrivez un test unitaire avec
tempfile::TempDiretsea_orm::Database::connect("sqlite::memory:").
Règle : les hooks ne doivent jamais bloquer un flux utilisateur critique. En cas d'erreur non fatale (écriture disque, audit log), loggez en WARN et retournez Ok(()).