bastien-mrq/gitfed / DESIGN.md
DESIGN.md
Code Preview
GitFed — Serveur Git fédéré (nom de code)
1. Résumé
Serveur git auto-hébergé, écrit en Go, accessible en SSH (HTTP non prioritaire), capable de fédérer l'identité et les autorisations entre instances indépendantes. Un utilisateur créé sur une instance peut pousser/tirer sur un repo hébergé par une autre instance, sans y recréer de compte, via un système de certificats SSH signés par l'instance d'origine.
La donnée d'un repo ne vit qu'à un seul endroit (pas de réplication). La fédération ne porte que sur l'identité et l'autorisation, pas sur le transfert d'objets git (qui reste le protocole git standard).
2. Objectifs
- Auth SSH obligatoire pour push (clé/certificat). HTTP: lecture seule, optionnel, non prioritaire.
- Fédération d'identité inter-instances via un protocole custom (pas ForgeFed/ActivityPub).
- Gestion des repos/utilisateurs/ACL via TUI dans un premier temps.
- Pas de réécriture du protocole git : on wrappe
git-receive-pack/git-upload-pack. - UI web plus tard, branchée sur le même noyau identité/ACL.
3. Non-objectifs (pour l'instant)
- Pas de réplication/mirroring automatique de repos entre instances.
- Pas de fork fédéré façon Forgejo/ActivityPub.
- Pas de gestion fine de webhooks/CI dans le MVP.
- Pas de HTTP smart-protocol complet (juste lecture simple si besoin).
4. Vue d'ensemble de l'architecture
┌─────────────────────────────┐ ┌─────────────────────────────┐
│ Instance A │ │ Instance B │
│ ┌───────────────────────┐ │ │ ┌───────────────────────┐ │
│ │ CA locale (ed25519) │ │ │ │ CA locale (ed25519) │ │
│ │ signe les certs users │ │ │ │ signe les certs users │ │
│ └───────────────────────┘ │ │ └───────────────────────┘ │
│ ┌───────────────────────┐ │ HTTPS │ ┌───────────────────────┐ │
│ │ /.well-known/gitfed.json│◄┼────────┼─►│/.well-known/gitfed.json│ │
│ └───────────────────────┘ │ (trust)│ └───────────────────────┘ │
│ ┌───────────────────────┐ │ │ │
│ │ Serveur SSH │◄─────────┼── alice@instanceB.example │
│ │ - vérifie cert │ │ SSH │ (push avec certificat) │
│ │ - résout principal │ │ direct │ │
│ │ - check ACL repo │ │ │ │
│ │ - exec git-receive-pack│ │ │ │
│ └───────────────────────┘ │ │ │
│ ┌───────────────────────┐ │ │ │
│ │ Store: repos + ACL + │ │ │ │
│ │ trust store CA distantes│ │ │ │
│ └───────────────────────┘ │ │ │
└─────────────────────────────┘ └─────────────────────────────┘
5. Modèle d'identité et de confiance
5.1 Principe
- Chaque instance possède une paire de clés CA (ed25519), générée au setup.
- Chaque utilisateur local reçoit, à la connexion/login initial, un certificat
SSH (format OpenSSH
ssh-keygen -s) signé par la CA de son instance.principal=<username>@<domaine-instance>(ex:alice@instanceb.example)valid_after/valid_before: TTL court (24–72h, configurable)key_id: identifiant unique du certificat (pour logs/audit)
- Le client présente ce certificat (pas la clé brute) à toute instance à laquelle il se connecte.
5.2 Découverte et confiance inter-instances
- Chaque instance expose un endpoint HTTPS statique :
https://<domaine>/.well-known/gitfed.json{ "version": 1, "domain": "instanceb.example", "ca_public_key": "ssh-ed25519 AAAA...", "software": "gitfed/0.1.0", "contact": "admin@instanceb.example" } - Quand instance A voit apparaître un principal
xxx@instanceb.example(ex : ajouté comme collaborateur sur un repo), elle va chercher ce endpoint pour récupérer la CA publique deinstanceb.example, si elle ne la connaît pas déjà. - Politique de confiance configurable par l'admin de l'instance :
whitelist(par défaut) : l'admin doit approuver manuellement chaque nouveau domaine avant que la CA soit ajoutée au trust store.auto-trust(opt-in) : la CA est ajoutée automatiquement dès la première rencontre (TOFU — trust on first use).
- Une fois la CA en trust store, la vérification d'un certificat est 100% locale (pas d'appel réseau à chaque connexion SSH) : robustesse et rapidité.
- Révocation : gérée par le TTL court du certificat + renouvellement périodique côté client auprès de son instance d'origine. Pas de CRL dans le MVP.
5.3 Pourquoi ce choix
- Évite un appel réseau "live" à chaque push (contrairement à une vérification à la volée), donc pas de dépendance de disponibilité de l'instance d'origine à chaque opération git.
- Décentralisé : pas de registre central unique, chaque instance décide qui elle trust.
- Réutilise un mécanisme SSH standard et éprouvé (certificats OpenSSH), pas de crypto maison.
6. Modèle d'autorisation (ACL)
- Chaque repo a une ACL locale à l'instance qui l'héberge :
{ "repo": "alice/mon-projet", "owner": "alice@instancea.example", "collaborators": [ { "principal": "bob@instanceb.example", "role": "write" }, { "principal": "carol@instancec.example", "role": "read" } ] } role:read|write|admin.- Ajouter un collaborateur distant déclenche la résolution de confiance (§5.2) si le domaine n'est pas encore connu.
7. Flux d'une opération git distante (push)
- Bob (compte sur
instanceb.example) est ajouté comme collaborateurwritesuralice/mon-projethébergé surinstancea.example. instancea.examplerésout la confiance versinstanceb.example(si pas déjà fait).- Bob se connecte en SSH à
instancea.example, présente son certificat signé par la CA deinstanceb.example. instancea.example:- vérifie la signature du certificat contre la CA connue de
instanceb.example - vérifie le TTL du certificat
- extrait le principal
bob@instanceb.example - vérifie l'ACL du repo demandé
- vérifie la signature du certificat contre la CA connue de
- Si autorisé → exec direct de
git-receive-pack(ougit-upload-packpour un pull) comme pour un utilisateur local. Aucune réplication, la donnée reste surinstancea.example.
8. Structure du projet (Go)
gitfed/
├── cmd/
│ ├── gitfed-server/ # binaire serveur (SSH + endpoint well-known)
│ └── gitfed-tui/ # TUI admin (users, repos, ACL, trust store)
├── internal/
│ ├── ca/ # génération/signature de certificats
│ ├── ssh/ # serveur SSH (golang.org/x/crypto/ssh), handlers
│ ├── federation/ # découverte, trust store, client well-known
│ ├── acl/ # modèle ACL + persistance
│ ├── gitexec/ # wrapping git-receive-pack / git-upload-pack
│ ├── store/ # stockage repos bare + métadonnées (bbolt ou sqlite)
│ └── tui/ # composants Bubble Tea
├── DESIGN.md
└── go.mod
Dépendances clés pressenties :
golang.org/x/crypto/ssh— serveur SSH + certificats natifscharmbracelet/wish— framework SSH-app (bâti sur x/crypto/ssh)charmbracelet/bubbletea+lipgloss— TUImodernc.org/sqliteouetcd-io/bbolt— stockage users/ACL/trust store- git : appelé en sous-processus (
os/exec), pas de réimplémentation du protocole
9. Roadmap par phases
Phase 0 — Squelette
- Serveur SSH minimal, auth par clé publique brute (sans certificat encore)
- Stockage bare repos sur disque, exec direct de git
Phase 1 — Instance locale complète
- CA locale : génération + émission de certificats utilisateurs
- Gestion users/clés/repos via TUI
- ACL locale par repo (read/write/admin)
Phase 2 — Fédération v0
- Endpoint
/.well-known/gitfed.json - Trust store des CA distantes + politique whitelist/auto-trust
- ACL étendue aux principals distants
- Commande TUI : ajouter un collaborateur fédéré, approuver un domaine
Phase 3 — Durcissement
- Renouvellement de certificat (TTL court côté client)
- Audit log des accès cross-instance
- Rate limiting / anti-abus sur la découverte de domaines inconnus
Phase 4 — UI web
- Réutilise le même noyau identité/ACL/fédération
10. Questions ouvertes / à trancher plus tard
- Faut-il un mécanisme de révocation explicite (CRL-like) au-delà du TTL court ?
- Comment gérer le renouvellement de certificat si l'instance d'origine de l'utilisateur est temporairement indisponible ?
- Format exact de nommage des repos distants côté client (
git clone gitfed://instancea.example/alice/mon-projet?). - Politique par défaut : whitelist ou auto-trust au premier lancement ?
- Multi-clé par utilisateur (plusieurs devices) : un certificat par device ou un certificat "identité" réutilisé ?