Skip to content

Commit

Permalink
[feature] Added support for ZeroTier VPN backend #604 #606 #801
Browse files Browse the repository at this point in the history
Closes #604
Closes #606
Closes #801
  • Loading branch information
Aryamanz29 authored and nemesifier committed Sep 26, 2023
1 parent 3512977 commit 2097bde
Show file tree
Hide file tree
Showing 11 changed files with 740 additions and 61 deletions.
28 changes: 20 additions & 8 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1347,15 +1347,20 @@ from the `official website <https://www.zerotier.com/download/>`_.
be applied to. For this example, we will leave it to ``OpenWRT``.
4. Select the correct VPN server from the dropdown for the **VPN** field. Here
it is ``ZeroTier``.
5. Make sure to check the **Automatic tunnel provisioning** option.
This will enable OpenWISP to automatically provision an IP address
for each ZeroTier VPN client.
5. Ensure that the **Automatic tunnel provisioning** option is checked.
This will enable OpenWISP to automatically provision an IP address and
ZeroTier identity secrets (used for assigning member IDs) for each ZeroTier VPN client.
6. After clicking on **Save and continue editing** button, you will see details
of *ZeroTier* VPN server in **System Defined Variables**. The template
configuration will be automatically generated which you can tweak
accordingly. We will use the automatically generated VPN client configuration
for this example.

**Note:** OpenWISP uses `zerotier-idtool
<https://github.com/zerotier/ZeroTierOne/blob/dev/doc/zerotier-idtool.1.md>`_
to manage **ZeroTier identity secrets**. Please make sure that you have
`ZeroTier package installed <https://www.zerotier.com/download/>`_ on the server.

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/template.png
:alt: ZeroTier VPN client template example

Expand All @@ -1367,12 +1372,19 @@ OpenWISP. Register or create a device before proceeding.

1. Open the **Configuration** tab of the concerned device.
2. Select the *ZeroTier Client* template.
3. Upon clicking on **Save and continue editing** button, you will see some
entries in **System Defined Variables**. It will contain internal IP address
for the ZeroTier client on the device along with details of VPN server.
3. Upon clicking the **Save and Continue Editing** button, you will see entries
in the **System Defined Variables** section. These entries will include **zerotier_member_id**, **identity_secret**,
and the internal **IP address** of the ZeroTier client (network member) on the device, along with details of the VPN server.

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-1.png
:alt: ZeroTier VPN device configuration example 1

4. Once the configuration is successfully applied to the device, you will notice a new ZeroTier interface
that is up and running. This interface will have the name ``owzt89f498`` (where ``owzt`` is followed
by the last six hexadecimal characters of the ZeroTier **network ID**).

.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration.png
:alt: ZeroTier VPN device configuration example
.. image:: https://raw.githubusercontent.com/openwisp/openwisp-controller/docs/docs/zerotier-tutorial/device-configuration-2.png
:alt: ZeroTier VPN device configuration example 2

**Voila!** You have successfully configured OpenWISP
to manage ZeroTier tunnels for your devices.
Expand Down
118 changes: 95 additions & 23 deletions openwisp_controller/config/api/zerotier_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,36 +51,24 @@ def _get_repsonse(self, repsonse):
return repsonse

def _add_routes_and_ip_assignment(self, config):
"""
Adds ZeroTier network routes
and IP assignmentpools through OpenWISP subnet
Params:
config (dict): ZeroTier network config dict
"""
config['routes'] = [{'target': str(self.subnet), 'via': ''}]
ip_end = str(self.subnet.broadcast_address)
ip_start = str(next(self.subnet.hosts()))
config['ipAssignmentPools'] = [{"ipRangeEnd": ip_end, "ipRangeStart": ip_start}]
return config

def join_network(self, network_id):
url = f'{self.url}/network/{network_id}'
response = requests.post(
url, json={}, headers=self.headers, timeout=REQUEST_TIMEOUT
)
return response

def leave_network(self, network_id):
url = f'{self.url}/network/{network_id}'
response = requests.delete(url, headers=self.headers, timeout=REQUEST_TIMEOUT)
return response

