Gitfed
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 de instanceb.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)

  1. Bob (compte sur instanceb.example) est ajouté comme collaborateur write sur alice/mon-projet hébergé sur instancea.example.
  2. instancea.example résout la confiance vers instanceb.example (si pas déjà fait).
  3. Bob se connecte en SSH à instancea.example, présente son certificat signé par la CA de instanceb.example.
  4. 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é
  5. Si autorisé → exec direct de git-receive-pack (ou git-upload-pack pour un pull) comme pour un utilisateur local. Aucune réplication, la donnée reste sur instancea.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 natifs
  • charmbracelet/wish — framework SSH-app (bâti sur x/crypto/ssh)
  • charmbracelet/bubbletea + lipgloss — TUI
  • modernc.org/sqlite ou etcd-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é ?