Réseau ZeroTier 100% Indépendant avec Planet Custom (Self-Hosted)

Introduction

Ce guide détaille la mise en place d'un réseau ZeroTier entièrement auto-hébergé, utilisant un planet custom pour remplacer les serveurs racines publics de ZeroTier.
Le réseau créé est 100% indépendant de l'infrastructure ZeroTier propriétaire : aucun trafic ne transite par leurs serveurs.

Architecture

Points clés

Ordre des opérations


Phase 1 : Noms d'Hôtes

Sur A

hostname A
echo "A" > /etc/hostname
echo "127.0.1.1 A" >> /etc/hosts
reboot

Sur B

hostname B
echo "B" > /etc/hostname
echo "127.0.1.1 B" >> /etc/hosts
reboot

Sur C

hostname C
echo "C" > /etc/hostname
echo "127.0.1.1 C" >> /etc/hosts
reboot

Phase 2 : Compilation de ZeroTier

Sur A et B

Compilation de ZeroTier sur Alpine 3.23 :

echo "nameserver 1.1.1.1" > /etc/resolv.conf
apk update ; apk upgrade -a

# Compiler ZeroTier sur Alpine 3.23
apk add git make g++ linux-headers cargo rust openssl-dev pkgconfig
modprobe tun
echo "tun" >> /etc/modules
cd /root
git clone https://github.com/zerotier/ZeroTierOne.git
cd ZeroTierOne
make -j$(nproc)
make install

Nettoyage des outils de compilation sur A et B (les sources ne sont plus nécessaires) :

cd /root
rm -rf ZeroTierOne
apk del git make g++ linux-headers cargo rust openssl-dev pkgconfig
apk cache clean

# Dépendances nécessaires au fonctionnement
apk add openssl libstdc++ libgcc

Sur C

(IMPORTANT : ZT_NONFREE=1 est requis pour le contrôleur !)

echo "nameserver 1.1.1.1" > /etc/resolv.conf
apk update ; apk upgrade -a

# Compiler ZeroTier sur Alpine 3.23
# IMPORTANT : ZT_NONFREE=1 est requis pour le contrôleur !
apk add git make g++ linux-headers cargo rust openssl-dev pkgconfig
modprobe tun
echo "tun" >> /etc/modules
cd /root
git clone https://github.com/zerotier/ZeroTierOne.git
cd ZeroTierOne
git checkout 1.14.2
make -j$(nproc) ZT_NONFREE=1
make install

(IMPORTANT : NE PAS supprimer les sources sur C ! Elles sont nécessaires pour construire mkworld (planet custom) à la phase 4.)

# Dépendances nécessaires au fonctionnement
apk add openssl libstdc++ libgcc jq curl

Phase 3 : Service OpenRC + Démarrage sur C

Sur A, B et C

Créer le service OpenRC :

cat << 'EOF' > /etc/init.d/zerotier-one
#!/sbin/openrc-run
description="ZeroTier One virtual network service"
depend() {
 need net
 after firewall
}
command="/usr/sbin/zerotier-one"
command_args="-d"
pidfile="/var/lib/zerotier-one/zerotier-one.pid"
start_pre() {
 checkpath --directory --owner root:root --mode 0755 /var/lib/zerotier-one
}
EOF
chmod +x /etc/init.d/zerotier-one
rc-update add zerotier-one

Sur C uniquement

On démarre C en premier pour générer son identité :

rc-service zerotier-one start
sleep 5

# Vérifier que ZeroTier fonctionne
zerotier-cli info
zerotier-cli status

