[__letsencrypt_acmetiny] Simpler alternative to certbot.
This is inspired heavily by `debops.pki` in the https://debops.org project. However there are several simplifications to their way of doing it.
This commit is contained in:
parent
f7d5f5bc97
commit
1bd19d6dee
8 changed files with 326 additions and 0 deletions
cdist/conf/type
__letsencrypt_acmetiny
__letsencrypt_acmetiny_base
109
cdist/conf/type/__letsencrypt_acmetiny/gencode-remote
Normal file
109
cdist/conf/type/__letsencrypt_acmetiny/gencode-remote
Normal file
|
@ -0,0 +1,109 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
ACME_TINY_CERT_REQUEST_DIR="/var/acme-tiny/cert-requests"
|
||||
ACME_TINY_ACCOUNT_KEY="/var/acme-tiny/account.key"
|
||||
ACME_CHALLENGE_DIR="/srv/www/sites/acme/public/.well-known/acme-challenge"
|
||||
|
||||
REALM="${__object_id}"
|
||||
EXTRA_DOMAINS=""
|
||||
if [ -f "${__object}/parameter/extra-domain" ]; then
|
||||
EXTRA_DOMAINS="$(cat "${__object}/parameter/extra-domain")"
|
||||
fi
|
||||
|
||||
#TODO: support linux too
|
||||
REALM_DIR="/usr/local/etc/pki/realms/${REALM}"
|
||||
REALM_CERT="${REALM_DIR}/default.crt"
|
||||
REALM_KEY="${REALM_DIR}/default.key"
|
||||
REALM_CERT_REQUEST="${ACME_TINY_CERT_REQUEST_DIR}/${REALM}.csr"
|
||||
REALM_CERT_REQUEST_CNF="${ACME_TINY_CERT_REQUEST_DIR}/${REALM}.cnf"
|
||||
|
||||
CSR_ALT_NAMES=""
|
||||
REALM_CERT_REQUEST_CNF_LINE=""
|
||||
if [ -n "${EXTRA_DOMAINS}" ]; then
|
||||
CSR_ALT_NAMES="DNS:${REALM}"
|
||||
for domain in ${EXTRA_DOMAINS}; do
|
||||
CSR_ALT_NAMES="${CSR_ALT_NAMES},DNS:${domain}"
|
||||
done
|
||||
# CSR requests are executed always against .new, only after succeeding .new replaces the .cnf
|
||||
REALM_CERT_REQUEST_CNF_LINE="-reqexts SAN -config '${REALM_CERT_REQUEST_CNF}.new'"
|
||||
fi
|
||||
|
||||
cat << EOF
|
||||
if [ ! -d '${REALM_DIR}' ]; then
|
||||
mkdir -p '${REALM_DIR}'
|
||||
fi
|
||||
if [ ! -f '${REALM_KEY}' ]; then
|
||||
openssl genrsa 4096 > '${REALM_KEY}'
|
||||
fi
|
||||
|
||||
if [ ! -d '${ACME_TINY_CERT_REQUEST_DIR}' ]; then
|
||||
mkdir '${ACME_TINY_CERT_REQUEST_DIR}'
|
||||
fi
|
||||
|
||||
FORCE_CSR_REGEN=""
|
||||
if [ -n '${CSR_ALT_NAMES}' ]; then
|
||||
# Generate new config
|
||||
cat /etc/ssl/openssl.cnf > '${REALM_CERT_REQUEST_CNF}.new'
|
||||
printf '[SAN]\nsubjectAltName=${CSR_ALT_NAMES}' >> '${REALM_CERT_REQUEST_CNF}.new'
|
||||
# Compare to previous config if necessary
|
||||
if [ -f '${REALM_CERT_REQUEST_CNF}' ]; then
|
||||
CNF_DIFF=\$(diff -q '${REALM_CERT_REQUEST_CNF}' '${REALM_CERT_REQUEST_CNF}.new' || true)
|
||||
if [ -n "\${CNF_DIFF}" ]; then
|
||||
# Options have changed
|
||||
FORCE_CSR_REGEN="YES"
|
||||
else
|
||||
# Since they match, we won't be using this, clean it
|
||||
rm '${REALM_CERT_REQUEST_CNF}.new'
|
||||
fi
|
||||
else
|
||||
# We never used SAN here, CSR regen needed.
|
||||
FORCE_CSR_REGEN="YES"
|
||||
fi
|
||||
else
|
||||
# We used SAN at some point, not any more
|
||||
if [ -f '${REALM_CERT_REQUEST_CNF}' ]; then
|
||||
rm '${REALM_CERT_REQUEST_CNF}'
|
||||
FORCE_CSR_REGEN="YES"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create or re-create when params have changed
|
||||
if [ ! -f '${REALM_CERT_REQUEST}' -o -n "\${FORCE_CSR_REGEN}" ]; then
|
||||
openssl req -new -sha256 -key '${REALM_KEY}' -subj '/CN=${REALM}' -out '${REALM_CERT_REQUEST}' ${REALM_CERT_REQUEST_CNF_LINE}
|
||||
fi
|
||||
|
||||
# Check if cert exists, and if so whether or not it's older than a month
|
||||
if [ -f '${REALM_CERT}' ]; then
|
||||
MODIFIED_IN_30d="\$(find '${REALM_CERT}' -mtime -30d)"
|
||||
if [ -z "\${MODIFIED_IN_30d}" ]; then
|
||||
# Cert is over a month old, it's fine to regenerate
|
||||
FORCE_CRT_REGEN="YES"
|
||||
fi
|
||||
else
|
||||
# This cert doesn't exist
|
||||
FORCE_CRT_REGEN="YES"
|
||||
fi
|
||||
|
||||
|
||||
# Only request certificate when needed
|
||||
# TODO: support linux too
|
||||
if [ -n "\${FORCE_CSR_REGEN}" -o -n "\${FORCE_CRT_REGEN}" ]; then
|
||||
doas -u acme-tiny -- acme_tiny \
|
||||
--account '${ACME_TINY_ACCOUNT_KEY}' \
|
||||
--csr '${REALM_CERT_REQUEST}' \
|
||||
--acme-dir '${ACME_CHALLENGE_DIR}' > '${REALM_CERT}.new'
|
||||
|
||||
if [ -s '${REALM_CERT}.new' ]; then
|
||||
mv '${REALM_CERT}.new' '${REALM_CERT}'
|
||||
else
|
||||
echo "Failed to generate cert for realm '${REALM}'."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n '${REALM_CERT_REQUEST_CNF_LINE}' -a -f '${REALM_CERT_REQUEST_CNF}.new' ]; then
|
||||
# CSR and cert generation succeded with a new config, put new config in-place.
|
||||
# This is the last thing we do, so we try again next time if sth fails.
|
||||
mv '${REALM_CERT_REQUEST_CNF}.new' '${REALM_CERT_REQUEST_CNF}'
|
||||
fi
|
||||
EOF
|
1
cdist/conf/type/__letsencrypt_acmetiny/manifest
Normal file
1
cdist/conf/type/__letsencrypt_acmetiny/manifest
Normal file
|
@ -0,0 +1 @@
|
|||
#__letsencrypt_acmetiny_base
|
0
cdist/conf/type/__letsencrypt_acmetiny/nonparallel
Normal file
0
cdist/conf/type/__letsencrypt_acmetiny/nonparallel
Normal file
|
@ -0,0 +1 @@
|
|||
extra-domain
|
12
cdist/conf/type/__letsencrypt_acmetiny_base/gencode-remote
Normal file
12
cdist/conf/type/__letsencrypt_acmetiny_base/gencode-remote
Normal file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
ACME_HOME="/var/acme-tiny"
|
||||
ACME_ACCOUNT_KEY="${ACME_HOME}/account.key"
|
||||
|
||||
cat << EOF
|
||||
if [ ! -f '${ACME_ACCOUNT_KEY}' ]; then
|
||||
openssl genrsa 4096 > '${ACME_ACCOUNT_KEY}'
|
||||
chown acme-tiny:acme-tiny '${ACME_ACCOUNT_KEY}'
|
||||
chmod 640 '${ACME_ACCOUNT_KEY}'
|
||||
fi
|
||||
EOF
|
202
cdist/conf/type/__letsencrypt_acmetiny_base/manifest
Normal file
202
cdist/conf/type/__letsencrypt_acmetiny_base/manifest
Normal file
|
@ -0,0 +1,202 @@
|
|||
# Arguments
|
||||
ACME_DOMAIN="$(cat $__object/parameter/acme_domain || true)"
|
||||
|
||||
if [ -z "${ACME_DOMAIN}" ]; then
|
||||
ACME_DOMAIN="${__target_host}"
|
||||
fi
|
||||
|
||||
|
||||
# Install needed stuffz
|
||||
|
||||
## TODO: consider not depending on nginx? It is... practical though.
|
||||
## TODO: Maybe just move this out to a sepecial type?
|
||||
__package "nginx"
|
||||
|
||||
NGINX_ETC="/usr/local/etc/nginx"
|
||||
|
||||
# Setup the acme-challenge snippet
|
||||
require="__package/nginx" __directory "${NGINX_ETC}/snippets" --state present
|
||||
require="__directory${NGINX_ETC}/snippets" __file "${NGINX_ETC}/snippets/acme-challenge.conf" \
|
||||
--mode 644 \
|
||||
--source - << EOF
|
||||
# This file is managed remotely, all changes will be lost
|
||||
|
||||
# This was heavily inspired by debops.org.
|
||||
|
||||
# Automatic Certificate Management Environment (ACME) support.
|
||||
# https://tools.ietf.org/html/draft-ietf-acme-acme-01
|
||||
# https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
|
||||
|
||||
|
||||
# Return the ACME challenge present in the server public root.
|
||||
# If not found, switch to global web server root.
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
default_type "text/plain";
|
||||
try_files \$uri @well-known-acme-challenge;
|
||||
}
|
||||
|
||||
# Return the ACME challenge present in the global server public root.
|
||||
# If not present, redirect request to a specified domain.
|
||||
location @well-known-acme-challenge {
|
||||
root /srv/www/sites/acme/public;
|
||||
default_type "text/plain";
|
||||
try_files \$uri @redirect-acme-challenge;
|
||||
}
|
||||
|
||||
# Redirect the ACME challenge to a different host. If a redirect loop is
|
||||
# detected, return 404.
|
||||
location @redirect-acme-challenge {
|
||||
if (\$arg_redirect) {
|
||||
return 404;
|
||||
}
|
||||
return 307 \$scheme://${ACME_DOMAIN}\$request_uri?redirect=yes;
|
||||
}
|
||||
|
||||
# Return 404 if ACME challenge well known path is accessed directly.
|
||||
location = /.well-known/acme-challenge/ {
|
||||
return 404;
|
||||
}
|
||||
EOF
|
||||
|
||||
require="__package/nginx" __directory "${NGINX_ETC}/sites-enabled" --state present
|
||||
require="__directory${NGINX_ETC}/sites-enabled" __file "${NGINX_ETC}/nginx.conf" \
|
||||
--mode 644 \
|
||||
--source - << EOF
|
||||
# This file is managed remotely, all changes will be lost
|
||||
|
||||
worker_processes 1;
|
||||
|
||||
# This default error log path is compiled-in to make sure configuration parsing
|
||||
# errors are logged somewhere, especially during unattended boot when stderr
|
||||
# isn't normally logged anywhere. This path will be touched on every nginx
|
||||
# start regardless of error log location configured here. See
|
||||
# https://trac.nginx.org/nginx/ticket/147 for more info.
|
||||
#
|
||||
#error_log /var/log/nginx/error.log;
|
||||
#
|
||||
|
||||
#pid logs/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
server_tokens off;
|
||||
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 5m;
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
types_hash_max_size 2048;
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
gzip_comp_level 5;
|
||||
gzip_min_length 256;
|
||||
gzip_proxied any;
|
||||
gzip_vary on;
|
||||
gzip_types
|
||||
application/atom+xml
|
||||
application/javascript
|
||||
application/json
|
||||
application/ld+json
|
||||
application/manifest+json
|
||||
application/rss+xml
|
||||
application/vnd.geo+json
|
||||
application/vnd.ms-fontobject
|
||||
application/x-font-ttf
|
||||
application/x-web-app-manifest+json
|
||||
application/xhtml+xml
|
||||
application/xml
|
||||
font/opentype
|
||||
image/bmp
|
||||
image/svg+xml
|
||||
image/x-icon
|
||||
text/cache-manifest
|
||||
text/css
|
||||
text/plain
|
||||
text/vcard
|
||||
text/vnd.rim.location.xloc
|
||||
text/vtt
|
||||
text/x-component
|
||||
text/x-cross-domain-policy;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
#add_header X-Clacks-Overhead "GNU Terry Pratchett";
|
||||
|
||||
# Virtual Hosts Configs
|
||||
include ${NGINX_ETC}/sites-enabled/*.conf;
|
||||
}
|
||||
EOF
|
||||
|
||||
require="__directory${NGINX_ETC}/sites-enabled" __file "${NGINX_ETC}/sites-enabled/welcome.conf" \
|
||||
--mode 644 \
|
||||
--source - << EOF
|
||||
# This file is managed remotely, all changes will be lost
|
||||
|
||||
# nginx server configuration for:
|
||||
# - https://welcome/
|
||||
|
||||
server {
|
||||
|
||||
listen [::]:80;
|
||||
|
||||
server_name welcome;
|
||||
|
||||
root /srv/www/sites/welcome/public;
|
||||
|
||||
include snippets/acme-challenge.conf;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
## TODO: this is kinda bad, don't restart every time.
|
||||
## Otherwise this isn't idempotent.
|
||||
require="__package/nginx" __service nginx --action onerestart
|
||||
require="__package/nginx" __start_on_boot nginx
|
||||
|
||||
|
||||
__package "acme-tiny"
|
||||
|
||||
# Create acme-tiny user and secure home dir
|
||||
ACME_TINY_HOME="/var/acme-tiny"
|
||||
require="__package/acme-tiny" __user acme-tiny --system --home ${ACME_TINY_HOME} --comment "acme-tiny client"
|
||||
require="__user/acme-tiny" __directory "${ACME_TINY_HOME}" --state present --mode 0750 --owner acme-tiny --group acme-tiny
|
||||
|
||||
# Create ACME challenge dirs to be served by nginx
|
||||
ACME_PUBLIC_DIR="/srv/www/sites/acme/public"
|
||||
ACME_WELLKNOWN_DIR="${ACME_PUBLIC_DIR}/.well-known"
|
||||
ACME_CHALLENGE_DIR="${ACME_WELLKNOWN_DIR}/acme-challenge"
|
||||
__directory "${ACME_PUBLIC_DIR}" \
|
||||
--parents \
|
||||
--state present \
|
||||
--owner acme-tiny --group www \
|
||||
--mode 2750 # TODO: check whether this does require gid?
|
||||
require="__directory${ACME_PUBLIC_DIR}" __directory "${ACME_WELLKNOWN_DIR}" \
|
||||
--state present \
|
||||
--owner acme-tiny --group www \
|
||||
--mode 0750
|
||||
require="__directory${ACME_WELLKNOWN_DIR}" __directory "${ACME_CHALLENGE_DIR}" \
|
||||
--state present \
|
||||
--owner acme-tiny --group www \
|
||||
--mode 0750
|
||||
|
||||
__package doas
|
||||
DOAS_CONF="/usr/local/etc/doas.conf"
|
||||
require="__package/doas" __file "${DOAS_CONF}" --mode 0640
|
||||
require="__file${DOAS_CONF}" __line "${DOAS_CONF}" \
|
||||
--regex 'root as acme-tiny' \
|
||||
--line 'permit nopass root as acme-tiny'
|
|
@ -0,0 +1 @@
|
|||
acme_domain
|
0
cdist/conf/type/__letsencrypt_acmetiny_base/singleton
Normal file
0
cdist/conf/type/__letsencrypt_acmetiny_base/singleton
Normal file
Loading…
Reference in a new issue