Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1068,14 +1068,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 @@ -1098,6 +1097,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 @@ -1141,7 +1175,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