diff --git a/type/__uacme_account/gencode-remote b/type/__uacme_account/gencode-remote new file mode 100644 index 0000000..e1d9551 --- /dev/null +++ b/type/__uacme_account/gencode-remote @@ -0,0 +1,32 @@ +#!/bin/sh + +os="$(cat "${__global:?}"/explorer/os)" + +case "$os" in + alpine|ubuntu|debian) + default_confdir=/etc/ssl/uacme + ;; + *) + echo "This type currently has no implementation for $os. Aborting." >&2; + exit 1 + ;; +esac + +admin_mail= +if [ -f "${__object:?}/parameter/admin-mail" ]; +then + admin_mail="$(cat "${__object:?}/parameter/admin-mail")"; +fi + +confdir="${default_confdir:?}" +if [ -f "${__object:?}/parameter/confdir" ]; +then + confdir="$(cat "${__object:?}/parameter/confdir")" +fi + +cat << EOF +if ! [ -f "${confdir}/private/key.pem" ]; +then + uacme -y new ${admin_mail} +fi +EOF diff --git a/type/__uacme_account/man.rst b/type/__uacme_account/man.rst new file mode 100644 index 0000000..be5efc6 --- /dev/null +++ b/type/__uacme_account/man.rst @@ -0,0 +1,52 @@ +cdist-type__uacme_account(7) +============================ + +NAME +---- +cdist-type__uacme_account - Install uacme and register Let's Encrypt account. + + +DESCRIPTION +----------- + +This type is used to bootstrap acquiring certificates from the Let's Encrypt +C.A by creating an account and accepting terms of use. The +`cdist-type__uacme_obtain(7)` type instances expect to depend on this type. + + +OPTIONAL PARAMETERS +------------------- +confdir + An alternative configuration directory for uacme's private keys and + certificates. + +admin-mail + Administrative contact email to register the account with. + +EXAMPLES +-------- + +.. code-block:: sh + + # Create account with default settings for the OS. + __uacme_account + + # Create an account with email and custom location. + __uacme_account --confdir /opt/custom/uacme --admin-mail admin@domain.tld + + +SEE ALSO +-------- +:strong:`cdist-type__letsencrypt_cert`\ (7) +:strong:`cdist-type__uacme_obtain`\ (7) + +AUTHORS +------- +Joachim Desroches + +COPYING +------- +Copyright \(C) 2020 Joachim Desroches. You can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. diff --git a/type/__uacme_account/manifest b/type/__uacme_account/manifest new file mode 100644 index 0000000..0515853 --- /dev/null +++ b/type/__uacme_account/manifest @@ -0,0 +1,14 @@ +#!/bin/sh + +os="$(cat "${__global:?}"/explorer/os)" + +case "$os" in + "alpine"|"debian"|"ubuntu") + uacme_package=uacme + ;; + *) + echo "__uacme_account is not yet implemented for os $os. Aborting." >&2; + exit 1; +esac + +__package $uacme_package diff --git a/type/__uacme_account/parameter/optional b/type/__uacme_account/parameter/optional new file mode 100644 index 0000000..0eaba67 --- /dev/null +++ b/type/__uacme_account/parameter/optional @@ -0,0 +1,2 @@ +confdir +admin-mail diff --git a/type/__uacme_account/singleton b/type/__uacme_account/singleton new file mode 100644 index 0000000..e69de29 diff --git a/type/__uacme_obtain/files/renew.sh.sh b/type/__uacme_obtain/files/renew.sh.sh new file mode 100755 index 0000000..18bf061 --- /dev/null +++ b/type/__uacme_obtain/files/renew.sh.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +cat << EOF +#!/bin/sh + +UACME_CHALLENGE_PATH=${CHALLENGEDIR:?} +export UACME_CHALLENGE_PATH + +# Issue certificate. +uacme -c ${CONFDIR:?} -h ${HOOKSCRIPT:?} ${DISABLE_OCSP?} ${MUST_STAPLE?} ${KEYTYPE?} \\ + issue -- ${DOMAIN:?} + +# Note: exit code 0 means that certificate was issued. +# Note: exit code 1 means that certificate was still valid, hence not renewed. +# Note: exit code 2 means that something went wrong. +status=\$? + +# All is well: we can stop now. +if [ \$status -eq 1 ]; +then + exit 0 +fi + +# An error occured. +if [ \$status -eq 2 ]; then + echo "Failed to renew certificate - exiting." >&2 + exit 1 +fi +EOF + +# Re-deploy, if needed. +if [ -n "${KEY_TARGET?}" ] && [ -n "${CERT_TARGET?}" ]; +then +cat << EOF + +# Deploy newly issued certificate. +set -e + +CERT_SOURCE=${CONFDIR:?}/${MAIN_DOMAIN:?}/cert.pem +KEY_SOURCE=${CONFDIR:?}/private/${MAIN_DOMAIN:?}/key.pem + +mkdir -p -- $(dirname "${CERT_TARGET?}") $(dirname "${KEY_TARGET?}") + +if ! cmp \${CERT_SOURCE:?} ${CERT_TARGET?} >/dev/null 2>&1; then + install -m 0640 \${KEY_SOURCE:?} ${KEY_TARGET?} + install -m 0644 \${CERT_SOURCE:?} ${CERT_TARGET?} + chown ${OWNER?} ${KEY_TARGET?} ${CERT_TARGET?} + + ${RENEW_HOOK?} +fi +EOF +fi diff --git a/type/__uacme_obtain/gencode-remote b/type/__uacme_obtain/gencode-remote new file mode 100644 index 0000000..739fc92 --- /dev/null +++ b/type/__uacme_obtain/gencode-remote @@ -0,0 +1,24 @@ +#!/bin/sh + +# FIXME: this code is duplicated from the manifest -> let's share it somehow. + +os="$(cat "${__global:?}"/explorer/os)" +case "$os" in + alpine|ubuntu|debian) + default_confdir=/etc/ssl/uacme + ;; + *) + echo "__uacme_obtain currently has no implementation for $os. Aborting." >&2; + exit 1; + ;; +esac + +confdir="${default_confdir:?}" +if [ -f "${__object:?}/parameter/confdir" ]; +then + confdir="$(cat "${__object:?}/parameter/confdir")" +fi + +# Run renew script 'by hand' for intial issue - renews will be handled by the +# cronjob. +echo "$confdir/${__object_id:?}/renew.sh" diff --git a/type/__uacme_obtain/man.rst b/type/__uacme_obtain/man.rst new file mode 100644 index 0000000..f1db899 --- /dev/null +++ b/type/__uacme_obtain/man.rst @@ -0,0 +1,81 @@ +cdist-type__uacme_obtain(7) +=========================== + +NAME +---- +cdist-type__uacme_obtain - obtain, renew and deploy Let's Encrypt certificates + +DESCRIPTION +----------- + +This type leverage uacme to issue and renew Let's Encrypt certificates and +provides a simple deployment mechanism. It is expected to be called after +`__uacme_account`. + +REQUIRED PARAMETERS +------------------- +None. + +OPTIONAL PARAMETERS +------------------- +challengedir + Path to publicly available (served by a third-party HTTP server, under + `$DOMAIN/.well-known/acme-challenge`) challenge directory. + +confdir + uacme configuration directory. + +hookscript + Path to the challenge hook program. + +owner + Owner of installed certificate (e.g. `www-data`), passed to `chown`. + +install-cert-to + Installation path of the issued certificate. + +install-key-to + Installation path of the certificate's private key. + +renew-hook + Renew hook executed on certificate renewal (e.g. `service nginx reload`). + +force-cert-ownership-to + Override default ownership for TLS certificate, passed as argument to chown. + +OPTIONAL MULTIPLE PARAMETERS +---------------------------- +altdomains + Alternative domain names for this certificate. + +BOOLEAN PARAMETERS +------------------ +no-ocsp + When this flag is *not* specified and the certificate has an Authority + Information Access extension with an OCSP server location *uacme* makes an + OCSP request to the server; if the certificate is reported as revoked *uacme* + forces reissuance regardless of the expiration date. + +must-staple + Request certificates with the RFC7633 Certificate Status Request + TLS Feature Extension, informally also known as "OCSP Must-Staple". + +use-rsa + Use RSA instead of EC for the private key. Only applies to newly generated keys. + +SEE ALSO +-------- +:strong:`cdist-type__letsencrypt_cert`\ (7) +:strong:`cdist-type__uacme_account`\ (7) + +AUTHORS +------- +Joachim Desroches +Timothée Floure + +COPYING +------- +Copyright \(C) 2020 Joachim Desroches. You can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. diff --git a/type/__uacme_obtain/manifest b/type/__uacme_obtain/manifest new file mode 100644 index 0000000..f41e881 --- /dev/null +++ b/type/__uacme_obtain/manifest @@ -0,0 +1,121 @@ +#!/bin/sh + +os="$(cat "${__global:?}"/explorer/os)" +case "$os" in + alpine|ubuntu|debian) + __package uacme + + default_challengedir=/var/www/.well-known/acme-challenge + default_hookscript=/usr/share/uacme/uacme.sh + default_confdir=/etc/ssl/uacme + ;; + *) + echo "__uacme_obtain currently has no implementation for $os. Aborting." >&2; + exit 1; + ;; +esac + +CHALLENGEDIR=${default_challengedir:?} +if [ -f "${__object:?}/parameter/challengedir" ]; +then + CHALLENGEDIR="$(cat "${__object:?}/parameter/challengedir")" +fi +export CHALLENGEDIR + +CONFDIR="${default_confdir:?}" +if [ -f "${__object:?}/parameter/confdir" ]; +then + CONFDIR="$(cat "${__object:?}/parameter/confdir")" +fi +export CONFDIR + +DISABLE_OCSP= +if [ -f "${__object:?}/parameter/no-ocsp" ]; +then + DISABLE_OCSP="--no-ocsp" +fi +export DISABLE_OCSP + +MAIN_DOMAIN=${__object_id:?} +DOMAIN=$MAIN_DOMAIN +if [ -f "${__object:?}/parameter/altdomains" ]; +then + # shellcheck disable=SC2013 + for altdomain in $(cat "${__object:?}/parameter/altdomains"); + do + DOMAIN="$DOMAIN $altdomain" + done +fi +export MAIN_DOMAIN DOMAIN + +HOOKSCRIPT=${default_hookscript} +if [ -f "${__object:?}/parameter/hookscript" ]; +then + HOOKSCRIPT="$(cat "${__object:?}/parameter/hookscript")" +fi +export HOOKSCRIPT + +KEYTYPE="-t EC" +if [ -f "${__object:?}/parameter/use-rsa" ]; +then + KEYTYPE="-t RSA" +fi +export KEYTYPE + +MUST_STAPLE= +if [ -f "${__object:?}/parameter/must-staple" ]; +then + MUST_STAPLE="--must-staple" +fi +export MUST_STAPLE + +OWNER=root +if [ -f "${__object:?}/parameter/owner" ]; +then + OWNER="$(cat "${__object:?}/parameter/owner")" +fi +export OWNER + +KEY_TARGET= +if [ -f "${__object:?}/parameter/install-key-to" ]; +then + KEY_TARGET="$(cat "${__object:?}/parameter/install-key-to")" +fi +export KEY_TARGET + +CERT_TARGET= +if [ -f "${__object:?}/parameter/install-cert-to" ]; +then + CERT_TARGET="$(cat "${__object:?}/parameter/install-cert-to")" +fi +export CERT_TARGET + +RENEW_HOOK= +if [ -f "${__object:?}/parameter/renew-hook" ]; +then + RENEW_HOOK="$(cat "${__object:?}/parameter/renew-hook")" +fi +export RENEW_HOOK + +if [ -n "$KEY_TARGET" ] && [ -z "$CERT_TARGET" ]; then + echo "You cannot specify --install-key-to without --install-cert-to." >&2 + exit 1 +elif [ -z "$KEY_TARGET" ] && [ -n "$CERT_TARGET" ]; then + echo "You cannot specify --install-cert-to without --install-key-to." >&2 + exit 1 +fi + +# Make sure challengedir exist. +__directory "$CHALLENGEDIR" --parents + +# Generate and deploy renew script. +mkdir -p "${__object:?}/files" +"${__type:?}/files/renew.sh.sh" > "${__object:?}/files/uacme-renew.sh" + +__directory "$CONFDIR/$MAIN_DOMAIN" +require="__directory/$CONFDIR/$MAIN_DOMAIN" __file "$CONFDIR/$MAIN_DOMAIN/renew.sh" \ + --mode 0755 --source "${__object:?}/files/uacme-renew.sh" + +# Set up renew cronjob - initial issue done in gencode-remote. +__cron "uacme-$MAIN_DOMAIN" --user root --hour 2 --minute 0 \ + --command "$CONFDIR/$MAIN_DOMAIN/renew.sh" diff --git a/type/__uacme_obtain/parameter/boolean b/type/__uacme_obtain/parameter/boolean new file mode 100644 index 0000000..44c04f4 --- /dev/null +++ b/type/__uacme_obtain/parameter/boolean @@ -0,0 +1,3 @@ +no-ocsp +must-staple +use-rsa diff --git a/type/__uacme_obtain/parameter/optional b/type/__uacme_obtain/parameter/optional new file mode 100644 index 0000000..fd721af --- /dev/null +++ b/type/__uacme_obtain/parameter/optional @@ -0,0 +1,7 @@ +challengedir +confdir +hookscript +owner +install-cert-to +install-key-to +renew-hook diff --git a/type/__uacme_obtain/parameter/optional_multiple b/type/__uacme_obtain/parameter/optional_multiple new file mode 100644 index 0000000..d3ebd88 --- /dev/null +++ b/type/__uacme_obtain/parameter/optional_multiple @@ -0,0 +1 @@ +altdomains