# 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-/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-/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..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` — 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- # coller le bloc rendu dans OPS-/plan/acces.yml make vpn-admin-plan && CONFIRMER=true make vpn-admin-appliquer python3 scripts/vpn_admin.py config --nom prenom-appareil --locataire OPS- ``` ## 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.