Aller au contenu

Le VPN (Headscale / Tailscale)

Le VPN maille les zones, le serveur de coordination et les appareils distants : Headscale 🡕 coordonne depuis le HCS, Tailscale 🡕 est le client de chaque nœud. Cette page réunit les gestes d’exploitation. Routage, DNS, MagicDNS, politique d’accès et clés sont expliqués dans Fonctionnement du tailnet.

Activer la coordination et déclarer le HCS (profile: hcs) suffit :

etc/config.yaml
network:
coordination:
enable: true
hostname: "hcs"
domain: "headscale"

Le rôle de chaque nœud se déduit de la topologie :

NœudIdentitéRôle
HCStag:hcscoordination, DNS pivot, services globaux, exit node (fermé)
Passerelletag:gw-<zone>routeur de sous-réseau : publie le /16 de sa zone
Poste d’administrationtag:adminSSH vers toutes les machines
Appareil personnelson propriétaireservices, selon ses groupes

Un poste d’administration est un hôte dont groups contient admin et dont le fichier d’hôte active le client :

usr/machines/portable/configuration.nix
darkone.service.tailscale.enable = true;
darkone.service.tailscale.autoPauseOnLan.enable = true;

autoPauseOnLan met le client en pause quand le portable est branché dans une zone (NetworkManager requis).

Les machines rejoignent le tailnet sans clé partagée : le HCS crée pour chacune une clé à usage unique, avec ses tags déclarés. just configure s’en charge, donc just full-install aussi. La recette seule se relance à tout moment :

Fenêtre de terminal
just tailnet-enroll gw
[ DNF ] TAILNET • gw not enrolled, backend NeedsLogin: sending a single-use key...
[ DNF ] TAILNET • gw: gw tag:gw-maison 100.64.0.8

Elle est idempotente :

SituationEffet
Nœud enrôlé, nom et tags conformesrien (already enrolled)
Tags ou nom différentscorrigés sur le HCS, sans reconnexion
Jamais enrôlé, supprimé ou réinstallénouvelle clé, enregistrement
Ancien nœud du même nom hors lignesupprimé, le nouveau prend le nom
Ancien nœud du même nom en lignerefus : deux machines portent ce nom
Hôte sans client Tailscaleignoré

Le mécanisme est détaillé dans Clés d’activation et enrôlement.

Une passerelle s’enrôle comme toute machine ; deux points lui sont propres.

  1. Vérifier la route de sa zone

    La passerelle annonce le /16 de sa zone, que la politique approuve d’office pour tag:gw-<zone>. Sur le HCS :

    Fenêtre de terminal
    sudo headscale nodes list-routes
  2. Déclarer son adresse tailnet

    Le HCS joint le résolveur de la zone par cette adresse (DNS pivot), comme les services qui la visent (sauvegardes, supervision). La relever dans la sortie de just tailnet-enroll, puis la reporter :

    etc/config.yaml
    zones:
    maison:
    gateway:
    vpn:
    ipv4: "100.64.0.8"
  3. Propager l’adresse

    Fenêtre de terminal
    just generate
    just apply hcs

    Redéployer aussi les hôtes qui visent cette adresse. HeadscaleNodeDrift signale une adresse déclarée qui ne correspond plus.

Un smartphone ou un ordinateur personnel se connecte avec l’identité Kanidm de son propriétaire : ni clé, ni validation manuelle.

Peut se connecterCondition dans etc/config.yaml
Administrateurprofil admin ou nix-admin
Membre d’une zonegroupe zone-<zone>

Ces comptes forment le groupe Kanidm tailnet, provisionné tout seul ; Kanidm refuse les autres. Pour ouvrir l’accès, ajouter le groupe à l’utilisateur, régénérer, puis just apply hcs. L’utilisateur suit ensuite Connecter un appareil.

L’appareil appartient à la personne (alice@) et expire après 180 jours : elle se reconnecte alors avec son identifiant.

Fenêtre de terminal
sudo headscale nodes list # appareils, propriétaires, expiration
sudo headscale nodes expire -i <id> # forcer une reconnexion
sudo headscale nodes delete -i <id> # retirer l'appareil

La politique est générée depuis la topologie : ses règles sont détaillées dans Politique d’accès. Des options l’ajustent, sur le HCS :

Option darkone.service.headscale.DéfautEffet
policy.adminDevices{ }appareils personnels autorisés en SSH : nom → IPv4 tailnet
policy.exitNodeSources[ ]sources autorisées à sortir sur Internet par le HCS
policy.extraHosts{ }alias supplémentaires : nom → CIDR
policy.extraAcls[ ]règles ajoutées après les règles générées
policy.enforcetruefalse : tout autorisé, pour une migration seulement
nodeExpiry"180d"expiration des appareils personnels (0 : jamais)
usr/machines/hcs/configuration.nix
darkone.service.headscale.policy = {
# SSH depuis le téléphone d'Alice (adresse lue dans `headscale nodes list`)
adminDevices.phone-alice = "100.64.0.9";
# Exit node du HCS ouvert aux administrateurs
exitNodeSources = [ "group:admins" ];
# Imprimante réseau pour tous les appareils personnels
extraHosts.printer = "10.0.4.10/32";
extraAcls = [
{
action = "accept";
src = [ "autogroup:member" ];
dst = [ "printer:631" ];
}
];
};

Puis just apply hcs : la politique est vérifiée à la construction, et Headscale la recharge sans redémarrer.

Sur le HCS (just enter hcs) :

