Last active
September 9, 2022 03:56
-
-
Save gamingrobot/d56bfde110a093b1324d91de0a38e6b5 to your computer and use it in GitHub Desktop.
very basic saltstack module and state for doing things with zerotier-cli and zerotier central api
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
''' | |
Zerotier | |
''' | |
# Import python libs | |
from __future__ import absolute_import, generators, print_function, with_statement, unicode_literals | |
import logging | |
import json | |
# Import 3rd-party libs | |
# pylint: disable=import-error,no-name-in-module,redefined-builtin | |
from salt.ext.six.moves.urllib.parse import urljoin as _urljoin | |
import salt.ext.six.moves.http_client | |
# pylint: enable=import-error,no-name-in-module | |
# Import salt libs | |
import salt.utils | |
import salt.utils.http | |
log = logging.getLogger(__name__) | |
def __virtual__(): | |
''' | |
Only load the module if zerotier is installed | |
''' | |
if salt.utils.which("zerotier-cli"): | |
return 'zerotier' | |
return (False, 'The zerotier execution module cannot be loaded: zerotier is not installed.') | |
def version(): | |
''' | |
Return version (``zerotier-cli -v``) | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.version | |
''' | |
cmd = 'zerotier-cli -v' | |
ret = __salt__['cmd.run'](cmd) | |
return ret | |
def node_id(): | |
''' | |
Return node id | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.node_id | |
''' | |
cmd = 'zerotier-cli -j info' | |
out = __salt__['cmd.run'](cmd) | |
info = json.loads(salt.utils.to_str(out)) | |
node_address = info["address"] | |
return node_address | |
def info(network_id): | |
''' | |
Info (``zerotier-cli info``) | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.info | |
''' | |
cmd = 'zerotier-cli -j info' | |
out = __salt__['cmd.run'](cmd) | |
ret = json.loads(salt.utils.to_str(out)) | |
return ret | |
def network_info(network_id): | |
''' | |
Network Info | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.network_info <network_id> | |
''' | |
cmd = 'zerotier-cli -j listnetworks' | |
out = __salt__['cmd.run'](cmd) | |
decoded = json.loads(salt.utils.to_str(out)) | |
ret = next((n for n in decoded if n['id'] == network_id), None) | |
return ret | |
def network_join(network_id): | |
''' | |
Join network (``zerotier-cli join <network_id>``) | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.newtork_join <network_id> | |
''' | |
cmd = 'zerotier-cli -j join {0}'.format(network_id) | |
out = __salt__['cmd.run'](cmd) | |
ret = json.loads(salt.utils.to_str(out)) | |
return ret | |
def network_leave(network_id): | |
''' | |
Leave network (``zerotier-cli leave <network_id>``) | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.network_leave <network_id> | |
''' | |
cmd = 'zerotier-cli -j leave {0}'.format(network_id) | |
out = __salt__['cmd.run'](cmd) | |
ret = json.loads(salt.utils.to_str(out)) | |
return ret | |
def central_get_member(network_id, api_key=None): | |
''' | |
Get information about network memeber | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.central_get_member <network_id> <api_key> | |
''' | |
nodeId = node_id() | |
base_url = 'https://my.zerotier.com' | |
url = _urljoin(base_url, '/api/network/{networkId}/member/{nodeId}'.format(networkId=network_id, nodeId=nodeId)) | |
headers = {'Authorization': "Bearer {0}".format(api_key), 'Content-type': 'application/json'} | |
member_info_raw = salt.utils.http.query( | |
url, | |
'GET', | |
text=True, | |
status=True, | |
header_dict=headers, | |
opts=__opts__ | |
) | |
if member_info_raw['status'] != 200: | |
raise Exception(member_info_raw['error']) | |
#Handle the fact that the result from this GET may be empty | |
if member_info_raw['text']: | |
member_info_raw['dict'] = json.loads(salt.utils.to_str(member_info_raw['text'])) | |
return member_info_raw.get('dict', {}) | |
def central_update_member(network_id, api_key=None, **config_args): | |
''' | |
Update information about network memeber | |
CLI Example: | |
.. code-block:: bash | |
salt '*' zerotier.central_update_member <network_id> <api_key> <config_args> | |
config_args: | |
hidden:bool - Hidden in UI | |
name:string - Short name describing member | |
description:string - Long form description | |
offlineNotifyDelay:number - Notify of offline after this many milliseconds | |
authorized:bool - True if authorized (only matters on private networks) | |
capabilities:array - Array of IDs of capabilities assigned to this member | |
tags:array - Array of tuples of tag ID, tag value | |
ipAssignments:array - Array of IP assignments published to member | |
noAutoAssignIps:bool - If true do not auto-assign IPv4 or IPv6 addresses, overriding | |
''' | |
nodeId = node_id() | |
# TODO: check we have network_id and node_id | |
base_url = 'https://my.zerotier.com' | |
url = _urljoin(base_url, '/api/network/{networkId}/member/{nodeId}'.format(networkId=network_id, nodeId=nodeId)) | |
headers = {'Authorization': "Bearer {0}".format(api_key), 'Content-type': 'application/json'} | |
member_info_raw = salt.utils.http.query( | |
url, | |
'POST', | |
decode=True, | |
status=True, | |
header_dict=headers, | |
opts=__opts__ | |
) | |
if member_info_raw['status'] != 200: | |
raise Exception(member_info_raw['error']) | |
member_info = member_info_raw.get('dict', {}) | |
#set properties | |
root_keys = ['hidden', 'name', 'description', 'offlineNotifyDelay'] | |
config_keys = ['authorized', 'capabilities', 'tags', 'ipAssignments', 'noAutoAssignIps'] | |
for key in root_keys: | |
if key in config_args and key in member_info: | |
log.debug("Setting root key {0} to {1}".format(key, config_args[key])) | |
member_info[key] = config_args[key] | |
for key in config_keys: | |
if key in config_args and key in member_info.get('config', {}): | |
log.debug("Setting config key {0} to {1}".format(key, config_args[key])) | |
member_info['config'][key] = config_args[key] | |
update_result = salt.utils.http.query( | |
url, | |
'POST', | |
params={}, | |
data=json.dumps(member_info), | |
decode=True, | |
status=True, | |
header_dict=headers, | |
opts=__opts__ | |
) | |
if update_result['status'] != 200: | |
raise Exception(update_result['error']) | |
return update_result.get('dict', {}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
''' | |
Zerotier state | |
For managing zero tier things | |
Config Options: | |
hidden:bool - Hidden in UI | |
name:string - Short name describing member | |
description:string - Long form description | |
offlineNotifyDelay:number - Notify of offline after this many milliseconds | |
authorized:bool - True if authorized (only matters on private networks) | |
capabilities:array - Array of IDs of capabilities assigned to this member | |
tags:array - Array of tuples of tag ID, tag value | |
ipAssignments:array - Array of IP assignments published to member | |
noAutoAssignIps:bool - If true do not auto-assign IPv4 or IPv6 addresses, overriding | |
.. code-block:: yaml | |
zerotier-membership: | |
zerotier.joined: | |
- network_id: abc | |
zerotier.central_member: | |
- network_id: abc | |
- api_key: xyz | |
- config: | |
name: testing | |
description: "hello world" | |
authorized: True | |
''' | |
from __future__ import absolute_import, generators, print_function, with_statement, unicode_literals | |
import logging | |
# Import python libs | |
import os.path | |
# Import Salt libs | |
import salt.utils | |
log = logging.getLogger(__name__) | |
def joined(name, network_id): | |
ret = {'name': name, | |
'changes': {}, | |
'result': None, | |
'comment': ''} | |
if network_id == None: | |
raise SaltInvocationError('network_id is required') | |
network_state = __salt__['zerotier.network_info'](network_id) | |
if network_state != None: | |
ret['result'] = True | |
ret['comment'] = 'Already joined network' | |
return ret | |
elif __opts__['test']: | |
ret['comment'] = 'Will join network' | |
ret['result'] = None | |
return ret | |
joined_state = __salt__['zerotier.network_join'](network_id) | |
if joined_state != None: | |
ret['changes'] = { | |
'old': network_state, | |
'new': joined_state | |
} | |
ret['result'] = True | |
ret['comment'] = 'Successfully joined network' | |
else: | |
ret['result'] = False | |
ret['comment'] = 'Failed to join network' | |
return ret | |
def central_member(name, network_id, api_key, config): | |
ret = {'name': name, | |
'changes': {}, | |
'result': None, | |
'comment': ''} | |
if network_id == None: | |
raise SaltInvocationError('network_id is required') | |
if api_key == None: | |
raise SaltInvocationError('api_key is required') | |
if __opts__['test']: | |
ret['comment'] = 'Will update central member' | |
ret['changes'].update({'member': {'old': '', 'new': config}}) | |
ret['result'] = None | |
return ret | |
original_member_info = __salt__['zerotier.central_get_member'](network_id, api_key) | |
#TODO: check values and see if we even need to update | |
member_info = __salt__['zerotier.central_update_member'](network_id, api_key, **config) | |
#Do a diff, prob a better way to do this | |
diff = {'old': {}, 'new': {}} | |
root_keys = ['hidden', 'name', 'description', 'offlineNotifyDelay'] | |
config_keys = ['authorized', 'capabilities', 'tags', 'ipAssignments', 'noAutoAssignIps'] | |
for key in root_keys: | |
if key in original_member_info: | |
old = original_member_info[key] | |
if key in member_info: | |
new = member_info[key] | |
if old != new: | |
diff['old'][key] = old | |
diff['new'][key] = new | |
for key in config_keys: | |
if key in original_member_info.get('config', {}): | |
old = original_member_info['config'][key] | |
if key in member_info.get('config', {}): | |
new = member_info['config'][key] | |
if old != new: | |
diff['old'][key] = old | |
diff['new'][key] = new | |
if not diff['old'] and not diff['new']: | |
ret['result'] = True | |
ret['comment'] = 'Central Member is up to date' | |
else: | |
#just some visual cleanup if empty dict | |
if not diff['old']: | |
diff['old'] = None | |
if not diff['new']: | |
diff['new'] = None | |
ret['changes'] = diff | |
ret['result'] = True | |
ret['comment'] = 'Updated central member' | |
return ret |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
zerotier-pkgrepo: | |
pkgrepo.managed: | |
- humanname: ZeroTier Ubuntu Repo | |
- name: {{ 'deb https://download.zerotier.com/debian/' + salt['grains.get']('oscodename') + ' ' + salt['grains.get']('oscodename') + ' main'}} | |
- file: /etc/apt/sources.list.d/zerotier.list | |
- key_url: https://pgp.mit.edu/pks/lookup?op=get&search=0x1657198823E52A61 | |
- clean_file: True | |
- require_in: | |
- pkg: zerotier | |
zerotier: | |
pkg.installed: | |
- name: zerotier-one | |
service.running: | |
- name: zerotier-one | |
- enable: True | |
- require: | |
- pkg: zerotier | |
zerotier-joined: | |
zerotier.joined: | |
- network_id: {{ salt['pillar.get']('zerotier:network_id') }} | |
zerotier-member: | |
zerotier.central_member: | |
- network_id: {{ salt['pillar.get']('zerotier:network_id') }} | |
- api_key: {{ salt['pillar.get']('zerotier:api_key') }} | |
- config: | |
name: {{ salt['grains.get']('host') }} | |
authorized: True | |
- onchanges: | |
- zerotier: zerotier-joined |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
zerotier: | |
network_id: abcdefg | |
api_key: xyzabc |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment