From 1370abb9912cf14277586ae58958aacec5ea9f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Fri, 19 Feb 2021 10:44:58 +0100 Subject: [PATCH 1/5] [__uacme_*] Import from cdist-recycledcloud --- type/__uacme_account/gencode-remote | 32 +++++ type/__uacme_account/man.rst | 50 ++++++++ type/__uacme_account/manifest | 3 + type/__uacme_account/parameter/optional | 2 + type/__uacme_account/singleton | 0 type/__uacme_obtain/files/renew.sh.sh | 38 ++++++ type/__uacme_obtain/gencode-remote | 23 ++++ type/__uacme_obtain/man.rst | 81 ++++++++++++ type/__uacme_obtain/manifest | 121 ++++++++++++++++++ type/__uacme_obtain/parameter/boolean | 3 + type/__uacme_obtain/parameter/optional | 7 + .../parameter/optional_multiple | 1 + 12 files changed, 361 insertions(+) create mode 100644 type/__uacme_account/gencode-remote create mode 100644 type/__uacme_account/man.rst create mode 100644 type/__uacme_account/manifest create mode 100644 type/__uacme_account/parameter/optional create mode 100644 type/__uacme_account/singleton create mode 100755 type/__uacme_obtain/files/renew.sh.sh create mode 100644 type/__uacme_obtain/gencode-remote create mode 100644 type/__uacme_obtain/man.rst create mode 100644 type/__uacme_obtain/manifest create mode 100644 type/__uacme_obtain/parameter/boolean create mode 100644 type/__uacme_obtain/parameter/optional create mode 100644 type/__uacme_obtain/parameter/optional_multiple 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..a2539ff --- /dev/null +++ b/type/__uacme_account/man.rst @@ -0,0 +1,50 @@ +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. This type is expected to be used internally, by the `__uacme_obtain`. + + +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..3f17d5e --- /dev/null +++ b/type/__uacme_account/manifest @@ -0,0 +1,3 @@ +#!/bin/sh + +__package uacme 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..163ae4b --- /dev/null +++ b/type/__uacme_obtain/files/renew.sh.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +cat << EOF +#!/bin/sh + +set -x + +CERT_SOURCE=$CONFDIR/$MAIN_DOMAIN/cert.pem +KEY_SOURCE=$CONFDIR/private/$MAIN_DOMAIN/key.pem + +export UACME_CHALLENGE_PATH=$CHALLENGEDIR + +# Issue certificate. +uacme issue -c $CONFDIR -h $HOOKSCRIPT $DISABLE_OCSP $MUST_STABLE $KEYTYPE \ +$DOMAIN +if [ $? -eq 2 ]; then + # 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. + echo "Failed to renew certificate - exiting." >&2 + exit 1 +fi + +# Re-deploy, if needed. +if [ -n "$KEY_TARGET" ] && [ -n "$CERT_TARGET" ]; then + set -e + + 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 +fi +EOF diff --git a/type/__uacme_obtain/gencode-remote b/type/__uacme_obtain/gencode-remote new file mode 100644 index 0000000..10d0644 --- /dev/null +++ b/type/__uacme_obtain/gencode-remote @@ -0,0 +1,23 @@ +#!/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' - 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..4063107 --- /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 abd 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 challenge hook program. + +owner + Owner of installed certificate (e.g. `www-data`), pass as argument 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..59ddce8 --- /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 From 5e2a28d929b0b9bd5172ae7281d5bd219ac24783 Mon Sep 17 00:00:00 2001 From: Joachim Desroches Date: Tue, 16 Mar 2021 11:04:26 +0100 Subject: [PATCH 2/5] Remove -x from renew.sh script to avoid empty cron messages. --- type/__uacme_obtain/files/renew.sh.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/type/__uacme_obtain/files/renew.sh.sh b/type/__uacme_obtain/files/renew.sh.sh index 163ae4b..e3e041c 100755 --- a/type/__uacme_obtain/files/renew.sh.sh +++ b/type/__uacme_obtain/files/renew.sh.sh @@ -3,8 +3,6 @@ cat << EOF #!/bin/sh -set -x - CERT_SOURCE=$CONFDIR/$MAIN_DOMAIN/cert.pem KEY_SOURCE=$CONFDIR/private/$MAIN_DOMAIN/key.pem From 5d9bebbdb5fdbb836e2072c5a50a92e4b6ab8cd1 Mon Sep 17 00:00:00 2001 From: Joachim Desroches Date: Tue, 16 Mar 2021 13:02:51 +0100 Subject: [PATCH 3/5] Fix remarks on __uacme_account. --- type/__uacme_account/man.rst | 4 +++- type/__uacme_account/manifest | 13 ++++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/type/__uacme_account/man.rst b/type/__uacme_account/man.rst index a2539ff..be5efc6 100644 --- a/type/__uacme_account/man.rst +++ b/type/__uacme_account/man.rst @@ -8,8 +8,10 @@ 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. This type is expected to be used internally, by the `__uacme_obtain`. +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 diff --git a/type/__uacme_account/manifest b/type/__uacme_account/manifest index 3f17d5e..0515853 100644 --- a/type/__uacme_account/manifest +++ b/type/__uacme_account/manifest @@ -1,3 +1,14 @@ #!/bin/sh -__package uacme +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 From f4caa52750f778b05ede0cbb0e58e3f115ee1ff1 Mon Sep 17 00:00:00 2001 From: Joachim Desroches Date: Tue, 16 Mar 2021 13:03:25 +0100 Subject: [PATCH 4/5] Cleanup renew.sh.sh so the output is more elegant. --- type/__uacme_obtain/files/renew.sh.sh | 56 +++++++++++++++++---------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/type/__uacme_obtain/files/renew.sh.sh b/type/__uacme_obtain/files/renew.sh.sh index e3e041c..18bf061 100755 --- a/type/__uacme_obtain/files/renew.sh.sh +++ b/type/__uacme_obtain/files/renew.sh.sh @@ -3,34 +3,50 @@ cat << EOF #!/bin/sh -CERT_SOURCE=$CONFDIR/$MAIN_DOMAIN/cert.pem -KEY_SOURCE=$CONFDIR/private/$MAIN_DOMAIN/key.pem - -export UACME_CHALLENGE_PATH=$CHALLENGEDIR +UACME_CHALLENGE_PATH=${CHALLENGEDIR:?} +export UACME_CHALLENGE_PATH # Issue certificate. -uacme issue -c $CONFDIR -h $HOOKSCRIPT $DISABLE_OCSP $MUST_STABLE $KEYTYPE \ -$DOMAIN -if [ $? -eq 2 ]; then - # 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. +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 - set -e +if [ -n "${KEY_TARGET?}" ] && [ -n "${CERT_TARGET?}" ]; +then +cat << EOF - 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 +# Deploy newly issued certificate. +set -e - $RENEW_HOOK - fi +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 From 73c14825bc478e6a09a8442f3cb2f589e22efa02 Mon Sep 17 00:00:00 2001 From: Joachim Desroches Date: Tue, 16 Mar 2021 13:04:26 +0100 Subject: [PATCH 5/5] Fix remarks on __uacme_obtain. --- type/__uacme_obtain/gencode-remote | 3 ++- type/__uacme_obtain/man.rst | 10 +++++----- type/__uacme_obtain/manifest | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/type/__uacme_obtain/gencode-remote b/type/__uacme_obtain/gencode-remote index 10d0644..739fc92 100644 --- a/type/__uacme_obtain/gencode-remote +++ b/type/__uacme_obtain/gencode-remote @@ -19,5 +19,6 @@ then confdir="$(cat "${__object:?}/parameter/confdir")" fi -# Run renew script 'by hand' - renews will be handled by the cronjob. +# 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 index 4063107..f1db899 100644 --- a/type/__uacme_obtain/man.rst +++ b/type/__uacme_obtain/man.rst @@ -1,5 +1,5 @@ cdist-type__uacme_obtain(7) -======================== +=========================== NAME ---- @@ -8,7 +8,7 @@ cdist-type__uacme_obtain - obtain, renew and deploy Let's Encrypt certificates DESCRIPTION ----------- -This type leverage uacme to issue abd renew Let's Encrypt certificates and +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`. @@ -26,10 +26,10 @@ confdir uacme configuration directory. hookscript - Path to challenge hook program. + Path to the challenge hook program. owner - Owner of installed certificate (e.g. `www-data`), pass as argument to `chown`. + Owner of installed certificate (e.g. `www-data`), passed to `chown`. install-cert-to Installation path of the issued certificate. @@ -44,7 +44,7 @@ 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. diff --git a/type/__uacme_obtain/manifest b/type/__uacme_obtain/manifest index 59ddce8..f41e881 100644 --- a/type/__uacme_obtain/manifest +++ b/type/__uacme_obtain/manifest @@ -15,7 +15,7 @@ case "$os" in ;; esac -CHALLENGEDIR=$default_challengedir +CHALLENGEDIR=${default_challengedir:?} if [ -f "${__object:?}/parameter/challengedir" ]; then CHALLENGEDIR="$(cat "${__object:?}/parameter/challengedir")"