In a previous post I wrote about using ZeroTier for my homelab and mentioned I ended up writing two scripts:

  • one for joining a network and authorizing the new device
  • another for creating DNS entries in DNSMASQ for DNS lookup

This first part will focus on how I join my new servers to my ZeroTier labnetwork. For more information about ZeroTier in general go read my other post that go in much more detail about that. Part two about DNS for ZeroTier will soon be available.

Lets get started!

joinnetwork

This script will automatically join a new device (node/member) to a ZeroTier network. It can be used standalone or with any provisioning, configuration management, or deployment tools. The Network ID must be specified to join the network. If an API Token is specified the script will also authorize the member. The script can also autorize an existing member that already have joined but still haven’t been autorized by a network owner/admin.
The script depends on curl, jq and zerotier-one to be installed for it to work, though it will check for these to be installed at runtime.

The whole idea is to automatically join and authorize a new member to a network so it makes absolute most sense to run it with the --api option. Just running it with --network is the same as just junning zerotier-cli join directly from the shell.

When runnig the script it will right of the bat check if the prequsites a taken care of and preform a simple syntax check for <API-TOKEN> and <NETWORK-ID>. If anything is wrong or missing it will inform you.
Next the script will check if the the device (Node ID) is already a member or not. New devices will via zerotier-cli join be joined and if already a member the script will then exit and tell if the member is authorized or not (outputting assigned IP if authorized). If you have specified <API-TOKEN> the script will not just join a member but via curl call the API of the network controller to authorize the member. A existing network member will just be authorized when <API-TOKEN> is specified. Common for both senarios is that the script will exit and output the assigned ZeroTier IP address for the network.
NOTE: The script tests via the local zerotier-cli utility when authorization have registered on the client before proceeding. It would be much faster to run these tasks asynchronous, but as I need to use the assigned ZeroTier IP right after the script is done, I wrote it so I could trust the network to be available for the subsequent tasks.

Usage:
To join and authorize a device to a network just run:

1
$ sudo joinnetwork --network=<NETWORK-ID> --api=<API-TOKEN> --member=<NAME-OF-DEVICE>

Runtime options can be set in whatever order you prefer.
They can be listed by running joinnetwork --help:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-a=,  --api=                      
Specifies the ZeroTier API Token (account) to authorize the new device member.

-u=, --url=
URL to a standalone ZeroTier network controller API.
Default value is https://my.zerotier.com/api as this is the public network controller hosted by ZeroTier.
NOTE: This argument is only for those who run a standalone ZeroTier network controller.

-n=, --network=
The ZeroTier network (Network ID) to join.

-m=, --member=
Set the device member shortname for the member on the ZeroTier network.
If not set the unique device Node ID (10-digit alphanumeric) is configured as member shortname.

-d=, --description=
Set the device member description field for this client.

Alternatively just edit the script variables in the section at line 78 to avoid the need for any runtime parameters.

  • NETWORK ID is the Network ID you wish to join and must be provided by an admin or existing member. Existing members can see the desired Network ID on the my.zerotier.com Network page or via the zeroctier-cli listnetworks command.
  • APIKEY is your API Access Token from the my.zerotier.com Account page. When specified this token will be used to authorize the new member.

It’s possible to check if the member is authorized via the API:

1
2
3
4
# First get your NodeID (some OS require sudo here)
$ MYID=$(zerotier-cli info | cut -d " " -f 3)
#Then call the API
$ curl -s -H "Authorization: Bearer <API-TOKEN>" https://my.zerotier.com/api /network/<NETWORK-ID>/member/$MYID | jq '.config.authorized'

…or via the webUI:

and last on the local client via zerotier-cli

1
2
3
4
$ zerotier-cli listnetworks
200 listnetworks <nwid> <name> <mac> <status> <type> <dev> <ZT assigned ips>
200 listnetworks a09acf02337076f6 already_member f6:6b:c9:a7:ce:aa OK PRIVATE feth4094 172.24.249.40/16
200 listnetworks 9bee8941b5a3c534 new_member 36:d8:1a:21:8d:ec OK PRIVATE feth2063 172.25.249.40/16

Hammer demotime
In the demo two things happen:

  1. A new device wil be joinded to a network and given a proper name and description
  2. A existing member will be Authorized to get proper access and IP assigned.

Cloud-init and the gang
I prefer to not overcomplicate tings and for VMs I use cloud-init for initial run conifguration and Ansible for everytning after. As I tend to spin up workloads in diffrent places I very much rely on ZeroTier to even be able to manage or connect these workloads. So it is paramount that the VM is automatically connected to my ZeroTier labnetwork before anything else.
I won’t share my entire user-data cloud-config, but here’s a validated example that will install everything needed (zerotier, curl, jq) and then download and execute joinnetwork:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#cloud-config
#Remember to validate your YAML config via
users:
- name: username
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, admin, sudo
ssh-import-id: gh:UserName
shell: /bin/bash
apt:
primary:
- arches: [default]
uri: http://mirrors.dotsrc.org/ubuntu/
sources:
zerotier.list:
# Creates a file in /etc/apt/sources.list.d/ for the sources list entry
source: "deb http://download.zerotier.com/debian/bionic bionic main"
key: | # Key from https://github.com/zerotier/ZeroTierOne/blob/master/doc/contact%40zerotier.com.gpg
-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: GPGTools - https://gpgtools.org

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
=JgAv
-----END PGP PUBLIC KEY BLOCK-----
# Install packages on first boot
packages:
- curl
- jq
- zerotier-one
# run commands. runcmd only runs during the first boot
runcmd:
- mkdir -p /run/zerotier
- curl -s https://raw.githubusercontent.com/KimTholstorf/zerotier-scripts/master/joinnetwork -o, /run/zerotier/joinnetwork
- chmod +x /run/zerotier/joinnetwork
- bash /run/zerotier/joinnetwork --api=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --network=NNNNNNNNNNNNNNNN --member="short-name"
final_message: "The system is ready and prepped (took $UPTIME seconds)"

NOTE: If you install the cloud-init package on your system you can validate your YAML config via:

1
$ cloud-init devel schema --config-file your-user-data-file.txt

My scripts for ZeriTier is available on github here.
If you find a bug or have a feature request please submit a gitbub issue.

I think perhaps that is enough for now :) In my next blog article I’ll go through the getnetworkmembers script that pull all networkmembers in a ZeroTier network and create DNS records in DNSMASQ.