From 8787f66777a0e952431c1f7bd33b2128ed083c44 Mon Sep 17 00:00:00 2001 From: mbugeia Date: Wed, 4 Nov 2015 19:07:53 +0100 Subject: [PATCH 1/2] Merge branch '' of /home/max/PycharmProjects/yunohost with conflicts. --- data/actionsmap/yunohost.yml | 28 +++++++ data/templates/nginx/server.conf.sed | 8 ++ src/yunohost/app.py | 2 +- src/yunohost/domain.py | 115 ++++++++++++++++++++++----- 4 files changed, 131 insertions(+), 22 deletions(-) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index bf6291614..9d27f1c63 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -277,6 +277,10 @@ domain: full: --dyndns help: Subscribe to the DynDNS service action: store_true + -l: + full: --noletsencrypt + help: Subscribe to the DynDNS service + action: store_true ### domain_remove() remove: @@ -291,6 +295,30 @@ domain: extra: pattern: *pattern_domain + ### domain_letsencrypt() + letsencrypt: + action_help: Manage let's encrypt certificate for a domain + api: POST /letsencrypt + configuration: + authenticate: all + arguments: + domain: + help: Domain to manage + extra: + pattern: *pattern_domain + -c: + full: --create + help: Create a Let's encrypt certificate + action: store_true + -r: + full: --renew + help: Force renewal of a Let's encrypt certificate + action: store_true + -d: + full: --revoke + help: Revoke a Let's encrypt certificate + action: store_true + ### domain_info() # info: # action_help: Get domain informations diff --git a/data/templates/nginx/server.conf.sed b/data/templates/nginx/server.conf.sed index 656a1d80e..1624cc19d 100644 --- a/data/templates/nginx/server.conf.sed +++ b/data/templates/nginx/server.conf.sed @@ -11,6 +11,14 @@ server { rewrite ^ https://$http_host$request_uri? permanent; } + root /etc/letsencrypt/webrootauth; + location /.well-known/acme-challenge { + alias /etc/letsencrypt/webrootauth/.well-known/acme-challenge; + location ~ /.well-known/acme-challenge/(.*) { + add_header Content-Type application/jose+json; + } + } + access_log /var/log/nginx/{{ domain }}-access.log; error_log /var/log/nginx/{{ domain }}-error.log; } diff --git a/src/yunohost/app.py b/src/yunohost/app.py index de6c6bb55..8e3680814 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1007,7 +1007,7 @@ def app_ssowatconf(auth): redirected_regex.update(app_settings['redirected_regex']) for domain in domains: - skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api']) + skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api', domain + '/.well-known/acme-challenge']) conf_dict = { 'portal_domain': main_domain, diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 53b938356..97449cfbe 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -69,8 +69,7 @@ def domain_list(auth, raw=False, filter=None, limit=None, offset=None): else: return { 'domains': result_list } - -def domain_add(auth, domain, dyndns=False): +def domain_add(auth, domain, dyndns=False, noletsencrypt=False): """ Create a custom domain @@ -125,24 +124,29 @@ def domain_add(auth, domain, dyndns=False): try: os.listdir(ssl_domain_path) except OSError: os.makedirs(ssl_domain_path) - command_list = [ - 'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path), - 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, ssl_domain_path), - 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' - % (ssl_domain_path, ssl_dir, ssl_dir), - 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' - % (ssl_domain_path, ssl_dir, ssl_dir), - 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % ssl_domain_path, - 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, ssl_domain_path), - 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, ssl_domain_path), - 'chmod 755 %s' % ssl_domain_path, - 'chmod 640 %s/key.pem' % ssl_domain_path, - 'chmod 640 %s/crt.pem' % ssl_domain_path, - 'chmod 600 %s/openssl.cnf' % ssl_domain_path, - 'chown root:metronome %s/key.pem' % ssl_domain_path, - 'chown root:metronome %s/crt.pem' % ssl_domain_path, - 'cat %s/ca.pem >> %s/crt.pem' % (ssl_domain_path, ssl_domain_path) - ] + if noletsencrypt: + command_list = [ + 'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path), + 'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, ssl_domain_path), + 'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch' + % (ssl_domain_path, ssl_dir, ssl_dir), + 'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch' + % (ssl_domain_path, ssl_dir, ssl_dir), + 'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % ssl_domain_path, + 'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, ssl_domain_path), + 'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, ssl_domain_path), + 'chmod 755 %s' % ssl_domain_path, + 'chmod 640 %s/key.pem' % ssl_domain_path, + 'chmod 640 %s/crt.pem' % ssl_domain_path, + 'chmod 600 %s/openssl.cnf' % ssl_domain_path, + 'chown root:metronome %s/key.pem' % ssl_domain_path, + 'chown root:metronome %s/crt.pem' % ssl_domain_path, + 'cat %s/ca.pem >> %s/crt.pem' % (ssl_domain_path, ssl_domain_path) + ] + else: + command_list = [ + 'yunohost domain letsencrypt -c %s' % domain + ] for command in command_list: if os.system(command) != 0: @@ -206,7 +210,22 @@ def domain_remove(auth, domain, force=False): m18n.n('domain_uninstall_app_first')) if auth.remove('virtualdomain=' + domain + ',ou=domains') or force: - os.system('rm -rf /etc/yunohost/certs/%s' % domain) + command_list = [ + 'rm -rf /etc/yunohost/certs/%s' % domain, + ] + + if os.path.exists('/etc/letsencrypt/live/%s' % domain): + command_list.extend([ + 'yunohost domain letsencrypt revoke %s' % domain, + 'rm -rf /etc/letsencrypt/archive/%s /etc/letsencrypt/live/%s' % domain, + 'rm -f /etc/letsencrypt/renewal/%s.conf' % domain, + 'rm -f /etc/cron.d/letsencrypt-{domain}' % domain + ]) + + for command in command_list: + if os.system(command) != 0: + msignals.display(m18n.n('path_removal_failed', command[7:]), + 'warning') else: raise MoulinetteError(errno.EIO, m18n.n('domain_deletion_failed')) @@ -218,3 +237,57 @@ def domain_remove(auth, domain, force=False): hook_callback('post_domain_remove', args=[domain]) msignals.display(m18n.n('domain_deleted'), 'success') + + +def domain_letsencrypt(auth, domain, create=False, renew=False, revoke=False): + """ + Manage let's encrypt certificate for a domain + + Keyword argument: + domain -- Domain to delete + create -- Create a Let's encrypt certificate + renew -- Force renewal of a Let's encrypt certificate + revoke -- Revoke a Let's encrypt certificate + + """ + if domain not in domain_list(auth)['domains']: + raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) + + if create and not renew and not revoke: + + # backup self signed certificate if exist + if os.path.exists('/etc/yunohost/certs/%s/cert.pem' % domain): + os.system('mkdir -p /etc/yunohost/certs/%s/yunohost_self_signed' % domain) + os.system('mv /etc/yunohost/certs/%s/*.pem /etc/yunohost/certs/%s/*.cnf /etc/yunohost/certs/%s/yunohost_self_signed/' % domain) + os.system('rm -f /etc/yunohost/certs/%s/*.pem /etc/yunohost/certs/%s/*.cnf' % domain) + + # create certificate + try: + os.system('/root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) + # restore right for metronome + os.system('chown root:metronome /etc/letsencrypt/archive/%s/*' % domain) + # create cron + os.system('echo "@monthly root yunohost domain letsencrypt -r %s" > /etc/cron.d/letsencrypt-%s' % domain) + # symbolic link for cert and key + os.system('ln -s /etc/letsencrypt/live/%s/privkey.pem /etc/yunohost/certs/%s/key.pem' % domain) + os.system('ln -s /etc/letsencrypt/live/%s/fullchain.pem /etc/yunohost/certs/%s/crt.pem' % domain) + msignals.display(m18n.n('domain_letsencrypt_created'), 'success') + except: + raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_create_failed')) + + elif renew and not create and not revoke: + try: + os.system('/root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) + # restore right for metronome + os.system('chown root:metronome /etc/letsencrypt/archive/%s/*' % domain) + msignals.display(m18n.n('domain_letsencrypt_renewed'), 'success') + except: + raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_renew_failed')) + elif revoke and not create and not renew: + try: + os.system('/root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) + msignals.display(m18n.n('domain_letsencrypt_revoked'), 'success') + except: + raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_revoke_failed')) + else: + raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_revoke_unknown')) From 55e9657f55f5f13bfaacab1571d27c09d193bf7b Mon Sep 17 00:00:00 2001 From: mbugeia Date: Wed, 4 Nov 2015 21:52:41 +0100 Subject: [PATCH 2/2] bootstrap let's encrypt feature --- data/hooks/conf_regen/16-letsencrypt | 31 ++++++++++++++++++++++++++++ locales/en.json | 7 +++++++ src/yunohost/domain.py | 16 +++++++------- 3 files changed, 46 insertions(+), 8 deletions(-) create mode 100644 data/hooks/conf_regen/16-letsencrypt diff --git a/data/hooks/conf_regen/16-letsencrypt b/data/hooks/conf_regen/16-letsencrypt new file mode 100644 index 000000000..838e35d9f --- /dev/null +++ b/data/hooks/conf_regen/16-letsencrypt @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +force=$1 + +function safe_copy () { + if [ $force ]; then + sudo yunohost service safecopy \ + -s letsencrypt \ + $1 $2 \ + --force + else + sudo yunohost service safecopy \ + -s letsencrypt \ + $1 $2 + fi +} + +# Install let's encrypt if not present +if [ ! -d /etc/letsencrypt ]; then + cd /root + git clone https://github.com/letsencrypt/letsencrypt /root + mkdir -p /etc/letsencrypt/webrootauth +fi + +domain_list=$(sudo yunohost domain list --plain) +for domain in $domain_list; do + if [ ! -d /etc/letsencrypt/live/$domain ]; then + yunohost domain letsencrypt -c $domain + fi +done \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index 02ddc02a2..0d3edebbb 100644 --- a/locales/en.json +++ b/locales/en.json @@ -57,6 +57,13 @@ "domain_deleted" : "Domain successfully deleted", "no_internet_connection": "Server not connected to the Internet", "no_ipv6_connectivity": "IPv6 connectivity is not available", + "domain_letsencrypt_created" : "Let's encrypt certificate successfully created.", + "domain_letsencrypt_create_failed" : "Unable to create Let's encrypt certificate.", + "domain_letsencrypt_renewed" : "Let's encrypt certificate successfully renewed.", + "domain_letsencrypt_renew_failed" : "Unable to renew Let's encrypt certificate.", + "domain_letsencrypt_revoked" : "Let's encrypt certificate successfully revoked.", + "domain_letsencrypt_revoke_failed" : "Unable to revoke Let's encrypt certificate.", + "domain_letsencrypt_badarg" : "Bad argument, choose between create, renew or revoke.", "dyndns_key_generating" : "DNS key is being generated, it may take a while...", "dyndns_unavailable" : "Unavailable DynDNS subdomain", diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 97449cfbe..6b288ab83 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -258,14 +258,14 @@ def domain_letsencrypt(auth, domain, create=False, renew=False, revoke=False): # backup self signed certificate if exist if os.path.exists('/etc/yunohost/certs/%s/cert.pem' % domain): os.system('mkdir -p /etc/yunohost/certs/%s/yunohost_self_signed' % domain) - os.system('mv /etc/yunohost/certs/%s/*.pem /etc/yunohost/certs/%s/*.cnf /etc/yunohost/certs/%s/yunohost_self_signed/' % domain) - os.system('rm -f /etc/yunohost/certs/%s/*.pem /etc/yunohost/certs/%s/*.cnf' % domain) + os.system('sudo mv /etc/yunohost/certs/%s/*.pem /etc/yunohost/certs/%s/*.cnf /etc/yunohost/certs/%s/yunohost_self_signed/' % domain) + os.system('sudo rm -f /etc/yunohost/certs/%s/*.pem /etc/yunohost/certs/%s/*.cnf' % domain) # create certificate try: - os.system('/root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) + os.system('sudo /root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) # restore right for metronome - os.system('chown root:metronome /etc/letsencrypt/archive/%s/*' % domain) + os.system('sudo chown root:metronome /etc/letsencrypt/archive/%s/*' % domain) # create cron os.system('echo "@monthly root yunohost domain letsencrypt -r %s" > /etc/cron.d/letsencrypt-%s' % domain) # symbolic link for cert and key @@ -277,17 +277,17 @@ def domain_letsencrypt(auth, domain, create=False, renew=False, revoke=False): elif renew and not create and not revoke: try: - os.system('/root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) + os.system('sudo /root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) # restore right for metronome - os.system('chown root:metronome /etc/letsencrypt/archive/%s/*' % domain) + os.system('sudo chown root:metronome /etc/letsencrypt/archive/%s/*' % domain) msignals.display(m18n.n('domain_letsencrypt_renewed'), 'success') except: raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_renew_failed')) elif revoke and not create and not renew: try: - os.system('/root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) + os.system('sudo /root/letsencrypt/letsencrypt-auto -a webroot --renew-by-default --agree-dev-preview --agree-tos --webroot-path /etc/letsencrypt/webrootauth -m root@%s -d %s auth' % domain) msignals.display(m18n.n('domain_letsencrypt_revoked'), 'success') except: raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_revoke_failed')) else: - raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_revoke_unknown')) + raise MoulinetteError(errno.EIO, m18n.n('domain_letsencrypt_badarg'))