Skip to content

Commit

Permalink
[feature] Added support for ZeroTier > 1.14 #312
Browse files Browse the repository at this point in the history
Closes #312
  • Loading branch information
pandafy authored Nov 4, 2024
1 parent f5115c6 commit 8afbb38
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 88 deletions.
30 changes: 25 additions & 5 deletions docs/source/backends/zerotier.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ See an example of initialization and rendering below:
"tags": [{"default": 1, "id": 1}],
"remoteTraceTarget": "7f5d90eb87",
"remoteTraceLevel": 1,
"client_options": {
"allow_managed": True,
"allowed_global": False,
"allowed_default": False,
"allowed_dns": False,
},
}
]
}
Expand Down Expand Up @@ -268,6 +274,20 @@ key name type default description
``tags`` list ``[{}]`` list of network tags dictionaries
``remoteTraceTarget`` string remote target ID for network tracing
``remoteTraceLevel`` integer level of network tracing
``client_options`` dict ``{}`` These options are only used for client configurations

=================== ======= ==========================================
key name type description
=================== ======= ==========================================
``allow_managed`` boolean allow ZeroTier to set IP addresses and
routes
``allowed_global`` boolean allow ZeroTier to set
global/public/not-private range IPs and
routes
``allowed_default`` boolean allow ZeroTier to set the default route on
the system
``allowed_dns`` boolean allow ZeroTier to set DNS servers
=================== ======= ==========================================
===================== ======= =========== =======================================================================

Client specific settings
Expand Down Expand Up @@ -314,7 +334,7 @@ key name type default description
determined
``port`` integer ``9993`` port number of the
zerotier service
``local_conf`` string path of the local
``local_conf_path`` string path of the local
zerotier configuration
(only used for advanced
configuration)
Expand Down Expand Up @@ -404,8 +424,8 @@ OpenWrt device, such as setting up trusted paths, blacklisting physical
paths, setting up physical path hints for certain nodes, and defining
trusted upstream devices, this can be achieved by creating a file named
``local.conf`` in a persistent filesystem location, such as
``/etc/openwisp/zerotier/local.conf`` and then adding the ``local_conf``
option to the ZeroTier UCI configuration.
``/etc/openwisp/zerotier/local.conf`` and then adding the
``local_conf_path`` option to the ZeroTier UCI configuration.

For example, let's create a local configuration file at
``/etc/openwisp/zerotier/local.conf`` (JSON) to blacklist a specific
Expand All @@ -421,7 +441,7 @@ physical network path **(10.0.0.0/24)** from all ZeroTier traffic.
}
}
Now add ``local_conf`` option to ``/etc/config/zerotier``:
Now add ``local_conf_path`` option to ``/etc/config/zerotier``:

.. code-block:: text
Expand All @@ -431,7 +451,7 @@ Now add ``local_conf`` option to ``/etc/config/zerotier``:
option enabled '1'
list join '9536600adf654322'
option secret '{{secret}}'
option local_conf '/etc/openwisp/zerotier/local.conf'
option local_conf_path '/etc/openwisp/zerotier/local.conf'
**More information**

Expand Down
67 changes: 63 additions & 4 deletions netjsonconfig/backends/openwrt/converters/zerotier.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,18 @@


class ZeroTier(OpenWrtConverter, BaseZeroTier):
_uci_types = ['zerotier']
_uci_types = ['zerotier', 'network']
_schema = schema['properties']['zerotier']['items']

def to_intermediate_loop(self, block, result, index=None):
vpn = self.__intermediate_vpn(block)
networks = vpn.pop('networks')
result.setdefault('zerotier', [])
result['zerotier'].append(vpn)
for network in networks:
result['zerotier'].append(self.__intermediate_network(network))
return result

def __intermediate_vpn(self, vpn):
nwid_ifnames = vpn.get('networks', [])
files = self.netjson.get('files', [])
Expand All @@ -21,18 +30,68 @@ def __intermediate_vpn(self, vpn):
'enabled': not vpn.pop('disabled', False),
}
)
del vpn['networks']
if vpn.get('local_conf'):
vpn['local_conf_path'] = vpn.get('local_conf')
elif vpn.get('local_conf_path'):
vpn['local_conf'] = vpn.get('local_conf_path')
return super().__intermediate_vpn(vpn, remove=[''])

def __intermediate_network(self, network):
# Generates configuration for ZeroTier > 1.14
# where networks are defined in individual blocks.
network.update(
{
'.name': self._get_uci_name(network.pop('ifname')),
'.type': 'network',
}
)
return self.sorted_dict(network)

def to_netjson_loop(self, block, result, index=None):
if block.get('.type') == 'zerotier':
vpn = self.__netjson_vpn(block)
result.setdefault('zerotier', [])
result['zerotier'].append(vpn)
else:
# Handles ZeroTier > 1.14 configuration where
# networks are defined in individual blocks.
network = self.__netjson_network(block)
result['zerotier'][0]['networks'].append(network)
return result

def __netjson_vpn(self, vpn):
nwids = vpn.pop('join')
vpn['name'] = vpn.pop('.name')
vpn['networks'] = [{"id": nwid, "ifname": f"owzt{nwid[-6:]}"} for nwid in nwids]
# 'disabled' defaults to False in OpenWRT
vpn['disabled'] = vpn.pop('enabled', '0') == '0'
del vpn['.type']
# Handles ZeroTier < 1.14 configuration where networks were present
# in the zerotier block.
nwids = vpn.pop('join', [])
vpn['networks'] = [
{"id": nwid, "ifname": self._get_ifname_from_id(nwid)} for nwid in nwids
]
if 'local_conf' in vpn:
vpn['local_conf_path'] = vpn.pop('local_conf')
return super().__netjson_vpn(vpn)

def __netjson_network(self, network):
for key in ['.name', '.type']:
network.pop(key)
network['ifname'] = self._get_ifname_from_id(network['id'])
# Handle boolean fields
if 'allowed_global' in network:
network['allowed_global'] = network['allowed_global'] == '1'
if 'allowed_default' in network:
network['allowed_default'] = network['allowed_default'] == '1'
if 'allowed_dns' in network:
network['allowed_dns'] = network['allowed_dns'] == '1'
if 'allow_managed' in network:
network['allow_managed'] = network['allow_managed'] == '1'
return network

def _get_ifname_from_id(self, network_id):
return f"owzt{network_id[-6:]}"

def __get_zt_ifname_files(self, vpn, files):
config_path = vpn.get('config_path', '/etc/openwisp/zerotier')
nwid_ifnames = vpn.get('networks', [])
Expand Down
40 changes: 37 additions & 3 deletions netjsonconfig/backends/openwrt/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -1222,14 +1222,13 @@
"name": {
"type": "string",
"propertyOrder": 2,
"default": "ow_zt",
"default": "global",
"minLength": 1,
"description": "Name of the zerotier network member configuration",
},
"networks": {
"type": "array",
"title": "Networks",
"minItems": 1,
"propertyOrder": 3,
"uniqueItems": True,
"additionalProperties": True,
Expand All @@ -1252,6 +1251,41 @@
"maxLength": 10,
"description": "Name of zerotier interface",
},
"allow_managed": {
"type": "boolean",
"title": "Allow Managed",
"default": True,
"format": "checkbox",
"description": (
"Allow ZeroTier to set IP Addresses"
" and Routes (local/private ranges only)",
),
},
"allow_global": {
"type": "boolean",
"title": "Allow Global",
"default": False,
"format": "checkbox",
"description": (
"Allow ZeroTier to set Global/Public/Not-Private"
" range IPs and Routes"
),
},
"allow_default": {
"type": "boolean",
"title": "Allow Default",
"format": "checkbox",
"description": (
"Allow ZeroTier to set the Default Route on the"
" system"
),
},
"allow_dns": {
"type": "boolean",
"title": "Allow DNS",
"format": "checkbox",
"description": "Allow ZeroTier to set DNS servers",
},
},
},
},
Expand Down Expand Up @@ -1295,7 +1329,7 @@
"propertyOrder": 7,
"description": "Port number of the zerotier service",
},
"local_conf": {
"local_conf_path": {
"type": "string",
"propertyOrder": 8,
"description": (
Expand Down
1 change: 1 addition & 0 deletions netjsonconfig/backends/zerotier/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def to_intermediate_loop(self, block, result, index=None):
return result

def __intermediate_vpn(self, config, remove=None):
config.pop('client_options', None)
return self.sorted_dict(config)

def to_netjson_loop(self, block, result, index=None):
Expand Down
39 changes: 39 additions & 0 deletions netjsonconfig/backends/zerotier/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,45 @@
"description": "Time when the network was created",
},
# Configurable properties
"client_options": {
"type": "object",
"title": "Client Options",
"propertyOrder": 14,
"properties": {
"allow_managed": {
"type": "boolean",
"title": "Allow Managed",
"default": True,
"format": "checkbox",
"description": (
"Allow ZeroTier to set IP Addresses and Routes (local/private ranges only)",
),
},
"allow_global": {
"type": "boolean",
"title": "Allow Global",
"default": False,
"format": "checkbox",
"description": (
"Allow ZeroTier to set Global/Public/Not-Private range IPs and Routes"
),
},
"allow_default": {
"type": "boolean",
"title": "Allow Default",
"format": "checkbox",
"description": (
"Allow ZeroTier to set the Default Route on the system"
),
},
"allow_dns": {
"type": "boolean",
"title": "Allow DNS",
"format": "checkbox",
"description": "Allow ZeroTier to set DNS servers",
},
},
},
"capabilities": {
"type": "array",
"items": {"type": "object"},
Expand Down
4 changes: 4 additions & 0 deletions netjsonconfig/backends/zerotier/zerotier.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ def auto_client(
identity_secret='{{secret}}',
config_path='/etc/openwisp/zerotier',
disabled=False,
client_options=None,
):
networks = networks or []
client_options = client_options or {}
for network in networks:
network.update(client_options)
return {
'name': name,
'networks': networks,
Expand Down
Loading

0 comments on commit 8afbb38

Please sign in to comment.