commit 2a217b5a82ea165f2d5c1bc2b1d0ccef036f12f8 Author: Piotr Dobrowolski Date: Sun Mar 12 19:55:05 2017 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88f2278 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.py[co] +config.cfg +ovh.conf +hook-config diff --git a/README.md b/README.md new file mode 100644 index 0000000..c28c57f --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +local-letsencrypt +================= + +PoC for letsencrypt SSL certificates, as public, as local hswaw DNS zone is. + +Usage +----- + cp server/config.cfg.dist server/config.cfg + vim server/config.cfg + + cp client/hook-config.dist client/hook-config + vim client/hook-config + + (cd server && python master.py) & + + certbot-auto certonly --manual --preferred-challenges=dns \ + --manual-auth-hook `pwd`/client/install-hook.sh \ + --manual-cleanup-hook `pwd`/client/cleanup-hook.sh \ + --manual-public-ip-logging-ok \ + -d testing2.waw.inf.re diff --git a/client/cleanup-hook.sh b/client/cleanup-hook.sh new file mode 100755 index 0000000..23fe60a --- /dev/null +++ b/client/cleanup-hook.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +. $(dirname $(realpath $0))/hook-config + +curl "$API_URL/api/1/remove?token=$API_TOKEN&record=$CERTBOT_DOMAIN" diff --git a/client/hook-config.dist b/client/hook-config.dist new file mode 100644 index 0000000..ae9ed7a --- /dev/null +++ b/client/hook-config.dist @@ -0,0 +1,2 @@ +API_URL="http://localhost:5000" # you may want to keep it on ssl... +API_TOKEN="testtoken" diff --git a/client/install-hook.sh b/client/install-hook.sh new file mode 100755 index 0000000..08576dd --- /dev/null +++ b/client/install-hook.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +. $(dirname $(realpath $0))/hook-config + +curl "$API_URL/api/1/add?token=$API_TOKEN&record=$CERTBOT_DOMAIN&value=$CERTBOT_VALIDATION" + +# FIXME: ovh is shit +while [ "$(dig _acme-challenge.$CERTBOT_DOMAIN TXT +short | wc -l)" -lt 1 ]; do + echo 'still waiting...' >&2 + sleep 5; +done diff --git a/server/backends.py b/server/backends.py new file mode 100644 index 0000000..be95f8c --- /dev/null +++ b/server/backends.py @@ -0,0 +1,45 @@ +class Backend(object): + def __init__(self, config): + self.config = config + + def add(self, name, value): + raise NotImplemented + + def remove(self, name): + raise NotImplemented + + +class OVHBackend(Backend): + def __init__(self, config): + import ovh + import ovh.exceptions + + self.config = config + self.client = ovh.Client() + try: + self.client.get('/auth/currentCredential') + except (ovh.exceptions.InvalidKey, ovh.exceptions.InvalidCredential): + req = ovh.Client().request_consumerkey([ + {'method': 'GET', 'path': '/domain/zone/*/record'}, + {'method': 'POST', 'path': '/domain/zone/*/record'}, + {'method': 'POST', 'path': '/domain/zone/*/refresh'}, + {'method': 'DELETE', 'path': '/domain/zone/*/record/*'}, + ]) + print(req) + + def add(self, name, value): + if not name.endswith(self.config['OVH_ZONE']): + raise Exception() + + v = self.client.post('/domain/zone/%s/record' % (self.config['OVH_ZONE'],), + fieldType='TXT', subDomain=name+'.', target=value) + print(v) + self.client.post('/domain/zone/%s/refresh' % (self.config['OVH_ZONE'],)) + + def remove(self, name): + ids = self.client.get( + '/domain/zone/%s/record' % (self.config['OVH_ZONE'],), fieldType='TXT', subDomain=name+'.') + for i in ids: + print('Removing', i) + self.client.delete('/domain/zone/%s/record/%d' % (self.config['OVH_ZONE'], i)) + self.client.post('/domain/zone/%s/refresh' % (self.config['OVH_ZONE'],)) diff --git a/server/config.cfg.dist b/server/config.cfg.dist new file mode 100644 index 0000000..964eb19 --- /dev/null +++ b/server/config.cfg.dist @@ -0,0 +1,7 @@ +# Global configuration +TOKENS = { + 'testing3.waw.inf.re': 'testtoken', +} + +# OVH-specific backend configuration +OVH_ZONE = 'inf.re' diff --git a/server/master.py b/server/master.py new file mode 100644 index 0000000..bcb3076 --- /dev/null +++ b/server/master.py @@ -0,0 +1,26 @@ +import flask +from flask import request + +from backends import OVHBackend +from utils import verify_token + +app = flask.Flask(__name__) +app.config.from_pyfile('config.cfg') + +app.backend = OVHBackend(app.config) + +@app.route('/api/1/add') +@verify_token +def add(): + app.backend.add('_acme-challenge.'+request.args['record'], request.args['value']) + return 'ok' + +@app.route('/api/1/remove') +@verify_token +def remove(): + app.backend.remove('_acme-challenge.'+request.args['record']) + return 'ok' + + +if __name__ == "__main__": + app.run(debug=True) diff --git a/server/utils.py b/server/utils.py new file mode 100644 index 0000000..5536290 --- /dev/null +++ b/server/utils.py @@ -0,0 +1,18 @@ +from functools import wraps +from flask import current_app, request, abort + + +def verify_token(f): + """Verifies request token""" + + @wraps(f) + def wrapped(*args, **kwargs): + rec = request.args.get('record', None) + token = request.args.get('token', None) + + if rec in current_app.config['TOKENS'] and current_app.config['TOKENS'][rec] == token: + return f(*args, **kwargs) + + abort(403) + + return wrapped