feat(mesh): headscale durci (DERP embarque) et noeuds tailscale

Le controleur headscale du VPS passe en mode durci : flotte DERP de Tailscale retiree (derp.urls vide), DERP embarque sur le VPS (region 900, verify_clients) et STUN en UDP 3478 ; route Traefik headscale.marcpartensky.com -> 127.0.0.1:8090. Cote clients, services/tailscale remplace l'ancien secret tailscale/auth_key (absent) par headscale/preauth_key, coupe l'envoi de logs vers Tailscale Inc (disableUpstreamLogging) et est importe sur tower, le deck et anywhere.
This commit is contained in:
Hermes Agent
2026-10-24 15:08:03 -04:00
parent 10058770c4
commit 932e3bc83d
6 changed files with 118 additions and 19 deletions
+3 -1
View File
@@ -27,8 +27,10 @@
../../services/pangolin
../../services/pangolin/gerbil-wg0-ensure.nix
../../services/wireguard
# ../../services/tailscale # desactive 26/09: exige le secret sops tailscale/auth_key (absent de secrets/anywhere.yml). Reactiver avec le mesh.
# Mesh headscale : serveur sur ce VPS (services/headscale) + nœud
# tailscale (services/tailscale, clé partagée secrets/headscale.yml).
../../services/headscale
../../services/tailscale
# ./services/chhoto
../../services/eternal-terminal
../../services/stalwart
+3
View File
@@ -24,6 +24,9 @@
# Réseau privé ygg-mesh (Yggdrasil via Tor + lien direct LAN), membres :
# tower, anywhere, deck. Voir services/ygg-mesh/default.nix.
../../services/ygg-mesh
# Mesh headscale : nœud tailscale (contrôleur sur le VPS anywhere, cf
# services/headscale et services/tailscale).
../../services/tailscale
./switch-emu.nix
];
+3
View File
@@ -19,6 +19,9 @@ in {
# Réseau privé ygg-mesh (Yggdrasil via Tor + lien direct LAN), membres :
# tower, anywhere, deck. Voir services/ygg-mesh/default.nix.
../../services/ygg-mesh
# Mesh headscale : nœud tailscale (contrôleur sur le VPS anywhere, cf
# services/headscale et services/tailscale).
../../services/tailscale
];
# Sessions hermes lancees par marc (panes herdr, HERMES_HOME partage, cf
+71 -13
View File
@@ -1,32 +1,67 @@
{ pkgs, config, ... }:
{
# Contrôleur du mesh headscale de marc (version DURCIE).
#
# Tourne sur le VPS `anywhere`, seul hôte à IP publique stable, et sert :
# - la coordination des nœuds tailscale (tower, deck, anywhere, laptop...) ;
# - un DERP embarqué : relais chiffré de secours quand le hole punching
# échoue. Le trafic y circule en WireGuard, le relais ne déchiffre rien.
#
# Durcissement (par rapport au tailscale.com public) :
# - derp.urls = [] : la flotte DERP de Tailscale Inc est retirée de la carte
# annoncée aux clients ; le seul relais est celui de ce VPS, qui fait
# partie du réseau. Aucune machine extérieure ne voit les IP des nœuds.
# - logtail.enabled = false : aucun log client n'est envoyé à Tailscale Inc.
#
# Côté clients : services/tailscale/default.nix, à importer sur chaque machine
# NixOS du flake.
#
# Exposition : headscale.marcpartensky.com -> Traefik local (Pangolin) ->
# 127.0.0.1:8090 en HTTP ; la route est dans services/traefik/dynamic.toml.
# Le DERP embarqué passe par le MÊME endpoint HTTPS (/derp), donc par
# Traefik ; seul le STUN (UDP 3478) est exposé en direct, un reverse proxy
# ne portant pas l'UDP.
#
# Bootstrap / rotation : voir en bas du fichier.
services.headscale = {
enable = true;
address = "127.0.0.1";
port = 8090;
settings = {
# URL publique que les clients Tailscale utiliseront pour rejoindre le tailnet.
# headscale.marcpartensky.com doit pointer vers CE VPS et être routé (Pangolin)
# vers 127.0.0.1:8090 en HTTP.
# URL publique que les clients utilisent pour rejoindre le tailnet.
server_url = "https://headscale.marcpartensky.com";
# Le reverse proxy (Pangolin/Traefik) tourne en local sur ce VPS : on honore
# ses en-têtes X-Forwarded-* pour logguer la vraie IP source des clients.
# Traefik tourne en local sur ce VPS : honorer ses X-Forwarded-*.
trusted_proxies = [ "127.0.0.1/32" ];
# DERP : flotte publique Tailscale par défaut pour le fallback derrière NAT
# (fonctionne out-of-the-box). Le data plane reste du WireGuard chiffré; le
# relais ne voit que des paquets qu'il ne peut pas déchiffrer. Pour une
# souveraineté totale plus tard : activer derp.server (embarqué) + exposer
# STUN, cf. docs headscale (exige server_url en https et TLS terminé par
# headscale lui-même, pas via reverse proxy).
derp = {
# Aucune carte DERP extérieure : uniquement le DERP embarqué ci-dessous.
urls = [ ];
paths = [ ];
auto_update_enabled = false;
server = {
enabled = true;
region_id = 900; # hors plage des régions officielles (1-30)
region_code = "vps";
region_name = "VPS RackNerd";
# STUN : aide au hole punching, en UDP direct sur l'IP publique.
stun_listen_addr = "0.0.0.0:3478";
# La région du DERP embarqué est ajoutée à la carte automatiquement,
# annoncée sur server_url (port 443, via Traefik).
automatically_add_embedded_derp_region = true;
# Seuls les nœuds du tailnet peuvent utiliser ce relais.
verify_clients = true;
# private_key_path : défaut du module
# (/var/lib/headscale/derp_server_private.key, généré au premier boot)
};
};
# Politique ACL depuis un fichier HuJSON (mode "file").
# ACL depuis un fichier HuJSON (mode "file").
policy = {
mode = "file";
path = ./policy.hujson;
};
# Ne jamais envoyer les logs clients vers Tailscale Inc.
# Aucun log client vers Tailscale Inc (défaut de 0.28, explicité ici).
logtail.enabled = false;
dns = {
@@ -39,4 +74,27 @@
};
environment.systemPackages = [ config.services.headscale.package ];
# STUN du DERP embarqué : UDP en direct, Traefik ne porte pas l'UDP.
networking.firewall.allowedUDPPorts = [ 3478 ];
}
# ================================ BOOTSTRAP ================================
#
# Déjà en place (23/10/2026) :
# - utilisateur headscale `marc` (id 1) ;
# - clé de pré-authentification réutilisable (expiration 10 ans, id 2),
# chiffrée dans secrets/headscale.yml (clé `preauth_key`), consommée par
# services/tailscale sur chaque machine.
#
# Rotation / nouvelle clé (après révocation par exemple) :
# sudo headscale users list # retrouver l'id de marc
# sudo headscale preauthkeys create -u <id> --reusable --expiration 0
# # remplacer preauth_key dans secrets/headscale.yml (sops), redéployer.
#
# Révocation d'un nœud : sudo headscale nodes delete -i <id> (ou nodes expire
# pour forcer une réauthentification).
#
# Les nœuds n'expirent pas (Expiration 0001-01-01 dans `nodes list`) ; en
# poser une si voulu :
# sudo headscale nodes expire -i <id> -e 2036-01-01T00:00:00Z
+26 -5
View File
@@ -1,6 +1,25 @@
{ pkgs, config, lib, ... }:
{ config, lib, pkgs, ... }:
{
sops.secrets."tailscale/auth_key" = {
# Nœud tailscale du mesh headscale de marc (voir services/headscale pour le
# contrôleur, qui tourne sur le VPS `anywhere`). À importer sur chaque machine
# NixOS du flake : tower, deck, anywhere, laptop...
#
# Enrôlement automatique : clé de pré-authentification réutilisable commune,
# dans secrets/headscale.yml (clé `preauth_key` ; destinataires anywhere,
# tower, mac, deck-host). Rotation : voir le bas du module headscale.
#
# Durcissement : disableUpstreamLogging coupe l'envoi de logs de tailscaled
# vers Tailscale Inc (équivalent de --no-logs-no-support) ; le contrôleur
# n'annonce que le DERP du VPS, jamais la flotte tailscale.com.
#
# Piège du premier enrôlement : tailscaled-autoconnect.service a 90 s pour
# joindre le contrôleur. Si l'endpoint public n'est pas encore prêt (cert
# Traefik en cours d'émission, route absente), il part en timeout et le nœud
# reste non enrôlé. Une fois l'endpoint OK :
# sudo systemctl restart tailscaled-autoconnect.service (ou un reboot)
sops.secrets."headscale/preauth_key" = {
sopsFile = ../../secrets/headscale.yml;
key = "preauth_key";
owner = "root";
group = "root";
mode = "0400";
@@ -12,12 +31,14 @@
services.tailscale = {
enable = true;
openFirewall = true;
useRoutingFeatures = "both"; # "client", "server" ou "both" si ce nœud est aussi exit node / subnet router
authKeyFile = config.sops.secrets."tailscale/auth_key".path;
# "both" : ce nœud peut aussi annoncer des sous-réseaux / être exit node.
useRoutingFeatures = "both";
authKeyFile = config.sops.secrets."headscale/preauth_key".path;
extraUpFlags = [
"--login-server=https://headscale.marcpartensky.com" # contrôleur auto-hébergé (headscale), pas le tailscale officiel
"--login-server=https://headscale.marcpartensky.com" # contrôleur auto-hébergé, pas Tailscale Inc
"--accept-routes"
];
disableUpstreamLogging = true;
};
networking.firewall.trustedInterfaces = [ "tailscale0" ];
+12
View File
@@ -32,6 +32,12 @@
[[http.services.int-api.loadBalancer.servers]]
url = "http://127.0.0.1:3003"
# headscale (contrôleur du mesh tailscale, cf services/headscale) : porte aussi
# le DERP embarqué des clients (/derp) ; le STUN est à part, en UDP 3478.
[http.services.headscale.loadBalancer]
[[http.services.headscale.loadBalancer.servers]]
url = "http://127.0.0.1:8090"
# --- ROUTERS ---
[http.routers.traefik]
rule = "Host(`traefik.vps.marcpartensky.com`)"
@@ -82,3 +88,9 @@
entryPoints = ["websecure"]
service = "int-api"
tls = { certResolver = "letsencrypt" }
[http.routers.headscale]
rule = "Host(`headscale.marcpartensky.com`)"
entryPoints = ["websecure"]
service = "headscale"
tls = { certResolver = "letsencrypt" }