RFC: letsencrypt SSL certificate support (#38)

* Add Let's Encrypt SSL cert support.

This patch has piku use the acme.sh script to request and maintain Let's
Encrypt SSL certs rather than generate self-signed certs.  For it to
work you must install acme.sh as the user piku.  Installation
instructions here: https://github.com/Neilpang/acme.sh#1-how-to-install
The next commit updates piku-bootstrap to install acme.sh by default.

If acme.sh is not installed piku continues to default to a self-signed
certificate.

* Install acme.sh SSL cert wrangler in bootstrap.

The previous commit contains details about usage.
pull/45/head
Chris McCormick 2019-06-23 22:47:58 +08:00 zatwierdzone przez Rui Carmo
rodzic 0a1dc6cf13
commit a9b58917b4
2 zmienionych plików z 74 dodań i 1 usunięć

Wyświetl plik

@ -178,6 +178,32 @@ main() {
args:
creates: ~/.ssh/authorized_keys
- name: Check if acme.sh is already installed
stat:
path: ~/.acme.sh/acme.sh
register: acme_stat_result
- name: Download acme.sh
get_url:
url: https://raw.githubusercontent.com/Neilpang/acme.sh/6ff3f5d/acme.sh
dest: ~/acme.sh
mode: 0755
when: acme_stat_result.stat.exists == False
register: acme_installer
- name: Execute acme.sh installer
shell: ./acme.sh --install
args:
chdir: ~/
creates: ~/.acme.sh/acme.sh
executable: /bin/bash
when: acme_installer is defined
- name: Remove acme.sh installer
file: path=~/acme.sh state=absent
when: acme_installer is defined
- hosts: all
become: yes
tasks:

49
piku.py
Wyświetl plik

@ -49,6 +49,8 @@ UWSGI_AVAILABLE = abspath(join(PIKU_ROOT, "uwsgi-available"))
UWSGI_ENABLED = abspath(join(PIKU_ROOT, "uwsgi-enabled"))
UWSGI_ROOT = abspath(join(PIKU_ROOT, "uwsgi"))
UWSGI_LOG_MAXSIZE = '1048576'
ACME_ROOT = environ.get('ACME_ROOT', join(environ['HOME'],'.acme.sh'))
ACME_WWW = abspath(join(PIKU_ROOT, "acme"))
# pylint: disable=anomalous-backslash-in-string
NGINX_TEMPLATE = """
@ -84,6 +86,11 @@ server {
$INTERNAL_NGINX_STATIC_MAPPINGS
location ^~ /.well-known/acme-challenge {
allow all;
root ${ACME_WWW};
}
location / {
$INTERNAL_NGINX_UWSGI_SETTINGS
proxy_http_version 1.1;
@ -96,6 +103,7 @@ server {
proxy_set_header X-Request-Start $msec;
$NGINX_ACL
}
}
"""
@ -107,6 +115,12 @@ server {
listen $NGINX_IPV6_ADDRESS:80;
listen $NGINX_IPV4_ADDRESS:80;
server_name $NGINX_SERVER_NAME;
location ^~ /.well-known/acme-challenge {
allow all;
root ${ACME_WWW};
}
return 301 https://$server_name$request_uri;
}
@ -152,6 +166,18 @@ server {
"""
# pylint: enable=anomalous-backslash-in-string
NGINX_ACME_FIRSTRUN_TEMPLATE = """
server {
listen $NGINX_IPV6_ADDRESS:80;
listen $NGINX_IPV4_ADDRESS:80;
server_name $NGINX_SERVER_NAME;
location ^~ /.well-known/acme-challenge {
allow all;
root ${ACME_WWW};
}
}
"""
INTERNAL_NGINX_STATIC_MAPPING = """
location $static_url {
sendfile on;
@ -506,10 +532,12 @@ def spawn_app(app, deltas={}):
nginx_ssl += " http2"
elif "--with-http_spdy_module" in nginx and "nginx/1.6.2" not in nginx: # avoid Raspbian bug
nginx_ssl += " spdy"
nginx_conf = join(NGINX_ROOT,"{}.conf".format(app))
env.update({
'NGINX_SSL': nginx_ssl,
'NGINX_ROOT': NGINX_ROOT,
'ACME_WWW': ACME_WWW,
})
# default to reverse proxying to the TCP port we picked
@ -527,7 +555,26 @@ def spawn_app(app, deltas={}):
domain = env['NGINX_SERVER_NAME'].split()[0]
key, crt = [join(NGINX_ROOT, "{}.{}".format(app,x)) for x in ['key','crt']]
if exists(join(ACME_ROOT, "acme.sh")):
acme = ACME_ROOT
www = ACME_WWW
# if this is the first run there will be no nginx conf yet
# create a basic conf stub just to serve the acme auth
if not exists(nginx_conf):
echo("-----> writing temporary nginx conf")
buffer = expandvars(NGINX_ACME_FIRSTRUN_TEMPLATE, env)
with open(nginx_conf, "w") as h:
h.write(buffer)
if not exists(key) or not exists(join(ACME_ROOT, domain, domain + ".key")):
echo("-----> getting letsencrypt certificate")
call('{acme:s}/acme.sh --issue -d {domain:s} -w {www:s}'.format(**locals()), shell=True)
call('{acme:s}/acme.sh --install-cert -d {domain:s} --key-file {key:s} --fullchain-file {crt:s}'.format(**locals()), shell=True)
else:
echo("-----> letsencrypt certificate already installed")
# fall back to creating self-signed certificate if acme failed
if not exists(key):
echo("-----> generating self-signed certificate")
call('openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=NY/L=New York/O=Piku/OU=Self-Signed/CN={domain:s}" -keyout {key:s} -out {crt:s}'.format(**locals()), shell=True)
# restrict access to server from CloudFlare IP addresses
@ -573,7 +620,7 @@ def spawn_app(app, deltas={}):
echo("-----> nginx will redirect all requests to hostname '{}' to HTTPS".format(env['NGINX_SERVER_NAME']))
else:
buffer = expandvars(NGINX_TEMPLATE, env)
with open(join(NGINX_ROOT,"{}.conf".format(app)), "w") as h:
with open(nginx_conf, "w") as h:
h.write(buffer)
# Configured worker count