From f4e79adc8942572277975ea14104018b02fa4bdc Mon Sep 17 00:00:00 2001 From: Fredrik Eriksson Date: Wed, 2 Dec 2020 13:54:33 +0100 Subject: [PATCH] added loopia method --- data/common.yaml | 9 ++- files/acme-auth-loopia.py | 109 +++++++++++++++++++++++++++++++++++ files/acme-cleanup-loopia.py | 93 ++++++++++++++++++++++++++++++ manifests/init.pp | 9 ++- manifests/loopia.pp | 25 ++++++++ manifests/params.pp | 8 +++ 6 files changed, 247 insertions(+), 6 deletions(-) create mode 100755 files/acme-auth-loopia.py create mode 100755 files/acme-cleanup-loopia.py create mode 100644 manifests/loopia.pp create mode 100644 manifests/params.pp diff --git a/data/common.yaml b/data/common.yaml index e8f0a7d..8ed3b0a 100644 --- a/data/common.yaml +++ b/data/common.yaml @@ -1,2 +1,9 @@ --- -certbot::package: py37-certbot +certbot::params::package: py37-certbot +certbot::params::bin_dir: /usr/local/sbin +certbot::params::etc_dir: /usr/local/etc +certbot::params::certbot_bin: /usr/local/bin/certbot + +certbot::loopia::api: https://api.loopia.se/RPCSERV +#certbot::loopia::user: +#certbot::loopia::password: diff --git a/files/acme-auth-loopia.py b/files/acme-auth-loopia.py new file mode 100755 index 0000000..484c705 --- /dev/null +++ b/files/acme-auth-loopia.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3.7 + +import argparse +import configparser +import os +import sys +import time +import xmlrpc.client +import http.client + +class ProxiedTransport(xmlrpc.client.Transport): + + def set_proxy(self, host, port=None, headers=None): + self.proxy = host, port + self.proxy_headers = headers + + def make_connection(self, host): + connection = http.client.HTTPSConnection(*self.proxy) + connection.set_tunnel(host, headers=self.proxy_headers) + self._connection = host, connection + return connection + + +def main(): + + if all([x in os.environ for x in ['CERTBOT_DOMAIN', 'CERTBOT_VALIDATION']]): + domain = os.environ['CERTBOT_DOMAIN'] + subdomain = '_acme-challenge' + token = os.environ['CERTBOT_VALIDATION'] + waittime = 600 + else: + parser = argparse.ArgumentParser(description='Update acme-record for subdomain') + parser.add_argument('--domain', '-d', nargs=1, required=True, help='domain to update') + parser.add_argument('--token', '-t', nargs=1, required=True, help='token to set as txt record') + + args = parser.parse_args() + + domain = args.domain[0] + subdomain = '_acme-challenge' + token = args.token[0] + waittime = 0 + + new_record = { + 'type': 'TXT', + 'ttl': '300', + 'rdata': token, + 'record_id': 0, + 'priority': 0 + } + + config = configparser.ConfigParser() + config.read('/usr/local/etc/loopiaapi.ini') + url = config.get('default', 'url') + user = config.get('default', 'username') + pwd = config.get('default', 'password') + + proxy = os.environ.get('http_proxy') + if not proxy: + proxy = os.environ.get('HTTP_PROXY') + + if proxy: + transport = ProxiedTransport() + proto, host, port = proxy.split(':') + transport.set_proxy(host.strip('/'), int(port)) + client = xmlrpc.client.ServerProxy(uri = url, encoding='utf-8', transport=transport) + else: + client = xmlrpc.client.ServerProxy(uri = url, encoding='utf-8') + + + while domain: + res = client.getSubdomains(user, pwd, domain) + if 'UNKNOWN_ERROR' not in res: + break + subdomain, domain = domain.split('.', maxsplit=1) + subdomain = '_acme-challenge.{}'.format(subdomain) + + if 'UNKNOWN_ERROR' in res: + print("Failed to find domain in loopiadns") + return 1 + + if subdomain not in res: + res = client.addSubdomain(user, pwd, domain, subdomain) + if res != 'OK': + print('Adding subdomain failed with status: {}'.format(res)) + return 1 + res = client.getZoneRecords(user, pwd, domain, subdomain) + + for rec in res: + if rec['type'] == 'TXT': + if rec['rdata'] == token: + return 0 + new_record['record_id'] = rec['record_id'] + break + if new_record['record_id']: + res = client.updateZoneRecord(user, pwd, domain, subdomain, new_record) + else: + res = client.addZoneRecord(user, pwd, domain, subdomain, new_record) + if res != 'OK': + print('Setting zone record failed with status: {}'.format(res)) + return 1 + + time.sleep(waittime) + + return 0 + + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/files/acme-cleanup-loopia.py b/files/acme-cleanup-loopia.py new file mode 100755 index 0000000..b6406bb --- /dev/null +++ b/files/acme-cleanup-loopia.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3.7 + +import argparse +import configparser +import os +import sys +import time +import xmlrpc.client +import http.client + +class ProxiedTransport(xmlrpc.client.Transport): + + def set_proxy(self, host, port=None, headers=None): + self.proxy = host, port + self.proxy_headers = headers + + def make_connection(self, host): + connection = http.client.HTTPSConnection(*self.proxy) + connection.set_tunnel(host, headers=self.proxy_headers) + self._connection = host, connection + return connection + +def main(): + + if all([x in os.environ for x in ['CERTBOT_DOMAIN', 'CERTBOT_VALIDATION']]): + domain = os.environ['CERTBOT_DOMAIN'] + subdomain = '_acme-challenge' + token = os.environ['CERTBOT_VALIDATION'] + else: + parser = argparse.ArgumentParser(description='Update acme-record for subdomain') + parser.add_argument('--domain', '-d', nargs=1, required=True, help='domain to update') + parser.add_argument('--token', '-t', nargs=1, required=True, help='token to set as txt record') + + args = parser.parse_args() + + domain = args.domain[0] + subdomain = '_acme-challenge' + token = args.token[0] + + + config = configparser.ConfigParser() + config.read('/usr/local/etc/loopiaapi.ini') + url = config.get('default', 'url') + user = config.get('default', 'username') + pwd = config.get('default', 'password') + + proxy = os.environ.get('http_proxy') + if not proxy: + proxy = os.environ.get('HTTP_PROXY') + + if proxy: + transport = ProxiedTransport() + proto, host, port = proxy.split(':') + transport.set_proxy(host.strip('/'), int(port)) + client = xmlrpc.client.ServerProxy(uri = url, encoding='utf-8', transport=transport) + else: + client = xmlrpc.client.ServerProxy(uri = url, encoding='utf-8') + + while domain: + res = client.getSubdomains(user, pwd, domain) + if 'UNKNOWN_ERROR' not in res: + break + subdomain, domain = domain.split('.', maxsplit=1) + subdomain = '_acme-challenge.{}'.format(subdomain) + + if 'UNKNOWN_ERROR' in res: + print("Failed to find domain in loopiadns") + return 1 + + if subdomain not in res: + return 0 + + res = client.getZoneRecords(user, pwd, domain, subdomain) + + ret = 0 + for rec in res: + if rec['type'] == 'TXT': + if rec['rdata'] == token: + res = client.removeZoneRecord(user, pwd, domain, subdomain, rec['record_id']) + if res != 'OK': + print('Failed to clean up record, loopia response: {}'.format(res)) + ret = 1 + res = client.removeSubdomain(user, pwd, domain, subdomain) + if res != 'OK': + print('Failed to clean up subdomain, loopia response: {}'.format(res)) + ret = 1 + return ret + return ret + + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/manifests/init.pp b/manifests/init.pp index c13f43c..639abc8 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -1,11 +1,10 @@ # install and configure certbot -class certbot( - String[1] $package, -) { +class certbot inherits certbot::params { package { - $package: - ensure => present; + 'certbot': + ensure => present, + name => $certbot::params::package; } } diff --git a/manifests/loopia.pp b/manifests/loopia.pp new file mode 100644 index 0000000..143a01b --- /dev/null +++ b/manifests/loopia.pp @@ -0,0 +1,25 @@ +# use loopiaapi for dns challange using certbot +class certbot::loopia ( + String[1] $api_url, + String[1] $user, + Sensitive[String[1]] $password, +) inherits certbot::params { + + file { + "${certbot::params::bin_dir}/acme-auth-loopia.py": + source => "puppet:///modules/${module_name}/acme-auth-loopia.py", + mode => '0750'; + "${certbot::params::bin_dir}/acme-cleanup-loopia.py": + source => "puppet:///modules/${module_name}/acme-cleanup-loopia.py", + mode => '0750'; + "${certbot::params::etc_dir}/loopiaapi.ini": + content => inline_epp("[default]\n<% \$params.each |key,val| { %><%= \$key %> = <%= \$val %>\n<% } %>\n", { + 'params' => { + 'url' => $api_url, + 'username' => $user, + 'passowrd' => unwrap($password), + } }), + show_diff => false, + mode => '0400'; + } +} diff --git a/manifests/params.pp b/manifests/params.pp new file mode 100644 index 0000000..8385a1c --- /dev/null +++ b/manifests/params.pp @@ -0,0 +1,8 @@ +# parameters for certbot +class certbot::params( + String[1] $package, + String[1] $bin_dir, + String[1] $etc_dir, + String[1] $certbot_bin, +) { +}