diff --git a/files/new_wrapper/create_challenge.py b/files/new_wrapper/create_challenge.py new file mode 100644 index 0000000000000000000000000000000000000000..b205b09b077db70076929baa9b25e2be9c0fa859 --- /dev/null +++ b/files/new_wrapper/create_challenge.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +""" Add Acme challenges to Infoblox """ +import os +import time +import configparser +import requests + + +def create_acme(iblox_domain, acme_token, iblox_user, iblox_pw): + """ upload txt record """ + post_req = requests.post( + 'https://infoblox.geant.org/wapi/v2.6.1/record:txt', + auth=(iblox_user, iblox_pw), + data={ + 'name': '_acme-challenge.{}'.format(iblox_domain), + 'text': acme_token, + 'ttl': '60', + "view": "External" + } + ) + return post_req.status_code + + +# Here we Go. +if __name__ == "__main__": + + CONFIG = configparser.RawConfigParser() + CONFIG.read_file(open('/root/.geant_acme.ini')) + IBLOX_PASS = CONFIG.get('geant_acme', 'iblox_pass') + IBLOX_USER = CONFIG.get('geant_acme', 'iblox_user') + + ARGS = os.sys.argv + _ = ARGS.pop(0) + + for host in ARGS: + if ARGS.index(host) % 2 == 0: + token = ARGS.index(host)+1 + http_code = create_acme(host, ARGS[token], IBLOX_USER, IBLOX_PASS) + if http_code != 201: + print('could not create {} for {}'.format(ARGS[token], host)) + os.sys.exit(1) + + time.sleep(10) diff --git a/files/new_wrapper/geant_acme.py b/files/new_wrapper/geant_acme.py new file mode 100644 index 0000000000000000000000000000000000000000..e6031de18feefa6f8883ace0d941f17dbff8f944 --- /dev/null +++ b/files/new_wrapper/geant_acme.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""Geant Acme + +Usage: + geant_acme.py --domain <DOMAIN>... (--client <CLIENT>... | --wildcard) [--verbose] + geant_acme.py (-h | --help) + +Options: + -h --help Show this screen. + -c CLIENT --client=CLIENT Client + -d DOMAIN --domain=DOMAIN Domain + -w --wildcard Use wildcard + -v --verbose Print out messages +""" +import os +import subprocess as sp +import configparser +from docopt import docopt +import requests + +BASE_URL = 'https://infoblox.geant.org/wapi/v2.6.1' + + +def os_exit(verbose=None): + """ exit """ + if verbose: + print('+' + 72*'-' + '+') + os.sys.exit() + + +def get_reference(iblox_domain, iblox_user, iblox_pw, verbose=None): + """ grab reference for txt object """ + ref_obj = requests.get( + '{}/record:txt?name=_acme-challenge.{}&_return_as_object=1'.format( + BASE_URL, iblox_domain), + auth=(iblox_user, iblox_pw) + ) + + if ref_obj.status_code != 200: + return 'error' + + try: + ref = ref_obj.json()['result'][0]['_ref'] + except IndexError: + ref = None + + if verbose: + if ref: + print('got reference object: {}'.format(ref)) + else: + print('{} empty reference object: no challenge to delete'.format( + iblox_domain)) + print('+' + 72*'-' + '+') + + return ref + + +def delete_challenge(object_reference, iblox_user, iblox_pw, verbose=None): + """ delete txt record """ + del_req = requests.delete( + '{}/{}'.format(BASE_URL, object_reference), + auth=(iblox_user, iblox_pw) + ) + if verbose: + print('delete challenge - http code (200 expected): {}'.format(del_req.status_code)) + print('+' + 72*'-' + '+') + + return del_req.status_code + + +def create_challenge(iblox_domain, acme_token, iblox_user, iblox_pw, verbose=None): + """ upload txt record """ + if verbose: + print('+' + 72*'-' + '+') + print('creating challenge _acme-challenge.{}'.format(iblox_domain)) + + post_req = requests.post( + '{}/record:txt'.format(BASE_URL), + auth=(iblox_user, iblox_pw), + data={ + 'name': '_acme-challenge.{}'.format(iblox_domain), + 'text': acme_token, + 'ttl': '30', + "view": "External" + } + ) + if verbose: + print('create challenge - http code (201 expected): {} {}'.format( + post_req.status_code, post_req.reason)) + print('+' + 72*'-' + '+') + + return post_req.status_code + + +def run_certbot(cbot_domain, wild_card=None, verbose=None): + """ get certificate from letsencrypt """ + if wild_card: + domain_list = '*.{}'.format(cbot_domain) + else: + domain_list = ' -d '.join(list(reversed(cbot_domain))) + + cbot_cmd = '/usr/local/bin/certbot certonly -c /etc/letsencrypt/cli.ini' \ + + ' --manual-auth-hook=/root/bin/create_challenge.py' \ + + ' --cert-name {} -d {}'.format(cbot_domain[0], domain_list) + + if verbose: + print('+' + 72*'-' + '+') + print('running: {}'.format(cbot_cmd)) + + cbot_child = sp.Popen(cbot_cmd, stdout=sp.PIPE, stderr=sp.PIPE, shell=True) + cbot_out, cbot_err = cbot_child.communicate() + if cbot_child.returncode != 0: + msg = "error running certbot: {}".format(cbot_err.decode("utf-8")) + else: + decoded_msg = cbot_out.decode("utf-8") + msg = decoded_msg[:decoded_msg.rfind('\n')] + + if verbose: + print('\n{}'.format(msg)) + + return msg + + +# Here we Go. +if __name__ == "__main__": + + ARGS = docopt(__doc__) + DOMAIN = ARGS['--domain'] + CLIENTS = ARGS['--client'] + WILDCARD = ARGS['--wildcard'] + VERBOSE = ARGS['--verbose'] + + CONFIG = configparser.RawConfigParser() + CONFIG.read_file(open('/root/.geant_acme.ini')) + IBLOX_PASS = CONFIG.get('geant_acme', 'iblox_pass') + IBLOX_USER = CONFIG.get('geant_acme', 'iblox_user') + + if WILDCARD: + REAL_DOMAIN = 'domain *.{}'.format(DOMAIN[0]) + else: + REAL_DOMAIN = 'host {}'.format(DOMAIN[0]) + + if VERBOSE: + print('+' + 72*'-' + '+') + + # Maybe there is an Acme challenge left over. We try to delete it first. + for domain_item in DOMAIN: + REF_OBJ = get_reference(domain_item, IBLOX_USER, IBLOX_PASS, VERBOSE) + if REF_OBJ == 'error': + print('error retrieving reference object') + os_exit(VERBOSE) + if REF_OBJ: + DEL_STATUS = delete_challenge( + REF_OBJ, IBLOX_USER, IBLOX_PASS, VERBOSE) + if DEL_STATUS != 200: + print('{}: {} error deleting challenge on Infoblox'.format( + domain_item, DEL_STATUS)) + os_exit(VERBOSE) + + # run certbot + run_certbot(DOMAIN, WILDCARD, VERBOSE) + + if VERBOSE: + print('+' + 72*'-' + '+') + + # remove the Acme challenges from Infoblox + for domain_item in DOMAIN: + REF_OBJ = get_reference(domain_item, IBLOX_USER, IBLOX_PASS, VERBOSE) + if REF_OBJ == 'error': + print('error retrieving reference object') + os_exit(VERBOSE) + if REF_OBJ: + DEL_STATUS = delete_challenge( + REF_OBJ, IBLOX_USER, IBLOX_PASS, VERBOSE) + if DEL_STATUS != 200: + print('{}: {} error deleting challenge on Infoblox'.format( + domain_item, DEL_STATUS)) + os_exit(VERBOSE) + + # if we are here, everything went fine and we can upload the certificates + if WILDCARD: + UPLOADER = '/root/bin/upload_wildcards.py' + if VERBOSE: + UPLOADER += ' -v' + os.system(UPLOADER) + else: + for client in CLIENTS: + UPLOADER = '/root/bin/geant_acme_uploader.py -d {} -c {}'.format( + DOMAIN[0], client) + if VERBOSE: + UPLOADER += ' -v' + os.system(UPLOADER) + + os_exit(VERBOSE)