Gitfed
bastien-mrq/gitfed/ Commits/ a875ca0

Rewrite README around project values, split into focused docs

README/README.fr.md now lead with why gitfed exists (ownership, federation, control) instead of jumping straight to setup, with a language-switch link at the top of each. The deep technical content that used to be crammed into the README moved into two new docs: docs/HOW_IT_WORKS.md (the federation/identity model, walked through end to end) and docs/ARCHITECTURE.md (code layout, the admin RPC socket, the k8s topology).

bastien-mrq 2026-07-28 19:06 commit a875ca089936c99d5574c8229ef39b8675828a19 parent 1976655b2b2145f8df70f9d092353bb3629d86b7
4 files changed +473 −117
A README.fr.md +118 −0
M README.md +69 −117
A docs/ARCHITECTURE.md +140 −0
A docs/HOW_IT_WORKS.md +146 −0
README.fr.md
diff --git a/README.fr.md b/README.fr.md new file mode 100644 index 0000000..51824d9 --- /dev/null +++ b/README.fr.md @@ -0,0 +1,118 @@ +**Langues :** [English](README.md) · Français + +# gitfed + +*Un serveur git auto-hébergé et fédéré, pour celles et ceux qui veulent que +leur code et leurs données leur appartiennent vraiment — sans renoncer à +pouvoir collaborer avec qui que ce soit, où qu'iel héberge son instance.* + +--- + +## Pourquoi gitfed existe + +La plupart des solutions d'hébergement git imposent un choix : garder son +code sur une infrastructure qu'on ne contrôle pas, ou renoncer à collaborer +avec quiconque en dehors de son propre serveur. gitfed existe pour supprimer +ce compromis. + +- **Propriété** — vos dépôts, vos utilisateurs, votre base de données, sur + une machine que vous contrôlez. Aucune plateforme ne peut limiter votre + accès, exploiter votre code, ou vous en couper l'accès. +- **Fédération** — votre instance peut faire confiance à d'autres instances, + comme les serveurs de mail se font confiance entre eux. Une personne sur + une instance gitfed complètement différente peut recevoir un accès à l'un + de vos dépôts sans jamais créer de compte chez vous. +- **Contrôle** — chaque dépôt est explicitement public ou privé, chaque + collaborateur a un rôle explicite (lecture/écriture/admin), et chaque + relation de confiance entre instances est également explicite — rien + n'est accordé par défaut simplement parce qu'une requête est arrivée. + +gitfed n'essaie pas de rivaliser avec GitHub ou GitLab sur le nombre de +fonctionnalités. L'interface web (navigateur de fichiers, gestion autonome +des dépôts, panneau admin) existe pour rendre ce modèle d'identité +utilisable au quotidien — le modèle d'identité est le vrai sujet. + +## Comment ça marche, en un paragraphe + +Chaque instance gitfed fait tourner sa propre autorité de certification. +Quand vous poussez ou récupérez du code, votre instance d'origine signe un +certificat SSH de courte durée qui prouve qui vous êtes. Toute instance qui +a choisi de faire confiance à l'autorité de certification de votre instance +d'origine accepte ce certificat — pas de second compte, pas de mot de passe +partagé, pas d'inscription par instance. L'explication complète, avec +schémas, est dans [`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md), et la même +explication est aussi servie en direct sur `/security` sur n'importe quelle +instance en fonctionnement. + +## Ce qui est inclus + +- **`gitfed-server`** — serveur SSH (opérations git + émission de + certificats) et le point de terminaison de fédération + `/.well-known/gitfed.json`. +- **`gitfed-web`** — l'interface web : un navigateur de dépôts public, et, + derrière une connexion par mot de passe, la gestion autonome (vos propres + clés et dépôts) et une section admin, en français ou en anglais. +- **`gitfed-tui`** — un outil d'administration en terminal ; le seul moyen + d'amorcer le tout premier compte. +- **`gitfed-renew-cert`** — un petit outil client pour renouveler votre + propre certificat avant son expiration. + +## Pour aller plus loin + +| Doc | Contenu | +|---|---| +| [`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md) | Le modèle de fédération/identité de bout en bout : certificats, magasin de confiance, ACL, un vrai push détaillé étape par étape. | +| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Comment le code est organisé : les quatre binaires, les paquets `internal/`, pourquoi il existe un socket RPC d'administration, la topologie Kubernetes. | +| [`docs/security/AUDIT.md`](docs/security/AUDIT.md) | L'audit de sécurité pré-production et ce qui en a été corrigé. | +| [`DESIGN.md`](DESIGN.md) | Le document de conception d'origine — le « pourquoi » derrière l'architecture, écrit avant le début de l'implémentation. | +| [`CHANGELOG.md`](CHANGELOG.md) | Historique des versions (également servi sur `/changelog` dans l'interface web). | + +## Le faire tourner en local + +```sh +go build -o bin/gitfed-server ./cmd/gitfed-server +go build -o bin/gitfed-web ./cmd/gitfed-web +go build -o bin/gitfed-tui ./cmd/gitfed-tui + +./bin/gitfed-server -init localhost -data-dir data -config gitfed.json +# éditez gitfed.json si vous voulez d'autres ports (par défaut : ssh :2222, http :8443) +./bin/gitfed-server -config gitfed.json & + +./bin/gitfed-tui -config gitfed.json +# Users -> a (add user) : nom d'utilisateur, votre clé publique SSH, un mot de passe, admin : y + +./bin/gitfed-web -config gitfed.json & +# ouvrez http://localhost:8088, connectez-vous avec ce que vous venez de créer +``` + +Le clonage/push passe toujours par SSH : + +```sh +git clone ssh://git@localhost:2222/<user>/<repo> +``` + +## Déployer pour de vrai + +L'explication complète — manifestes, pourquoi le pod est formé ainsi, DNS, +amorçage du premier compte — est dans +[`deploy/k8s/README.md`](deploy/k8s/README.md). Chaque mise à jour après la +première doit passer par [`deploy/update.sh`](deploy/update.sh), qui +incrémente la version, construit et importe une image taguée, et effectue +le déploiement : + +```sh +# ajoutez d'abord une section "## X.Y.Z" à CHANGELOG.md décrivant le changement +deploy/update.sh # incrément patch +deploy/update.sh minor # ou : major, ou une version explicite X.Y.Z +``` + +## Sauvegardes + +Tout ce qui compte vit sur un seul volume persistant : la base de données +(utilisateurs, dépôts, ACL, sessions, magasin de confiance, journal +d'audit, révocations de certificats), la clé de l'autorité de certification +de l'instance, la clé d'hôte SSH, et les dépôts bare eux-mêmes. Perdre la +clé de la CA invalide tous les certificats jamais émis par cette instance +et casse toutes les relations de confiance fédérées qui pointent vers elle +— il n'y a pas de récupération possible en dehors de tout reconstruire +depuis zéro. Sauvegardez le volume entier, pas seulement les données git.
README.md
diff --git a/README.md b/README.md index d4c0c7f..afe2f0c 100644 --- a/README.md +++ b/README.md @@ -1,100 +1,67 @@ +**Languages:** English · [Français](README.fr.md) + # gitfed -A self-hosted git server where pushing to someone else's instance doesn't -mean creating a second account there. Your home instance vouches for you — -literally, via a short-lived SSH certificate it signs — and any instance -that trusts your home instance's certificate authority accepts it. - -Current version: see [CHANGELOG.md](CHANGELOG.md) (also served at -`/changelog` in the web UI, next to a `gitfed X.Y.Z` link in the footer). - -## What this is (and isn't) - -- **Git over SSH only.** No HTTP git protocol, no reimplementation of git - itself — gitfed wraps `git-upload-pack`/`git-receive-pack` as - subprocesses and lets git do what git does. -- **Federated identity, not federated data.** A repo lives on exactly one - instance; there's no replication or mirroring. Federation is purely - about who's allowed to push/pull, carried by certificates instead of - per-instance accounts. -- **Not a GitHub/GitLab clone**, even though the web UI looks like one. - The point of the project is the identity model above — everything else - (file browser, self-service repos, admin panel) exists to make that - model usable day to day, not to compete on feature count. - -Full design rationale and the protocol-level detail live in -[DESIGN.md](DESIGN.md); this file is the practical "what do I actually run" -version. - -## How it works - -- Each instance generates its own ed25519 **certificate authority** on - first run. A local user with a registered SSH key can ask their own - instance for a certificate (`ssh git@host gitfed-cert`, or the - `gitfed-renew-cert` helper below); the certificate embeds the principal - `<username>@<domain>` and expires in a couple of days by default. -- To let someone from another instance collaborate on a repo, an admin (or - the repo owner) grants their principal a role — `read`, `write`, or - `admin`. The first time a given remote domain shows up, the instance - fetches `https://<domain>/.well-known/gitfed.json` to learn that - instance's CA public key, and either trusts it immediately or holds it - **pending** for an admin to approve, depending on the configured trust - policy (whitelist by default, auto-trust/TOFU opt-in). -- Once a domain is trusted, verifying a certificate from it is entirely - local — no network call on every SSH connection, no dependency on the - remote instance being reachable at push/pull time. -- Repos can be public (readable by any authenticated principal, from any - trusted domain, without an explicit grant) or private (owner and - explicitly granted collaborators only). Write access always requires an - explicit grant regardless of visibility. +*A self-hosted, federated git server for people who want their code and their +data to stay theirs — without losing the ability to work with anyone, +wherever they host.* + +--- + +## Why gitfed exists + +Most git-hosting options ask for a trade: keep your code somewhere you don't +control, or give up the ability to collaborate with anyone outside your own +server. gitfed exists to remove that trade-off. + +- **Ownership** — your repositories, your users, your database, on hardware + you control. No platform can throttle your access, mine your code, or shut + you out. +- **Federation** — your instance can trust other people's instances, the way + mail servers trust each other. Someone on a completely different gitfed + instance can be granted access to one of your repos without ever creating + an account with you. +- **Control** — every repo is explicitly public or private, every + collaborator has an explicit role (read/write/admin), and every trust + relationship between instances is explicit too — nothing is granted by + default just because a request showed up. + +gitfed isn't trying to out-feature GitHub or GitLab. The web UI (file +browser, self-service repos, admin panel) exists to make the identity model +usable day to day — the identity model itself is the point. + +## How it works, in one paragraph + +Each gitfed instance runs its own certificate authority. When you push or +pull, your home instance signs a short-lived SSH certificate that says who +you are. Any instance that has chosen to trust your home instance's +certificate authority accepts that certificate — no second account, no +shared password, no per-instance registration. The full walkthrough is in +[`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md) — written in French, like the +rest of the project's internal docs — and the same explanation, with +diagrams, is served live at `/security` on any running instance. ## What's included -- **`gitfed-server`** — the daemon: SSH server (git + cert issuance) and - the `/.well-known/gitfed.json` HTTP endpoint. -- **`gitfed-web`** — the web UI: a public, unauthenticated repo browser - (file tree, rendered README/LICENSE, tags, topics) plus, behind a - username/password login, self-service (your own keys and repos) and an - admin section (users, trust store, audit log) gated by an admin flag on - the account. The web login only ever grants access to this UI — git - push/pull always goes over SSH with a key or certificate, independently - of it. -- **`gitfed-tui`** — a terminal admin tool that talks to the same store - gitfed-web does. It's the way to bootstrap the very first account (there - is no self-registration), and works even if the web UI is down, since it - can also open the database directly when the server isn't running. -- **`gitfed-renew-cert`** — a small client-side tool end users run on - *their own* machine (e.g. from a cron job or systemd timer) to renew - their certificate shortly before it expires. - -## Project layout - -``` -cmd/ - gitfed-server/ SSH server + well-known endpoint - gitfed-web/ web UI (public browsing + self-service + admin) - gitfed-tui/ terminal admin tool - gitfed-renew-cert/ client-side certificate renewal helper -internal/ - ca/ CA key generation + certificate signing - ssh/ SSH server, auth, ACL enforcement, git exec - federation/ .well-known discovery, trust store - acl/ repo access-control checks - gitexec/ git-upload-pack/receive-pack + tree/blob reads - store/ bbolt-backed persistence - admin/ the Ops interface both TUI and web drive - adminrpc/ lets gitfed-web talk to a *running* server - over a Unix socket instead of racing it for - the store's exclusive file lock - opsconnect/ picks live-socket vs. direct-store mode -deploy/ - docker/Dockerfile multi-stage build for server/web/tui - k8s/ manifests + deployment README (k3s/Traefik/ - cert-manager) - update.sh version bump + build + deploy in one command -DESIGN.md architecture and protocol rationale -CHANGELOG.md version history -``` +- **`gitfed-server`** — SSH server (git operations + certificate issuance) + and the `/.well-known/gitfed.json` federation endpoint. +- **`gitfed-web`** — the web UI: a public repo browser, and, behind a + password login, self-service (your own keys and repos) and an admin + section, in French or English. +- **`gitfed-tui`** — a terminal admin tool; the only way to bootstrap the + first account. +- **`gitfed-renew-cert`** — a small client tool to renew your own + certificate before it expires. + +## Learn more + +| Doc | What's in it | +|---|---| +| [`docs/HOW_IT_WORKS.md`](docs/HOW_IT_WORKS.md) *(French)* | The federation/identity model end to end: certificates, trust store, ACLs, a real push walked through step by step. | +| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) *(French)* | How the code is organized: the four binaries, the `internal/` packages, why there's an admin RPC socket, the Kubernetes topology. | +| [`docs/security/AUDIT.md`](docs/security/AUDIT.md) *(French)* | The pre-production security audit and what was fixed as a result. | +| [`DESIGN.md`](DESIGN.md) *(French)* | The original design rationale — the "why" behind the architecture, written before implementation started. | +| [`CHANGELOG.md`](CHANGELOG.md) | Version history (also served at `/changelog` in the web UI). | ## Running it locally @@ -122,26 +89,11 @@ git clone ssh://git@localhost:2222/<user>/<repo> ## Deploying for real -The setup this project actually runs on is a single-node k3s VPS with -Traefik and cert-manager already handling another app (specifically, -[ess-helm](https://github.com/element-hq/ess-helm)) — gitfed reuses both -rather than standing up its own ingress/TLS. Full walkthrough, including -why the deployment is shaped the way it is (bbolt's single-writer lock, -why there are two containers in one pod, why the SSH port can't be 22), -is in **[deploy/k8s/README.md](deploy/k8s/README.md)**. - -Short version: - -```sh -docker build -f deploy/docker/Dockerfile -t gitfed:latest . -docker save gitfed:latest -o gitfed.tar # then import into your cluster's runtime -kubectl apply -f deploy/k8s/ # namespace, configmap, pvc, deployment, service, ingress -``` - -For every update after the first, use `deploy/update.sh` instead of -repeating that by hand — it bumps `VERSION`, builds and imports an image -tagged with the actual version (not just `:latest`), points the deployment -at it, applies, and commits: +The full walkthrough — manifests, why the pod is shaped the way it is, DNS, +bootstrapping the first account — is in +[`deploy/k8s/README.md`](deploy/k8s/README.md). Every update after the first +should go through [`deploy/update.sh`](deploy/update.sh), which bumps the +version, builds and imports a tagged image, and rolls it out: ```sh # add a "## X.Y.Z" section to CHANGELOG.md describing the change first @@ -151,10 +103,10 @@ deploy/update.sh minor # or: major, or an explicit X.Y.Z ## Backups -Everything that matters lives on one persistent volume: the database -(users, repos, ACLs, sessions, trust store, audit log), the instance's CA -key, the SSH host key, and the bare repos themselves. Losing the CA key -specifically invalidates every certificate this instance has ever issued -and breaks every federated trust relationship pointing at it — there's no +Everything that matters lives on one persistent volume: the database (users, +repos, ACLs, sessions, trust store, audit log, certificate revocations), the +instance's CA key, the SSH host key, and the bare repos themselves. Losing +the CA key invalidates every certificate this instance has ever issued and +breaks every federated trust relationship pointing at it — there's no recovery short of everyone re-establishing trust from scratch. Back up the whole volume, not just the git data.
docs/ARCHITECTURE.md
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..46b354a --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,140 @@ +# Architecture + +Comment le code de gitfed est organisé, et pourquoi. Pour le modèle +d'identité/fédération lui-même, voir [`HOW_IT_WORKS.md`](HOW_IT_WORKS.md) ; +pour la marche à suivre de déploiement, voir +[`deploy/k8s/README.md`](../deploy/k8s/README.md). + +--- + +## 1. Vue d'ensemble : quatre binaires, un cœur partagé + +``` +cmd/ + gitfed-server/ démon : serveur SSH + endpoint /.well-known + gitfed-web/ interface web (navigation publique + self-service + admin) + gitfed-tui/ outil d'administration en terminal + gitfed-renew-cert/ outil client de renouvellement de certificat +``` + +`gitfed-server` est le seul processus qui touche directement la base de +données et les dépôts git. `gitfed-web` et `gitfed-tui` ne réimplémentent +rien : ils pilotent le même cœur métier via l'interface `admin.Ops` +(§3) — soit en parlant à un `gitfed-server` déjà lancé via un socket, +soit, s'il n'est pas lancé, en ouvrant directement la base. +`gitfed-renew-cert` est complètement à part : un petit client autonome, +pensé pour tourner sur la machine d'un·e utilisateur·ice (cron/systemd +timer), qui ne parle qu'en SSH à une instance distante. + +## 2. Les paquets `internal/` + +| Paquet | Rôle | +|---|---| +| `ca` | Génère la clé d'autorité de certification de l'instance et signe les certificats utilisateurs. | +| `ssh` | Le serveur SSH lui-même : authentification (clé nue ou certificat), vérification de révocation, exécution de `git-upload-pack`/`git-receive-pack`. | +| `federation` | Découverte `.well-known`, garde-fous anti-SSRF (`wellknown.go`), magasin de confiance et sa politique (`resolver.go`). | +| `acl` | Les règles d'autorisation par dépôt (public/privé, rôles read/write/admin). | +| `gitexec` | Tout ce qui invoque `git` en sous-processus : init, lecture d'arbre/fichier à HEAD, résolution de branche par défaut. | +| `store` | Persistance bbolt : utilisateurs, dépôts, ACL, sessions, magasin de confiance, journal d'audit, révocations. | +| `admin` | L'interface `Ops` : chaque opération d'administration ou de self-service, dans un seul endroit, implémentée une fois. | +| `adminrpc` | Protocole JSON-sur-socket-Unix qui expose `admin.Ops` à un client distant (voir §3). | +| `opsconnect` | Choisit automatiquement le mode socket-vivant ou base-directe selon qu'un `gitfed-server` tourne déjà. | +| `i18n` | Dictionnaires de traduction (français/anglais) de l'interface web et fonction de résolution de langue. | +| `config` | Chargement du fichier de configuration de l'instance (`gitfed.json`). | +| `version` | Numéro de version, injecté à la compilation. | + +## 3. Pourquoi un socket RPC d'administration + +bbolt (la base embarquée utilisée par `store`) impose un **verrou exclusif +mono-écrivain** sur son fichier : un seul processus peut l'ouvrir en +écriture à la fois. Si `gitfed-web` ouvrait la base directement pendant que +`gitfed-server` tourne déjà, l'un des deux échouerait à démarrer, ou pire, +les deux se disputeraient le verrou. + +La solution : `gitfed-server` expose son `admin.Ops` sur un socket Unix +local (`data/admin.sock`), via un petit protocole JSON maison +(`internal/adminrpc`). `gitfed-web` et `gitfed-tui` s'y connectent comme +client au lieu de rouvrir la base — c'est `internal/opsconnect` qui décide, +au démarrage, s'il faut passer par ce socket (mode « live ») ou ouvrir la +base directement (mode « hors-ligne », utilisé par `gitfed-tui` quand aucun +serveur ne tourne, par exemple pour créer le tout premier compte). + +Un piège corrigé au passage : les erreurs qui traversent ce socket perdent +leur identité si on n'y prend pas garde — `errors.New("not found")` côté +serveur redevient une simple chaîne de caractères côté client, qui ne peut +plus être comparée avec `== store.ErrNotFound`. `adminrpc.Client` reconstruit +donc explicitement les erreurs sentinelles connues par correspondance de +message plutôt que de les laisser passer telles quelles. + +## 4. L'interface web (`cmd/gitfed-web`) + +Chaque page est un handler HTTP qui : vérifie les droits d'accès via +`admin.Ops`, exécute un petit template Go pour produire le corps de la +page, puis appelle `server.render()` qui enveloppe ce corps dans le +"shell" commun (barre de navigation, pied de page, feuille de style, +sprite d'icônes SVG) défini dans `render.go`. + +Points notables : + +- **Pas de framework JS.** Tout le rendu est fait côté serveur avec + `html/template` ; le peu de JavaScript (menu profil, bascule d'onglets, + copie dans le presse-papiers) est un unique bloc inline, dont le hash + SHA-256 est épinglé dans la politique CSP (voir §6). +- **i18n par fonction de template.** Chaque page passe sa langue courante + (résolue par cookie, puis `Accept-Language`, voir `lang.go`) dans les + données du template ; les chaînes de caractères passent par + `{{t .Lang "clé"}}`, qui va chercher la traduction dans `internal/i18n`. +- **Authentification web indépendante de git.** Le mot de passe web + (haché bcrypt, coût 12) n'ouvre qu'une session opaque côté web — il + n'intervient jamais dans l'authentification SSH/git, qui repose + uniquement sur les clés et certificats (voir `HOW_IT_WORKS.md`). + +## 5. Topologie de déploiement + +``` +┌─────────────────────────── Pod (1 réplique) ───────────────────────────┐ +│ │ +│ conteneur "server" conteneur "web" │ +│ gitfed-server gitfed-web │ +│ ├─ SSH :2222 (hostPort) ├─ HTTP :8088 │ +│ ├─ well-known :8443 └─ socket data/admin.sock (client) │ +│ └─ socket data/admin.sock (serveur) │ +│ │ +│ volume "data" (PVC) monté dans les deux conteneurs : │ +│ base bbolt, dépôts bare, clé de CA, clé d'hôte SSH, socket admin │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +- **Une seule réplique, toujours.** bbolt (verrou mono-écrivain), le socket + admin et le port SSH exposé en `hostPort` sont tous, par nature, des + ressources à instance unique — `deployment.yaml` fixe `replicas: 1` et + `strategy: Recreate` plutôt qu'un rolling update. +- **Deux conteneurs, un seul pod.** Ils partagent le même volume de + données ; le conteneur `web` attend, via une boucle shell, que + `data/admin.sock` existe avant de démarrer. +- **SSH sur `hostPort: 2222`**, pas 22 — le port 22 du nœud est déjà pris + par le sshd de la VPS elle-même. +- **`gitfed-web` seul est exposé par une Ingress** (Traefik + cert-manager + déjà en place sur le cluster pour un autre projet) ; c'est sûr de + l'exposer publiquement car l'authentification web est réelle (mot de + passe + session), pas un simple accès admin non protégé. + +Le détail pas-à-pas (DNS, premier déploiement, amorçage du compte admin) +est dans [`deploy/k8s/README.md`](../deploy/k8s/README.md). + +## 6. Ce qui protège l'instance en production + +Voir [`docs/security/AUDIT.md`](security/AUDIT.md) pour le détail complet, +mais en résumé, ce qui est en place structurellement : + +- **Aucun protocole git en HTTP** — tout passe par SSH, une seule porte + d'entrée réseau pour le git lui-même. +- **Anti-SSRF au moment de la connexion**, pas seulement à la validation du + nom de domaine (`internal/federation/wellknown.go`) — protège aussi + contre le DNS-rebinding. +- **CSP stricte** avec script inline épinglé par hash, en-têtes de + durcissement standards, vérification d'origine sur les requêtes de + mutation (défense CSRF en profondeur, en complément de `SameSite=Lax`). +- **Révocation de certificat immédiate**, consultée à chaque authentification + SSH (`internal/ssh/server.go`). +- **Limitation de débit** sur le login web, par compte et par IP.
docs/HOW_IT_WORKS.md
diff --git a/docs/HOW_IT_WORKS.md b/docs/HOW_IT_WORKS.md new file mode 100644 index 0000000..0e83b15 --- /dev/null +++ b/docs/HOW_IT_WORKS.md @@ -0,0 +1,146 @@ +# Comment fonctionne gitfed + +Ce document explique le mécanisme central de gitfed : comment deux instances +qui ne se connaissent pas peuvent laisser leurs utilisateurs collaborer sans +qu'aucun compte ne soit jamais partagé entre elles. Pour le contexte plus +large (fichiers, code, déploiement), voir le [README](../README.fr.md) ; pour +le détail de chaque paquet Go, voir [`ARCHITECTURE.md`](ARCHITECTURE.md). + +--- + +## 1. Le problème que ça résout + +Sur GitHub ou GitLab, collaborer avec quelqu'un suppose soit que vous ayez +tous les deux un compte sur la même plateforme, soit que l'un des deux +migre. gitfed part d'un principe différent, plus proche du mail ou +d'ActivityPub : **chaque personne a un compte sur une seule instance — la +sienne — et cette instance peut prouver son identité auprès de n'importe +quelle autre instance qui accepte de lui faire confiance.** + +Concrètement : alice a un compte sur `chez-moi.fr`. Bob a un compte sur +`ailleurs.net`. Bob peut pousser du code sur un dépôt d'alice sans jamais +créer de compte chez elle — à condition que l'instance d'alice ait choisi de +faire confiance à l'instance de Bob. + +## 2. L'identité : un certificat plutôt qu'un mot de passe partagé + +Chaque instance gitfed génère, à sa création, sa propre **autorité de +certification** (une paire de clés ed25519, stockée sur le volume de +données). C'est le socle de tout le modèle : + +1. Un utilisateur local enregistre une clé SSH publique auprès de son + instance (via `gitfed-tui` pour le tout premier compte, ou en + autonomie ensuite dans `Paramètres → Clés SSH`). +2. Quand il en a besoin, il demande à son instance un certificat + (`ssh git@instance gitfed-cert`, ou via l'outil `gitfed-renew-cert` en + tâche planifiée). L'instance vérifie que la clé lui appartient bien, puis + signe un **certificat SSH** au format OpenSSH. +3. Ce certificat embarque un **principal** — `bob@ailleurs.net`, jamais un + simple nom d'utilisateur seul — et une durée de validité courte, + **24 heures par défaut**. +4. Quand Bob se connecte à l'instance d'alice, il présente ce certificat au + lieu de sa clé nue. L'instance d'alice vérifie deux choses : que la + signature du certificat correspond bien à une autorité en laquelle elle a + confiance, et que ce certificat n'a pas été révoqué (§6). + +Aucune de ces deux instances n'a besoin de contacter l'autre en direct pour +cette vérification — la signature suffit, un peu comme un passeport n'a pas +besoin d'un appel téléphonique au pays émetteur pour être vérifié à la +frontière, tant qu'on connaît la clé publique de l'autorité qui l'a délivré. + +## 3. La confiance entre instances : le magasin de confiance + +Faire confiance à une autorité, ça se construit. La première fois que +l'instance d'alice croise le domaine `ailleurs.net` (typiquement parce +qu'alice a accordé un accès à `bob@ailleurs.net` sur un de ses dépôts), elle +déclenche une **découverte fédérée** : + +1. Elle récupère `https://ailleurs.net/.well-known/gitfed.json`, un document + public qui contient la clé publique de l'autorité de certification de + cette instance. +2. Elle vérifie que ce document est cohérent (le domaine déclaré correspond + bien, une clé est présente) et que la requête ne pointe pas vers une + adresse interne ou privée (protection anti-SSRF : IP littérales, + `127.0.0.0/8`, `10.0.0.0/8`, `169.254.0.0/16`/métadonnées cloud, etc. sont + toutes refusées, y compris après résolution DNS). +3. Selon la politique configurée sur l'instance d'alice : + - **liste blanche (par défaut)** — le domaine est enregistré comme **en + attente** ; un administrateur doit l'approuver explicitement dans + `Administration → Confiance fédérée` avant que le moindre accès ne soit + accordé. + - **auto-confiance (TOFU — "trust on first use")** — le domaine est + approuvé immédiatement à la première découverte, à activer uniquement + si on accepte ce niveau de risque. +4. La découverte est limitée à **10 nouveaux domaines par minute** pour + toute l'instance, pour empêcher qu'un compte compromis ne s'en serve + comme sonde réseau à grande échelle. + +Une fois un domaine approuvé, la vérification d'un certificat qui en +provient est **entièrement locale** : aucune requête réseau n'est refaite à +chaque `git push`. + +## 4. L'autorisation : qui peut faire quoi sur quel dépôt + +L'identité fédérée dit *qui* on est ; elle ne dit rien de ce qu'on a le +droit de faire. Ça, c'est le rôle du modèle d'ACL, entièrement local à +chaque dépôt : + +- Chaque dépôt a un **propriétaire** (toujours admin) et, optionnellement, + des **collaborateurs**, chacun avec un rôle explicite : + **lecture**, **écriture**, ou **admin**. +- Un dépôt est en plus **public** ou **privé**. Un dépôt public accorde la + **lecture** à n'importe quel principal authentifié, de n'importe quelle + instance de confiance, sans qu'il soit besoin de l'ajouter comme + collaborateur. L'**écriture** n'est en revanche jamais implicite — même + sur un dépôt public, il faut un rôle explicite pour pousser du code. +- Octroyer un accès à un principal sur un domaine encore inconnu de + l'instance est justement ce qui déclenche la découverte fédérée décrite + au §3. + +## 5. Le cycle complet, étape par étape + +Reprenons l'exemple : alice veut donner à Bob un accès en écriture sur +`alice/mon-projet`. + +1. **Sur l'instance d'alice** — alice va dans les paramètres du dépôt et + accorde le rôle *écriture* à `bob@ailleurs.net`. +2. Si `ailleurs.net` est un domaine encore inconnu, l'instance d'alice + déclenche la découverte (§3) et le domaine reste **en attente** tant + qu'un admin ne l'a pas approuvé. +3. **Sur l'instance de Bob** — Bob demande un certificat à sa propre + instance (`gitfed-cert` ou renouvellement automatique). Son instance + signe un certificat portant le principal `bob@ailleurs.net`. +4. Bob exécute `git push ssh://git@chez-moi.fr:2222/alice/mon-projet.git`, + en s'authentifiant avec son certificat. +5. L'instance d'alice vérifie la signature du certificat contre la clé + d'autorité qu'elle a approuvée pour `ailleurs.net`, vérifie qu'il n'est + pas révoqué, puis vérifie dans son ACL locale que `bob@ailleurs.net` a + bien le rôle *écriture* sur `alice/mon-projet`. +6. Si tout est en ordre, `git-receive-pack` s'exécute normalement, exactement + comme pour un utilisateur local. + +À aucun moment Bob n'a eu besoin d'un compte, d'un mot de passe ou d'une +clé enregistrée chez alice. + +## 6. Expiration et révocation + +Un certificat de 24 h qui traîne après la suppression d'un compte reste, en +théorie, un accès résiduel. gitfed traite ça de deux façons complémentaires : + +- **Le TTL est court** (24 h par défaut, configurable) — la fenêtre + d'exposition d'une clé compromise est bornée même sans action de + l'administrateur. +- **La révocation est immédiate.** Supprimer un utilisateur ou retirer une + de ses clés SSH enregistre la révocation correspondante (par principal + et/ou par empreinte de clé) ; chaque authentification SSH consulte cette + liste avant d'accepter un certificat, même s'il est encore + cryptographiquement valide. + +## 7. Ce qui n'est *pas* fédéré + +Pour être clair sur les limites du modèle : **un dépôt vit sur une seule +instance.** Il n'y a ni réplication, ni miroir automatique entre instances. +La fédération ne concerne que l'identité — qui a le droit de pousser ou de +lire — jamais les données elles-mêmes. Si l'instance d'alice tombe, son +dépôt n'est disponible nulle part ailleurs tant qu'il n'a pas été cloné en +dehors.