Aller au contenu

Fonctionnement du tailnet

Le tailnet est le VPN maillé du réseau : il relie les zones, le serveur de coordination (HCS) et les appareils distants. Headscale 🡕 le coordonne, Tailscale 🡕 le fait vivre sur chaque nœud. Cette page explique qui y figure, par où passent paquets et requêtes DNS, qui peut joindre quoi, et comment une machine y entre. Les gestes d’exploitation sont dans VPN.

Headscale distribue les informations, jamais le trafic : les nœuds échangent directement entre eux, chiffrés par WireGuard 🡕.

Diagram
PlanPorté parContenu
ContrôleHeadscale sur le HCS (HTTPS)identités, clés publiques, adresses, routes, DNS, filtres ACL
Donnéeschaque client Tailscale (UDP 41641)le trafic, d’un nœud à l’autre
Relaisserveurs DERP publics de Tailscalele trafic chiffré, faute de chemin direct
  • Les nœuds percent le NAT pour se joindre en direct. Le port UDP 41641 est ouvert sur le HCS et sur le WAN des passerelles, ce qui aide derrière un NAT strict.
  • Sans chemin direct (UDP filtré, NAT strict des deux côtés), le trafic passe par un relais DERP : toujours chiffré de bout en bout, mais plus lent.
  • HCS indisponible : les tunnels établis continuent de fonctionner, mais rien ne change (nouveau nœud, nouvelle route, nouvelle politique) jusqu’à son retour.

Deux sortes d’identités coexistent : les machines, identifiées par un tag qui dit leur rôle, et les appareils personnels, rattachés à leur propriétaire.

NœudIdentitéObtenue parExpiration
HCStag:hcsclé à usage unique (just tailnet-enroll)jamais
Passerelle d’une zonetag:gw-<zone>clé à usage uniquejamais
Poste d’administrationtag:adminclé à usage uniquejamais
Appareil personnelson propriétaire (alice@)connexion Kanidm180 jours
  • Les tags se déduisent de la topologie : profil hcs, passerelle de zone, hôte dont groups contient admin et qui active le client Tailscale.
  • Un nœud tagué n’appartient à personne (tagged-devices dans Headscale).
  • Seuls les comptes du groupe Kanidm tailnet connectent un appareil : administrateurs et membres d’une zone (groupe zone-<zone>).
  • Les tags libres d’un hôte dans etc/config.yaml n’accordent aucun droit.
  • Chaque nœud porte un nom MagicDNS : <nœud>.tailnet.internal.

Le tailnet donne une adresse à chaque nœud et relie les sous-réseaux des zones. Trajet d’un poste de la zone maison vers un serveur de la zone atelier :

Diagram
TrajetCheminMécanisme
Poste d’une zone → autre zonesa passerelle, le tailnet, la passerelle distanteroutes de sous-réseau
Appareil distant → service d’une zonele tailnet, la passerelle de la zoneroute de sous-réseau de la zone
Appareil distant → service globalle tailnet, le HCSadresse tailnet du HCS
Nœud → nœuddirectadresses 100.64.x.y
Nœud → Internetsa propre connexionexit node du HCS fermé par défaut
  • Adresses : Headscale les attribue dans l’ordre, dans 100.64.0.0/10 (et fd7a:115c:a1e0::/48). Une machine réinstallée (nouvelle clé de machine) en reçoit une nouvelle.
  • Routes annoncées : chaque passerelle annonce le /16 de sa zone. La politique l’approuve d’office pour le seul tag:gw-<zone> : une passerelle ne peut pas détourner le sous-réseau d’une autre zone.
  • Routes acceptées : chaque client installe les /16 des autres zones dans la table de routage 52 de Tailscale, consultée avant la table principale. Une passerelle n’y installe pas sa propre zone.
  • Sans SNAT : une passerelle relaie sans masquer la source (--snat-subnet-routes=false). Une machine de la zone voit la vraie adresse tailnet et répond par sa route par défaut, la passerelle ; d’où le filtrage de chemin inverse souple des passerelles (règle R12 de la sécurité).
  • Exit node : le HCS s’annonce comme sortie vers Internet, approuvée d’office, mais aucune règle ne l’autorise tant que policy.exitNodeSources est vide.
  • Filtrage : la politique s’applique à l’arrivée. Pour une zone, c’est sa passerelle qui trie ce qui entre depuis le tailnet.

