#!/usr/bin/env python # -*- coding: utf-8 -*- """API client to core-networks""" import requests import os import json import urlparse import configparser import sys import logging import tldextract import codecs from time import sleep def setup_logger(): # seup logging logger = logging.getLogger("certbot_dns_corenetworks") logging.basicConfig(level=logging.INFO) # silence logging from requests module logging.getLogger('requests').setLevel(logging.CRITICAL) # create console handler ch = logging.StreamHandler() ch.setLevel(logging.INFO) formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s: %(message)s', datefmt='%Y-%m-%d,%H:%M:%S') ch.setFormatter(formatter) # add handler to logger logger.addHandler(ch) logger.propagate = False return logger def set_log_level(config): try: loglevel = str(config.get('LOG', 'LEVEL')).upper() except (configparser.NoSectionError, configparser.NoOptionError) as e: logger.error("Config error: %s" % (e)) sys.exit(1) if loglevel == 'INFO': level = logging.INFO elif loglevel == 'ERROR': level = logging.ERROR elif loglevel == 'DEBUG': level = logging.DEBUG else: level = logging.INFO logger = logging.getLogger('certbot_dns_corenetworks') logger.setLevel(level) for handler in logger.handlers: handler.setLevel(level) def load_config(logger): config_path = os.path.join(os.path.expanduser("~"), ".certbot_dns_corenetworks", "config.ini") if os.path.isfile(config_path): config = configparser.ConfigParser() config.read(config_path) else: logger.error("Configfile " + config_path + " not present.") sys.exit(1) return config def write_backup(filename, name, value, logger): data = {} data["name"] = name data["data"] = value with codecs.open(filename, 'w', encoding='utf8') as f: f.write(json.dumps(data, sort_keys=True, indent=4, ensure_ascii=False)) def get_auth_token(config, logger): data = {} try: data["login"] = config.get('API', 'USER') data["password"] = config.get('API', 'PASSWORD') json_data = json.dumps(data) host = config.get('API', 'HOST') except (configparser.NoSectionError, configparser.NoOptionError) as e: logger.error("Config error: %s" % (e)) sys.exit(1) url = urlparse.urljoin(host, os.path.join("auth", "token")) try: r = requests.post(url, data=json_data) r.raise_for_status() except requests.exceptions.HTTPError as e: logger.error(e) sys.exit(1) return r def get_zone(config, logger): try: host = config.get('API', 'HOST') zone = config.get('DNS', 'ZONE') token = config.get('API', 'TOKEN') except (configparser.NoSectionError, configparser.NoOptionError) as e: logger.error("Config error: %s" % (e)) sys.exit(1) url = urlparse.urljoin(host, os.path.join("dnszones", zone)) headers = {"Authorization": "Bearer %s" % (token)} try: r = requests.get(url, headers=headers) r.raise_for_status() except requests.exceptions.HTTPError, e: logger.error(e) sys.exit(1) return r def set_record(config, logger): filename = '/tmp/CERTBOT_%s.json' % (os.environ['CERTBOT_DOMAIN']) if os.path.isfile(filename): logger.error("File '%s' exists. There seems to be an existing DNS record." % (filename)) sys.exit(1) try: host = config.get('API', 'HOST') zone = config.get('DNS', 'ZONE') token = config.get('API', 'TOKEN') except (configparser.NoSectionError, configparser.NoOptionError) as e: logger.error("Config error: %s" % (e)) sys.exit(1) url = urlparse.urljoin(host, os.path.join("dnszones", zone, "records/")) headers = {"Authorization": "Bearer %s" % (token)} try: custom_cache_extract = tldextract.TLDExtract( cache_file=os.path.join(os.path.expanduser("~"), "tld_set")) subdomain = custom_cache_extract(os.environ['CERTBOT_DOMAIN']).subdomain record_name = "_acme-challenge.%s" % (os.environ['CERTBOT_DOMAIN']) if subdomain: certbot_domain = "_acme-challenge.%s" % (subdomain) else: certbot_domain = "_acme-challenge" certbot_validation = os.environ['CERTBOT_VALIDATION'] except KeyError, e: logger.error("Environment Variable %s not set" % (e)) sys.exit(1) data = {} data["name"] = certbot_domain data["ttl"] = 120 data["type"] = "TXT" data["data"] = certbot_validation json_data = json.dumps(data) try: r = requests.post(url, headers=headers, data=json_data) r.raise_for_status() except requests.exceptions.HTTPError, e: logger.error(e) sys.exit(1) write_backup(filename, certbot_domain, certbot_validation, logger) return r def dns_commit(config, logger): try: host = config.get('API', 'HOST') zone = config.get('DNS', 'ZONE') token = config.get('API', 'TOKEN') except (configparser.NoSectionError, configparser.NoOptionError) as e: logger.error("Config error: %s" % (e)) sys.exit(1) url = urlparse.urljoin(host, os.path.join("dnszones", zone, "records", "commit")) headers = {"Authorization": "Bearer %s" % (token)} try: r = requests.post(url, headers=headers) r.raise_for_status() except requests.exceptions.HTTPError, e: logger.error(e) sys.exit(1) return r def main(): """Main logic entrypoint""" logger = setup_logger() config = load_config(logger) set_log_level(config) # get auth token logger.info("Try to get auth token") r_token = get_auth_token(config, logger) config.set('API', 'TOKEN', r_token.json()["token"]) logger.info("Authentication successful") # verify dns zone r_zone = get_zone(config, logger) try: zone_name = config.get('DNS', 'ZONE') except (configparser.NoSectionError, configparser.NoOptionError) as e: logger.error("Config error: %s" % (e)) sys.exit(1) zone_active = r_zone.json()["active"] if zone_active: logger.info("Zone '%s' verified (active)" % (zone_name)) else: logger.error("Zone '%s' verified (not active)" % (zone_name)) sys.exit(1) # add txt record logger.info("Try to set TXT record for dns challenge") z_record = set_record(config, logger) # commit changes z_commit = dns_commit(config, logger) logger.info("Set TXT record successful") logger.info("Sleep to propagate over changes to DNS") sleep(30) if __name__ == "__main__": main()