plan/acces.yml chez le locataire (cles publiques), reseau/port/instance derives de l'index. La garde du devis refuse qu'un tunnel de locataire vise autre chose que son supernet : sans elle, un ecosysteme s'ouvrirait un acces chez un voisin depuis son propre plan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
117 lines
6.5 KiB
Markdown
117 lines
6.5 KiB
Markdown
# L'accès d'administration — un tunnel nominatif
|
|
|
|
> **Pour qui :** celui qui administre le site et ses écosystèmes, et celui qui se demande
|
|
> par où un humain entre, avec quelle clé, et ce qu'il peut atteindre une fois entré.
|
|
>
|
|
> Décidé le 2026-09-17, en réponse à une question : *le runner ne devrait-il pas être le
|
|
> rebond SSH des admins ?*
|
|
|
|
## Pourquoi pas le runner
|
|
|
|
Le runner du site détient **la clé de la voûte du site** et les clés SSH qui configurent
|
|
toutes les machines. En faire la porte des humains réunirait deux pouvoirs que tout le reste
|
|
du dépôt sépare :
|
|
|
|
- une session humaine compromise (un agent SSH transféré de trop) deviendrait le **plan de
|
|
contrôle** — pas seulement un rebond ;
|
|
- il **ne doit jamais entrer chez un locataire** (charte des responsabilités) ; en faire le
|
|
passage obligé des admins créerait ce chemin en fait ;
|
|
- il est **reconstructible par le code**, et c'est sa vertu. Un point d'entrée doit survivre à
|
|
la reconstruction de ce qu'il sert ;
|
|
- « qui est entré » et « qu'est-ce qui a été déployé » cesseraient de se raconter séparément.
|
|
|
|
## Ce qui a été construit
|
|
|
|
Un **second** tunnel WireGuard sur la frontière — l'instance `admins`, port 51821 — à côté du
|
|
tunnel site-à-site vers le site pair (instance `chezlePro`, port 51820), jamais mêlé à lui.
|
|
|
|
| | |
|
|
|---|---|
|
|
| déclaré | `SITE-<nom>/plan/10-intrants.yml`, clé `acces_admin_vpn` |
|
|
| réconcilié | `scripts/vpn_admin.py` (`make vpn-admin-plan`, `make vpn-admin-appliquer`) |
|
|
| réseau | `10.37.29.0/24`, la frontière en `.1` |
|
|
| un pair | **une personne ET un appareil** — révoquer l'appareil perdu ne coupe pas les autres |
|
|
| clés | seule la clé **publique** est au plan ; la privée ne quitte jamais l'appareil |
|
|
|
|
**Le réseau du tunnel est un réseau d'administration, et tout en dérive** : les pare-feux des
|
|
machines du site (`resoudre_flux`), le contrat vers les locataires (`site_intrants`, donc les
|
|
pare-feux de leurs machines) et les règles de la frontière (`devis_opnsense`). Rien à recopier
|
|
— une liste recopiée prend toujours du retard sur celle qu'elle suit.
|
|
|
|
## Ajouter un appareil
|
|
|
|
```bash
|
|
python3 scripts/vpn_admin.py pair-nouveau --nom prenom-appareil # tire la paire de clés
|
|
# coller le bloc `pairs:` rendu dans SITE-<nom>/plan/10-intrants.yml
|
|
make vpn-admin-plan # lire ce qui changerait
|
|
CONFIRMER=true make vpn-admin-appliquer # poser le pair
|
|
python3 scripts/vpn_admin.py config --nom prenom-appareil # la config de l'appareil
|
|
make flux && make deployer-groupe GROUPE=serveur_durci # les pare-feux des machines
|
|
```
|
|
|
|
La clé privée ne s'affiche **qu'une fois**, au moment où elle est tirée : elle appartient à
|
|
l'appareil. Perdue, on en tire une autre ; l'ancienne se révoque par `etat: absent`.
|
|
|
|
## Retirer un accès
|
|
|
|
`etat: absent` sur le pair, puis `CONFIRMER=true make vpn-admin-appliquer`. Le script retire
|
|
aussi tout pair **attaché à notre instance que le plan ne déclare plus** : un accès qui
|
|
survivrait à la décision de le retirer est exactement ce qu'on ne veut pas.
|
|
|
|
## Deux trous que seul l'usage a montrés (2026-09-17, une heure après la pose)
|
|
|
|
- **Le SSH des machines du site.** Le socle déclare son 22 en `pair: [flotte, externe]`, jamais
|
|
`admin` : la source dérivée est le site lui-même. L'exploitant y arrivait **par rebond sur la
|
|
frontière** — la connexion partait alors du boîtier, que pf laisse sortir sans règle. Par le
|
|
tunnel, il arrive comme une source extérieure, et plus rien ne l'autorisait. Les locataires,
|
|
eux, marchaient : leur règle SSH naît de `nftables_admin_ssh`, qui contient déjà le tunnel.
|
|
- **La frontière elle-même.** Aucun flux ne la désignait comme destination d'administration :
|
|
monter le tunnel faisait perdre sa console et son API — donc le moyen même de réparer la
|
|
règle manquante. *La panne se referme sur celui qui la répare* : il a fallu remettre
|
|
l'adresse d'avant pour poser le correctif.
|
|
|
|
Les deux règles sont désormais émises par `devis_opnsense` dès que `acces_admin_vpn` est
|
|
déclaré (`ports_frontiere`, par défaut 22 et 443). Une règle **par port** : OPNsense refuse
|
|
« 22,443 » dans un champ de port, et aucun flux jusque-là n'en portait deux.
|
|
|
|
## Un tunnel par locataire (2026-09-17)
|
|
|
|
Chaque écosystème a le sien, et **c'est lui qui décide qui entre chez lui**. Le site porte la
|
|
route, jamais la liste des gens — même partage que les zones DNS publiques.
|
|
|
|
| | |
|
|
|---|---|
|
|
| déclaré par | le **locataire**, dans `plan/acces.yml` (registre `acces_admin_vpn`) |
|
|
| réseau | `10.<index>.29.0/24` — **dérivé** de son index, hors de ses six zones (.16 à .21) |
|
|
| port | `52000 + index` — dérivé, donc sans collision : P21 garde déjà l'unicité des index |
|
|
| instance | `wg<index>` — un rang (2, 3, 4…) renommerait les interfaces le jour où un écosystème part |
|
|
| ce qu'il atteint | **son supernet, et rien d'autre** : SSH et consoles web |
|
|
|
|
**L'isolement est gardé, pas promis.** `devis_opnsense.verifier` refuse toute règle dont la
|
|
source est le tunnel d'un locataire et la destination autre chose que **son** supernet —
|
|
sinon, un écosystème obtiendrait un accès chez un voisin en ajoutant un pair dans son propre
|
|
plan. La garde est exécutée par P24, avant toute écriture. Mise en défaut vérifiée.
|
|
|
|
**Le pare-feu de ses machines suit tout seul** : `resoudre_flux` ajoute le réseau du tunnel aux
|
|
sources d'administration **dérivées de son plan**, et seulement s'il déclare au moins un pair
|
|
actif — déclarer un réseau que personne n'emprunte ouvrirait le SSH à un tunnel qui n'existe
|
|
pas.
|
|
|
|
```bash
|
|
python3 scripts/vpn_admin.py pair-nouveau --nom prenom-appareil --locataire OPS-<X>
|
|
# coller le bloc rendu dans OPS-<X>/plan/acces.yml
|
|
make vpn-admin-plan && CONFIRMER=true make vpn-admin-appliquer
|
|
python3 scripts/vpn_admin.py config --nom prenom-appareil --locataire OPS-<X>
|
|
```
|
|
|
|
## Ce que le tunnel donne, et ce qu'il ne donne pas
|
|
|
|
`AllowedIPs` est **dérivé** de la carte : zones du site, fabric, lien de transit et supernets
|
|
des locataires. Ce que la frontière laisse ensuite passer reste décidé par les flux déclarés —
|
|
SSH partout, et les consoles d'administration (Grafana, Icinga Web, la forge, le runner).
|
|
|
|
**Le seul port ouvert sur l'Internet par cette voie est le 51821/udp**, vers l'adresse publique
|
|
de la frontière. Tout le reste voyage dans le tunnel.
|
|
|
|
**Ce qui n'est pas fait** : l'enregistrement des sessions (un rebond dédié le permettrait, pas
|
|
un tunnel), et la double authentification — la possession de l'appareil fait foi.
|