(NE PAS encore démarrer ZeroTier sur A et B ! Il faut d'abord construire le planet custom et le leur distribuer.)


Phase 4 : Construction du Planet Custom sur C

Sur C

Définir les variables et récupérer l'identité publique de C :

# Variables
SOCKET="192.168.1.150/9993"
IDENTITY=$(cat /var/lib/zerotier-one/identity.public)

echo "Identité publique de C : $IDENTITY"

Modifier mkworld.cpp pour définir C comme unique serveur racine :

cd /root/ZeroTierOne/attic/world
cp mkworld.cpp mkworld.cpp.original

head -n 85 mkworld.cpp > mkworld_new.cpp

cat >> mkworld_new.cpp << ROOTS

	// C - Self-hosted root server
	roots.push_back(World::Root());
	roots.back().identity = Identity("$IDENTITY");
	roots.back().stableEndpoints.push_back(InetAddress("$SOCKET"));
ROOTS

tail -n +109 mkworld.cpp >> mkworld_new.cpp
mv mkworld_new.cpp mkworld.cpp

# Vérification visuelle
echo "=== Vérification de mkworld.cpp (lignes 80-100) ==="
cat -n mkworld.cpp | head -100 | tail -20

Compiler mkworld et générer le fichier planet :

# Compiler mkworld
c++ -std=c++11 -I../.. -I../../ext -I.. -g -o mkworld \
 ../../node/C25519.cpp \
 ../../node/Salsa20.cpp \
 ../../node/SHA512.cpp \
 ../../node/Identity.cpp \
 ../../node/Utils.cpp \
 ../../node/InetAddress.cpp \
 ../../osdep/OSUtils.cpp \
 mkworld.cpp -lm

# Générer le fichier planet (world.bin)
./mkworld

(CRITIQUE : Sauvegarder les clés de signature ! Ces clés sont nécessaires pour mettre à jour le planet ultérieurement.)

mkdir -p /root/planet-signing-keys-backup
cp previous.c25519 current.c25519 world.bin /root/planet-signing-keys-backup/
echo "=== Clés de signature sauvegardées dans /root/planet-signing-keys-backup/ ==="

Phase 5 : Installation du Planet Custom sur C + Nettoyage

Sur C

Arrêter ZeroTier, sauvegarder l'ancien planet et installer le nouveau :

# Arrêter ZeroTier
pkill -9 zerotier-one
sleep 3

# Sauvegarder l'ancien planet et installer le nouveau
cp /var/lib/zerotier-one/planet /var/lib/zerotier-one/planet.original
cp /root/ZeroTierOne/attic/world/world.bin /var/lib/zerotier-one/planet

# Redémarrer ZeroTier avec le nouveau planet
zerotier-one -d
sleep 5

# Vérifier que C fonctionne avec le nouveau planet
zerotier-cli peers
zerotier-cli info

Préparer le base64 du planet pour le transférer à A et B :

PLANET_BASE64=$(base64 -w 0 /var/lib/zerotier-one/planet)
echo ""
echo "PLANET_BASE64 à copier sur A et B :"
echo "=================================="
echo "$PLANET_BASE64"
echo "=================================="
echo ""

(Exemple de PLANET_BASE64 : AQAAAAAI6skKAAABbOPiOVX0/te4qba7OEfX3as9H+uzj/tRHwWM5kH4Akx0ANR/Dkk...Jwk=)

Nettoyage des sources sur C :

cd /root
rm -rf ZeroTierOne
apk del git make g++ linux-headers cargo rust openssl-dev pkgconfig
apk cache clean

Phase 6 : Création du Réseau via le Contrôleur sur C

Sur C

Récupérer les informations nécessaires :

NODE_ID_C=$(zerotier-cli info | awk '{print $3}')
AUTH_TOKEN=$(cat /var/lib/zerotier-one/authtoken.secret)
NETWORK_SUFFIX="020101" # 6 caractères hex au choix
NETWORK_ID="${NODE_ID_C}${NETWORK_SUFFIX}"

echo "Node ID de C : $NODE_ID_C"
# 35d67877bd
echo "Network ID sera : $NETWORK_ID"
# 35d67877bd020101

Créer le réseau via l'API locale :

curl -s -X POST \
 -H "X-ZT1-Auth: ${AUTH_TOKEN}" \
 -d '{}' \
 "http://127.0.0.1:9993/controller/network/${NETWORK_ID}"

Configuration complète du réseau :

curl -s -X POST \
 -H "X-ZT1-Auth: ${AUTH_TOKEN}" \
 -H "Content-Type: application/json" \
 -d '{
 "name": "poc-selfhosted",
 "private": true,
 "v4AssignMode": {"zt": true},
 "ipAssignmentPools": [
 {"ipRangeStart": "10.147.17.1", "ipRangeEnd": "10.147.17.254"}
 ],
 "routes": [
 {"target": "10.147.17.0/24", "via": null}
 ],
 "rules": [
 {"type": "ACTION_ACCEPT"}
 ]
 }' \
 "http://127.0.0.1:9993/controller/network/${NETWORK_ID}" | jq .