CommandeUsage
sudo headscale nodes listnœuds, propriétaire, tags, adresses, expiration
sudo headscale nodes list-routesroutes annoncées et approuvées
sudo headscale nodes rename -i <id> <nom>renommer un nœud
sudo headscale nodes expire -i <id>forcer une reconnexion
sudo headscale nodes delete -i <id>supprimer un nœud
sudo headscale users listpropriétaires d’appareils personnels
sudo headscale preauthkeys listclés pré-autorisées : tags, usage, expiration
sudo headscale policy getpolitique servie
sudo dnf-tailnet-enroll status <hôte>identité déclarée d’un hôte

Tags et routes ne se règlent pas à la main : just tailnet-enroll pose les tags déclarés, la politique approuve les routes. Les autres réglages du client (routes annoncées, SSH Tailscale coupé, exit node) sont réalignés par tailscaled-autoconnect, au démarrage et aux déploiements qui le modifient.

CommandeRépond à
tailscale statuspairs, en ligne ou non, chemin direct ou relais
tailscale ping <nœud>chemin réel : via <ip>:41641 (direct) ou via DERP
tailscale whois <ip>nœud, tags ou propriétaire derrière une adresse
tailscale dns statusMagicDNS, DNS scindé, domaines de recherche reçus
journalctl -u tailscaled-autoconnectdernier enregistrement ou réalignement des réglages

Sans option --bypass…, headscale policy check compile un fichier contre les vrais nœuds et exécute son bloc tests, sans toucher à la politique en place.

  1. Partir de la politique servie :

    Fenêtre de terminal
    sudo headscale policy get > /tmp/policy.json
  2. Ajouter un bloc tests : une source, puis les destinations attendues, chacune un hôte unique (adresse et port) :

    "tests": [
    { "src": "alice@", "accept": ["100.64.0.8:443"], "deny": ["100.64.0.8:22"] }
    ]
  3. Vérifier :

    Fenêtre de terminal
    sudo headscale policy check -f /tmp/policy.json
SymptômeVérifierRemède
TailscaleUnhealthy, backend NeedsLogintailscale status sur le nœudjust tailnet-enroll <hôte>
Zone injoignable depuis le tailnetheadscale nodes list-routespasserelle enrôlée avec son tag, route approuvée
Service de zone refusé à un appareilgroupes de l’utilisateur, tailscale whois <ip>ajouter zone-<zone>, just apply hcs
SSH refusé sur une adresse tailnetposte tag:admin ou appareil adminDevicesdéclarer l’appareil
Noms internes non résolus hors zonetailscale dns statusactiver « Use Tailscale DNS »
Trafic lenttailscale ping affiche via DERPUDP sortant filtré, ou NAT strict des deux côtés
no declared tailnet identityhôte tagué, HCS déployé depuis sa déclarationjust apply hcs
named … is online, not deleting itdeux machines portent le même nométeindre l’ancienne ou renommer l’hôte

Toutes les 15 minutes, headscale-audit compare les nœuds réels à la topologie déclarée, sans rien modifier. Le détail des écarts est dans son journal :

Fenêtre de terminal
journalctl -u headscale-audit -n 20
AlerteSignaleAprès
HeadscaleNodeDriftHCS ou passerelle absent, tags faux, adresse différente de celle déclarée30 min
HeadscaleUnexpectedNodenœud tagué non déclaré, ou appareil d’un compte hors du groupe tailnetaussitôt
HeadscalePolicyInvalidpolitique refusée, ou politique servie différente du fichier30 min
HeadscaleOidcFallbackKanidm absent au démarrage de Headscale : connexions OIDC coupéesaussitôt
HeadscaleNodeExpiringappareil personnel qui expire sous 7 jours1 h
HeadscaleAuditStaleaudit sans succès depuis plus d’une heureaussitôt

Toutes sont en warning, sur le Prometheus qui supervise le HCS. Un poste d’administration absent n’est pas une anomalie ; présent, ses tags sont vérifiés.

Un tailscaled peut décrocher silencieusement de Headscale : la connexion de contrôle est perdue alors que le service reste « actif », et le sous-réseau d’une passerelle devient injoignable. Un watchdog local détecte et corrige ce cas, sur chaque client Tailscale.

AspectComportement
Sondetoutes les 60 s : tailscale status (backend, état en ligne)
Anti-rebond3 sondes en échec d’affilée (~3 min) avant d’agir
Correctionredémarre tailscaled, qui se reconnecte au plan de contrôle
Anti-boucleune seule correction par fenêtre de 10 min
Nœud non enrôléaucun redémarrage (NeedsLogin) : just tailnet-enroll
Portable en pausewatchdog à l’arrêt, métrique retirée
Observabilitédnf_tailscale_healthy, dnf_tailscale_selfheal_restarts_total (textfile node_exporter)

Deux alertes accompagnent le mécanisme :

  • TailscaleUnhealthy : toujours décroché après 10 min, malgré la correction ;
  • TailscaleFlapping : plus de 3 corrections en une heure.

Sur une passerelle, dnsmasq envoie tout domain.tld au résolveur du HCS à travers le tailnet : le client aurait besoin du VPN pour trouver le serveur qui monte le VPN. Au démarrage, en plus, le résolveur local n’écoute pas encore.

Chaque client Tailscale reçoit donc l’adresse publique du HCS dans son /etc/hosts, déduite de etc/config.yaml (le HCS lui-même est exclu). dnsmasq répond à ce nom depuis ce fichier, sans jamais le transmettre :

Fenêtre de terminal
grep headscale /etc/hosts # 203.0.113.10 headscale.domain.tld

Le client se connecte ainsi sans aucun résolveur, y compris quand le DNS local est en panne, précisément quand l’auto-réparation doit pouvoir agir.

  • Appareil personnel : voir Connecter un appareil personnel.
  • Portable d’administration : en déplacement, il joint toutes les zones par le tailnet (just enter <hôte>). Rebranché dans une zone, son client se met en pause et la passerelle locale reprend la main.