Ce qu’une passerelle laisse traverser est détaillé dans Ce qui traverse une passerelle.

Chaque machine interroge un résolveur différent, mais les noms d’une zone finissent toujours chez le même détenteur : la passerelle de cette zone.

MachineRésolveurNoms d’une zoneServices globauxInternet
Poste d’une zone, passerellednsmasq de la passerelle, derrière AdGuard Home s’il est activédnsmasq, ou passerelle de la zonednsmasq, ou unbound du HCSDNS publics, filtrés par AdGuard Home
HCSunbound, localpasserelle de la zoneunboundQuad9, chiffré
Appareil distantMagicDNS (100.100.100.100)par unbound du HCSpar unbound du HCSrésolveur du réseau visité
Diagram
  • Passerelle : dnsmasq connaît les machines de toutes les zones et les services déclarés. Un nom inconnu d’une autre zone part vers la passerelle de cette zone (adresse LAN, à travers le tailnet) ; le reste de domain.tld part vers unbound, sur le HCS.
  • Services d’une zone : *.<zone>.domain.tld pointe vers l’adresse LAN de la passerelle de la zone, qui les sert derrière son proxy.
  • HCS : unbound écoute sur 127.0.0.1 et sur l’adresse tailnet du HCS, pour les seules adresses du tailnet. Il transmet <zone>.domain.tld au résolveur de la passerelle, à son adresse tailnet : d’où l’importance de déclarer vpn.ipv4.
  • headscale.domain.tld : jamais demandé au tunnel qu’il sert à monter, voir Amorçage du plan de contrôle.

MagicDNS est le petit résolveur que chaque client Tailscale embarque, à l’adresse 100.100.100.100. Headscale l’active pour tout le tailnet et lui transmet ses consignes :

ConsigneValeurEffet
Noms des nœudstailnet.internalgw-atelier.tailnet.internal → adresse tailnet, tenue à jour par Headscale
Domaine de recherchetailnet.internalssh gw-atelier suffit
DNS scindédomain.tld → unbound du HCSles noms internes partent, par le tunnel, au résolveur pivot
Résolveur globalaucunle reste va au résolveur habituel de l’appareil

Un client ne s’en sert comme résolveur que s’il accepte le DNS du tailnet (--accept-dns).

Machine--accept-dnsRôle de MagicDNS
Appareil personnel, portable hors zoneouirésolveur du système : noms des nœuds et DNS scindé
Portable rebranché dans sa zoneclient en pauseaucun, la passerelle locale reprend la main
Passerellenonnoms inverses des adresses 100.x, pour les statistiques d’AdGuard Home
HCSouiprésent, mais le système interroge unbound (127.0.0.1)
Poste d’une zonepas de clientaucun
  • Nommer les nœuds hors des zones : un portable en déplacement joint gw-atelier en direct sur le tailnet, sans dépendre d’une route de sous-réseau ni du DNS d’une zone. C’est ce que fait just enter gw-atelier hors de chez soi.
  • Résoudre les noms internes partout : le DNS scindé suffit, sans faire passer tout le DNS dans le VPN.
  • Garder Internet hors du tunnel : si le chemin VPN faiblit, la résolution des noms publics tient (pourquoi).
  • Laisser la main aux passerelles : elles gardent leur propre DNS (--accept-dns=false), dont dépendent leur zone et ses clients.

Un appareil distant ouvre un service de la zone maison :

Diagram
  1. MagicDNS reconnaît domain.tld et transmet la question, par le tunnel, à unbound.
  2. unbound la transmet au résolveur de la passerelle maison, à son adresse tailnet.
  3. La réponse est l’adresse LAN de la passerelle (10.0.1.1).
  4. L’appareil l’atteint par la route de sous-réseau de la zone ; la politique l’autorise sur le port 443 (gateway-maison).

Sur le tailnet, rien ne passe par défaut. Headscale compile une politique en filtres qu’il pousse à chaque nœud ; chacun jette ce qu’aucune règle n’autorise. Pour une zone, le tri se fait sur sa passerelle.