Joindre C à son propre réseau :

zerotier-cli join ${NETWORK_ID}

Phase 7 : Déploiement du Planet Custom sur A et B

Sur A et B

Coller ici le PLANET_BASE64 affiché à la phase 5, puis installer le planet custom :

# Coller ici le PLANET_BASE64 affiché à la phase 5
PLANET_BASE64="AQAAAAAI6skKAAABbOPiOVX0/te4qba7OEfX3as9H+uzj/tRHwWM5kH4Akx0ANR/DkkKOr0QxRFYuAtTYfmchYnZKGT2EAQAdLp6Finec1rs4eXF57CH7zoEpFEUIYd0TGjf9fvqVuWoQuZzTyBhIAQGcxYppXyTXvfKiraagdGNk7bFQar1srrd+4LiJkfDAqh7aYQ2a7jmrfD5JoYZuGsDUjo8MaqbGwRivqPoGWA8ATXWeHe9AIq300ojyVbDXIZD9u/Y0g7qB2LqnOUIjtVT+l6I/Rsc7A8XJIXNZLUN55yJ58r9IEO9Tuk6h9ikAd8r3EN3tXoAAQTAqAGWJwk="

# Sauvegarder l'ancien planet et installer le custom
cp /var/lib/zerotier-one/planet /var/lib/zerotier-one/planet.original
echo "$PLANET_BASE64" | base64 -d > /var/lib/zerotier-one/planet

# Démarrer ZeroTier avec le planet custom
rc-service zerotier-one start
sleep 5

# Vérifier que A/B voient C comme seul peer (LEAF)
zerotier-cli peers
# C doit apparaître comme PLANET ou LEAF, pas les serveurs publics ZeroTier

Phase 8 : Jonction au Réseau depuis A et B

Sur A

Utiliser le NETWORK_ID obtenu à la phase 6 :

NODE_ID_C="35d67877bd"
NETWORK_SUFFIX="020101"
NETWORK_ID="${NODE_ID_C}${NETWORK_SUFFIX}"

zerotier-cli join ${NETWORK_ID}

NODE_ID_A=$(zerotier-cli info | awk '{print $3}')
echo "Node ID de A : $NODE_ID_A"
# bf6c417328

Sur B

NODE_ID_C="35d67877bd"
NETWORK_SUFFIX="020101"
NETWORK_ID="${NODE_ID_C}${NETWORK_SUFFIX}"

zerotier-cli join ${NETWORK_ID}

NODE_ID_B=$(zerotier-cli info | awk '{print $3}')
echo "Node ID de B : $NODE_ID_B"
# 13aa0e39a4

Phase 9 : Autorisation des Membres sur C

Sur C

Lister les membres en attente :

AUTH_TOKEN=$(cat /var/lib/zerotier-one/authtoken.secret)
NODE_ID_C=$(zerotier-cli info | awk '{print $3}')
NETWORK_ID="${NODE_ID_C}020101"

# Lister les membres en attente
curl -s -H "X-ZT1-Auth: ${AUTH_TOKEN}" \
 "http://127.0.0.1:9993/controller/network/${NETWORK_ID}/member" | jq .

Autoriser A (remplacer par le vrai NODE_ID_A) :

NODE_ID_A="bf6c417328"

curl -s -X POST \
 -H "X-ZT1-Auth: ${AUTH_TOKEN}" \
 -H "Content-Type: application/json" \
 -d '{
 "authorized": true,
 "ipAssignments": ["10.147.17.1"]
 }' \
 "http://127.0.0.1:9993/controller/network/${NETWORK_ID}/member/${NODE_ID_A}" | jq .

Autoriser B (remplacer par le vrai NODE_ID_B) :

NODE_ID_B="13aa0e39a4"

