From fa6c0aadfede4de0ee551c96913fd9f1b8c8c135 Mon Sep 17 00:00:00 2001 From: Frank Brehm Date: Thu, 3 Aug 2017 18:04:21 +0200 Subject: [PATCH] Implemented getting zones from API --- pp_lib/config_named_app.py | 144 +++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 4 deletions(-) diff --git a/pp_lib/config_named_app.py b/pp_lib/config_named_app.py index cdaf8be..8cd3209 100644 --- a/pp_lib/config_named_app.py +++ b/pp_lib/config_named_app.py @@ -23,13 +23,16 @@ import grp # Third party modules import six +import requests + +from six.moves.urllib.parse import urlunsplit # Own modules -from .common import pp, to_bool +from .common import pp, to_bool, to_bytes from .cfg_app import PpCfgAppError, PpConfigApplication -__version__ = '0.2.1' +__version__ = '0.3.0' LOG = logging.getLogger(__name__) @@ -46,7 +49,8 @@ class PpConfigNamedApp(PpConfigApplication): default_pdns_api_host = 'systemshare.pixelpark.com' default_pdns_api_port = 8081 - default_pdns_api_root_path = '/api/v1/servers/localhost' + default_pdns_api_root_path = '/api/v1' + default_pdns_api_server_id = 'localhost' default_named_conf = '/etc/named.conf' default_named_zones_cfg_dir = '/etc/named' default_named_basedir = '/var/named' @@ -63,12 +67,16 @@ class PpConfigNamedApp(PpConfigApplication): re_split_addresses = re.compile(r'[,;\s]+') re_integer = re.compile(r'^\s*(\d+)\s*$') + re_ipv4_zone = re.compile(r'^((?:\d+\.)+)in-addr\.arpa\.$') + re_ipv6_zone = re.compile(r'^((?:[\da-f]\.)+)ip6\.arpa\.$') + # ------------------------------------------------------------------------- def __init__(self, appname=None, version=__version__): self.pdns_api_host = self.default_pdns_api_host self.pdns_api_port = self.default_pdns_api_port self.pdns_api_root_path = self.default_pdns_api_root_path + self.pdns_api_server_id = self.default_pdns_api_server_id self.pdns_api_key = None self.is_internal = False @@ -96,6 +104,8 @@ class PpConfigNamedApp(PpConfigApplication): self.named_show_bind_version = False self.named_version2show = self.default_named_version2show + self.zones = {} + description = textwrap.dedent('''\ Generation of configuration of named (the BIND 9 name daemon). ''').strip() @@ -199,6 +209,9 @@ class PpConfigNamedApp(PpConfigApplication): section, section_name, 'root_path', 'pdns_api_root_path', True, 'root path of the PowerDNS') + if 'server_id' in section and section['server_id'].strip(): + self.pdns_api_server_id = section['server_id'].strip().lower() + if 'key' in section: key = section['key'].strip() self.pdns_api_key = key @@ -353,7 +366,130 @@ class PpConfigNamedApp(PpConfigApplication): if os.geteuid(): LOG.error("You must be root to execute this script.") self.exit(1) - LOG.info("Jetzt geht's looos") + + self.get_api_zones() + + # ------------------------------------------------------------------------- + def get_api_zones(self): + + LOG.info("Trying to get all zones from PDNS API ...") + + headers = {} + if self.pdns_api_key: + headers['X-API-Key'] = self.pdns_api_key + + path = os.path.join( + self.pdns_api_root_path, 'servers', self.pdns_api_server_id, 'zones') + server = self.pdns_api_host + if self.pdns_api_port != 80: + server = '{}:{}'.format(server, self.pdns_api_port) + url = urlunsplit(('http', server, path, None, None)) + LOG.debug("URL to send API call: {!r}.".format(url)) + if self.verbose > 1: + LOG.debug("Headers:\n%s", pp(headers)) + session = requests.Session() + response = session.request( + 'GET', url, headers=headers, timeout=10) + if self.verbose > 1: + LOG.debug("Response status code: {}".format(response.status_code)) + if not response.ok: + try: + err = response.json() + code = err['httpStatus'] + msg = err['messages'] + LOG.error("Got an error from API ({}) with status {}: {}".format( + url, code, msg)) + self.exit(6) + except ValueError: + msg = 'Failed to parse the response from {!r}: {}'.format( + url, response.text) + LOG.error(msg) + self.exit(6) + + json_response = response.json() + if self.verbose > 3: + LOG.debug("Got a response:\n{}".format(pp(json_response))) + + for entry in json_response: + +# { 'account': '', +# 'dnssec': False, +# 'id': '56.66.217.in-addr.arpa.', +# 'kind': 'Master', +# 'last_check': 0, +# 'masters': [], +# 'name': '56.66.217.in-addr.arpa.', +# 'notified_serial': 2017080202, +# 'serial': 2017080202, +# 'url': 'api/v1/servers/localhost/zones/56.66.217.in-addr.arpa.'}, + + zone_name = entry['name'] + zone = { + 'account': entry['account'], + 'kind': entry['kind'], + 'serial': entry['serial'], + } + + if entry['dnssec']: + self.named_dnssec = True + if self.verbose > 1: + LOG.debug("Found zone {!r}.".format(zone_name)) + + uni_name = None + match = self.re_ipv4_zone.search(zone_name) + if match: + prefix = self._get_ipv4_prefix(match.group(1)) + if prefix: + uni_name = 'rev.' + prefix + + match = self.re_ipv6_zone.search(zone_name) + if match: + prefix = self._get_ipv6_prefix(match.group(1)) + if prefix: + uni_name = 'rev.' + prefix + + if not uni_name: + uni_name = zone_name.encode('utf-8').decode('idna') + + zone['canoniical_name'] = uni_name + + self.zones[zone_name] = zone + + if self.verbose > 2: + LOG.debug("Got zones:\n{}".format(pp(self.zones))) + + # ------------------------------------------------------------------------- + def _get_ipv4_prefix(self, match): + + tuples = [] + for t in match.split('.'): + if t: + tuples.insert(0, t) + LOG.debug("Got IPv4 tuples: {}".format(pp(tuples))) + return '.'.join(tuples) + + # ------------------------------------------------------------------------- + def _get_ipv6_prefix(self, match): + + tuples = [] + for t in match.split('.'): + if t: + tuples.insert(0, t) + LOG.debug("Got IPv6 tuples: {}".format(pp(tuples))) + + tokens = [] + while len(tuples): + token = ''.join(tuples[0:4]).ljust(4, '0') + if token.startswith('000'): + token = token[3:] + elif token.startswith('00'): + token = token[2:] + elif token.startswith('0'): + token = token[1:] + tokens.append(token) + del tuples[0:4] + + return ':'.join(tokens) # ============================================================================= -- 2.39.5