La politique n’est pas écrite à la main : DNF la génère depuis etc/config.yaml (zones, hôtes, utilisateurs), la vérifie à la construction (headscale policy check), puis Headscale la recharge à chaud au déploiement.

BriqueContenu généré
groupsgroup:admins (profils admin, nix-admin), group:zone-<zone> (groupe zone-<zone>)
tagOwnerstag:hcs, tag:gw-<zone>, tag:admin, détenus par group:admins
hostszone-<zone> (sous-réseau), gateway-<zone> (adresse LAN de la passerelle), postes d’administration (adresse LAN), adminDevices, extraHosts
autoApproversroute de chaque zone → tag:gw-<zone> ; exit node → tag:hcs
aclsles règles ci-dessous
#SourceDestinationPour
1machines taguées, sous-réseaux des zonestoutes les machines et zones, tous portsl’infrastructure : DNS, supervision, sauvegardes, déploiement
2appareils personnels (autogroup:member)HCS, ports 53 et 443DNS interne, services globaux
3group:adminspasserelles de toutes les zones, port 443les services de toutes les zones
4group:zone-<zone>passerelle de la zone, port 443les services de ses zones
5appareils personnelsappareils du même propriétaireentre ses propres appareils
6tag:admin, postes d’administration, adminDevicestoutes les machines et zones, port 22l’administration SSH
7policy.exitNodeSourcesInternetl’exit node du HCS, s’il est ouvert
8policy.extraAclslibreles besoins propres au réseau
  • Le SSH suit le poste, pas la personne : un administrateur sur son téléphone n’a pas le SSH, sauf appareil déclaré dans adminDevices.
  • gateway-<zone> en plus du tag : les services d’une zone se résolvent vers l’adresse LAN de sa passerelle, que tag:gw-<zone> ne couvre pas (il ne désigne que ses adresses tailnet).
  • Un groupe vide disparaît de la politique : une règle qui le citerait la ferait refuser en entier.
  • Mode ouvert (policy.enforce = false) : une règle unique autorise tout. Réservé à une migration, le temps de taguer les nœuds.

Les options qui ajustent la politique sont décrites dans Adapter la politique d’accès.

Un nœud prouve une seule fois à Headscale qu’il a le droit d’entrer. Ensuite, sa clé de nœud suffit : pour toujours s’il est tagué, jusqu’à expiration pour un appareil personnel.

CléRôle
Clé de machine/var/lib/tailscale du nœudidentifie la machine auprès de Headscale ; perdue à la réinstallation
Clé de nœud/var/lib/tailscale du nœudchiffre le trafic ; Headscale publie sa partie publique aux pairs
Clé pré-autoriséecréée par Headscaleautorise un enregistrement sans navigateur, avec les tags qu’elle porte
Session OIDCKanidmautorise l’enregistrement d’un appareil personnel

just tailnet-enroll <hôte> fait entrer une machine, sans clé partagée :

Diagram
  • Tags imposés : le HCS crée la clé pour les tags déclarés de l’hôte (dnf-tailnet-enroll), quoi que demande l’appelant.
  • Usage unique, 10 minutes : sans valeur une fois servie.
  • Aucune trace : la clé passe par un tube, jamais par une ligne de commande, une variable, un disque ou le journal d’entrées et sorties de sudo (règles NOLOG_INPUT et NOLOG_OUTPUT).
  • Déposée en mémoire : dnf-tailnet-join la pose dans /run/tailscale-enroll, tailscaled-autoconnect l’utilise (tailscale up --force-reauth) puis l’efface.
  • Portable en pause dans sa zone : il s’enregistre sans routes ni DNS, puis se remet en pause.
  • Contrôle final : adopt pose les tags et le nom déclarés, remplace un ancien nœud hors ligne du même nom, et la recette vérifie le résultat.
  1. L’application Tailscale contacte Headscale, qui la renvoie vers Kanidm.
  2. Kanidm n’accepte que les membres du groupe tailnet ; Headscale le vérifie aussi.
  3. L’appareil est enregistré au nom de la personne (alice@) et expire après 180 jours (nodeExpiry) : elle se reconnecte alors.