curl -s -X POST \
 -H "X-ZT1-Auth: ${AUTH_TOKEN}" \
 -H "Content-Type: application/json" \
 -d '{
 "authorized": true,
 "ipAssignments": ["10.147.17.2"]
 }' \
 "http://127.0.0.1:9993/controller/network/${NETWORK_ID}/member/${NODE_ID_B}" | jq .

Autoriser C lui-même :

curl -s -X POST \
 -H "X-ZT1-Auth: ${AUTH_TOKEN}" \
 -H "Content-Type: application/json" \
 -d '{
 "authorized": true,
 "ipAssignments": ["10.147.17.254"]
 }' \
 "http://127.0.0.1:9993/controller/network/${NETWORK_ID}/member/${NODE_ID_C}" | jq .

Phase 10 : Vérification et Tests

Sur A, B et C

Vérifier l'état du réseau :

zerotier-cli listnetworks
# A : OK PRIVATE 10.147.17.1
# B : OK PRIVATE 10.147.17.2
# C : OK PRIVATE 10.147.17.254

ip addr show | grep zt
# Doit montrer une interface zt* avec l'IP assignée

Sur A

zerotier-cli peers
ping -c 3 10.147.17.2 # vers B
ping -c 3 10.147.17.254 # vers C

Sur B

zerotier-cli peers
ping -c 3 10.147.17.1 # vers A
ping -c 3 10.147.17.254 # vers C

Test de trafic chiffré (optionnel)

Sur C – Capturer le trafic sur l'interface physique :

apk add tcpdump
tcpdump -i eth0 -n udp port 9993 -X

Sur B – Lancer un listener :

nc -l -p 12345

Sur A – Envoyer un message (le trafic capturé sur C doit être illisible) :

echo "MESSAGE_SECRET_TEST_12345" | nc 10.147.17.2 12345

Récapitulatif de l'Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│ RÉSEAU ZEROTIER SELF-HOSTED │
│ ─────────────────────────────── │
│ │
│ A (10.147.17.1) ◄────► C (10.147.17.254) ◄────► B (10.147.17.2) │
│ Client Serveur racine Client │
│ 192.168.1.149 + Contrôleur 192.168.1.119 │
│ 192.168.1.150 │
│ │
│ • C est le PLANET (serveur racine unique) │
│ • C est le CONTRÔLEUR du réseau │
│ • Aucun trafic vers les serveurs ZeroTier publics │
│ • Le moon n'est pas nécessaire (C est déjà racine) │
│ • Les clés de signature sont dans : │
│ /root/planet-signing-keys-backup/ sur C │
└─────────────────────────────────────────────────────────────────────────┘

Recommandations pour une Mise en Production

Cette procédure est conçue comme un PoC (Proof of Concept) fonctionnel. Pour un déploiement en production, les points suivants doivent être pris en compte.

Cumul des rôles sur C : serveur racine et contrôleur

Dans cette architecture, C cumule les rôles de serveur racine (planet) et de contrôleur de réseau. Cela en fait un point de défaillance unique (SPOF) : si C tombe, plus aucun nouveau peer ne peut rejoindre le réseau, et les clients existants perdent leur point de rendez-vous.

Pour un environnement de production, il est recommandé de :

Filtrage réseau avec nftables sur C

En production, C sera exposé sur Internet avec une IP publique. Il est indispensable de configurer un pare-feu avec nftables pour :

Exemple de configuration nftables minimale pour C :

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
 chain input {
 type filter hook input priority 0; policy drop;

 # Loopback
 iif lo accept

 # Connexions établies
 ct state established,related accept

 # SSH (adapter le port si nécessaire)
 tcp dport 22 accept

 # ZeroTier
 udp dport 9993 accept

 # ICMP / ICMPv6
 ip protocol icmp accept
 ip6 nexthdr icmpv6 accept
 }

 chain forward {
 type filter hook forward priority 0; policy drop;
 }

 chain output {
 type filter hook output priority 0; policy accept;
 }
}

(Penser à installer nftables avec apk add nftables, activer le service avec rc-update add nftables, et placer la configuration dans /etc/nftables.nft.)



↑ Haut de page