def update_network_member(self, node_id, network_id, member_ip):
url = f'{self.url}/controller/network/{network_id}/member/{node_id}'
# Authorize and assign ip to the network member
response = requests.post(
url,
json={'authorized': True, 'ipAssignments': [str(member_ip)]},
headers=self.headers,
timeout=5,
)
return response

def get_node_status(self):
"""
Fetches the status of the running ZeroTier controller
This method is used for host validation during VPN creation
"""
url = f'{self.url}/status'
try:
response = requests.get(url, headers=self.headers, timeout=REQUEST_TIMEOUT)
Expand All @@ -94,7 +82,41 @@ def get_node_status(self):
}
)

def join_network(self, network_id):
"""
Adds ZeroTier Controller to the specified network
Params:
network_id (str): ID of the network to join
"""
url = f'{self.url}/network/{network_id}'
response = requests.post(
url, json={}, headers=self.headers, timeout=REQUEST_TIMEOUT
)
return response

def leave_network(self, network_id):
"""
Removes ZeroTier Controller from the specified network
Params:
network_id (str): ID of the network to leave
"""
url = f'{self.url}/network/{network_id}'
response = requests.delete(url, headers=self.headers, timeout=REQUEST_TIMEOUT)
return response

def create_network(self, node_id, config):
"""
Creates a new network in the ZeroTier Controller
Params:
node_id (str): ID of the controller node
config (dict): Configuration of the new network
Returns:
network_config(dict): Filtered response from the ZeroTier Controller API
"""
url = f"{self.url}{self._get_endpoint('network', 'create', node_id)}"
config = self._add_routes_and_ip_assignment(config)
try:
Expand All @@ -110,6 +132,13 @@ def create_network(self, node_id, config):
)

def update_network(self, config, network_id):
"""
Update configuration of an existing ZeroTier Controller network
Params:
config (dict): New configuration data for the network
network_id (str): ID of the network to update
"""
url = f"{self.url}{self._get_endpoint('network', 'update', network_id)}"
config = self._add_routes_and_ip_assignment(config)
response = requests.post(
Expand All @@ -118,6 +147,49 @@ def update_network(self, config, network_id):
return response, self._get_repsonse(response.json())

def delete_network(self, network_id):
"""
Deletes ZeroTier Controller network
Params:
network_id (str): ID of the ZeroTier network to be deleted
"""
url = f"{self.url}{self._get_endpoint('network', 'delete', network_id)}"
response = requests.delete(url, headers=self.headers, timeout=REQUEST_TIMEOUT)
return response

def update_network_member(self, node_id, network_id, member_ip):
"""
Update ZeroTier Network Member Configuration
This method is currently used to authorize, enable the bridge
and assign an IP address to a network member
Params:
node_id (str): Node ID of the network member
network_id (str): Network ID to which the member belongs
member_ip (str): IP address to be assigned to the network member
"""
url = f'{self.url}/controller/network/{network_id}/member/{node_id}'
response = requests.post(
url,
json={
'authorized': True,
'activeBridge': True,
'ipAssignments': [str(member_ip)],
},
headers=self.headers,
timeout=5,
)
return response

def remove_network_member(self, node_id, network_id):
"""
Remove a member from ZeroTier network
Params:
node_id (str): ID of the network member
network_id (str): ID of the ZeroTier network
"""
url = f'{self.url}/controller/network/{network_id}/member/{node_id}'
response = requests.delete(url, headers=self.headers, timeout=5)
return response
9 changes: 8 additions & 1 deletion openwisp_controller/config/base/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ def manage_vpn_clients(cls, action, instance, pk_set, **kwargs):
).exists():
continue
client = vpn_client_model(
config=instance, vpn=template.vpn, auto_cert=template.auto_cert
config=instance,
vpn=template.vpn,
auto_cert=template.auto_cert,
)
client.full_clean()
client.save()
Expand Down Expand Up @@ -601,6 +603,11 @@ def get_vpn_context(self):
context[
vpn_context_keys['vni']
] = f'{vpnclient.vni or vpnclient.vpn._vxlan_vni}'
if vpnclient.secret:
context[
vpn_context_keys['zerotier_member_id']
] = vpnclient.zerotier_member_id
context[vpn_context_keys['secret']] = vpnclient.secret
return context

def get_context(self, system=False):
Expand Down
Loading

0 comments on commit 2097bde

Please sign in to comment.