From ace580c522836e5287f4603ceea5da41881a4651 Mon Sep 17 00:00:00 2001 From: Lexi Winter Date: Wed, 4 Jun 2025 19:33:21 +0100 Subject: replace lfacme-{setup,renew} with a single lfacme command --- 900.lfacme.sh.in | 4 +- Makefile | 24 +++-- README | 8 +- acme.conf.5.in | 18 ++-- acme.conf.sample.in | 2 +- lfacme-dns.7.in | 1 - lfacme-http.7.in | 1 - lfacme-kerberos.7.in | 1 - lfacme-renew.8.in | 25 ++--- lfacme-renew.sh.in | 272 --------------------------------------------------- lfacme-setup.8.in | 26 ++--- lfacme-setup.sh.in | 34 ------- lfacme-ualpn.7.in | 1 - lfacme.7.in | 76 -------------- lfacme.8.in | 88 +++++++++++++++++ lfacme.sh.in | 44 +++++++++ renew.sh.in | 263 +++++++++++++++++++++++++++++++++++++++++++++++++ setup.sh.in | 25 +++++ 18 files changed, 469 insertions(+), 444 deletions(-) delete mode 100644 lfacme-renew.sh.in delete mode 100644 lfacme-setup.sh.in delete mode 100644 lfacme.7.in create mode 100644 lfacme.8.in create mode 100644 lfacme.sh.in create mode 100644 renew.sh.in create mode 100644 setup.sh.in diff --git a/900.lfacme.sh.in b/900.lfacme.sh.in index f1fae33..599a349 100644 --- a/900.lfacme.sh.in +++ b/900.lfacme.sh.in @@ -13,7 +13,7 @@ export PATH # Exit if lfacme isn't installed but the periodic script was left over # for some reason. -if ! [ -x __BINDIR__/lfacme-renew ]; then +if ! [ -x __BINDIR__/lfacme ]; then exit 0 fi @@ -21,7 +21,7 @@ case "$daily_lfacme_enable" in [Yy][Ee][Ss]) printf 'Renewing ACME certificates with lfacme:\n' - __BINDIR__/lfacme-renew + __BINDIR__/lfacme renew ;; *) diff --git a/Makefile b/Makefile index bcf080d..bade4b8 100644 --- a/Makefile +++ b/Makefile @@ -29,8 +29,9 @@ CHALLENGE= dns.sh \ ualpn.sh BINMODE?= 0755 -BIN= lfacme-renew.sh \ - lfacme-setup.sh +BIN= lfacme.sh +CMD= renew.sh \ + setup.sh CONFMODE?= 0644 CONF= acme.conf.sample \ @@ -42,12 +43,12 @@ HOOK= example-hook.sh MANMODE?= 0644 MAN5= acme.conf.5 \ domains.conf.5 -MAN7= lfacme.7 \ - lfacme-dns.7 \ +MAN7= lfacme-dns.7 \ lfacme-http.7 \ lfacme-kerberos.7 \ lfacme-ualpn.7 -MAN8= lfacme-renew.8 \ +MAN8= lfacme.8 \ + lfacme-renew.8 \ lfacme-setup.8 PERIODICMODE?= 0755 @@ -65,10 +66,10 @@ REPLACE= sed -e 's,__PREFIX__,${PREFIX},g' \ default: all -all: ${MAN5} ${MAN7} ${MAN8} ${LIB} ${BIN} ${CHALLENGE} ${HOOK} ${PERIODIC} ${CONF} +all: ${MAN5} ${MAN7} ${MAN8} ${LIB} ${BIN} ${CMD} ${CHALLENGE} ${HOOK} ${PERIODIC} ${CONF} clean: - rm -f ${MAN5} ${MAN7} ${MAN8} ${LIB} ${BIN} + rm -f ${MAN5} ${MAN7} ${MAN8} ${LIB} ${BIN} ${CMD} rm -f ${HOOK} ${CHALLENGE} ${PERIODIC} ${CONF} .sh.in.sh: @@ -86,7 +87,7 @@ clean: .8.in.8: ${REPLACE} <$< >$@ -install: install-lib install-bin install-conf install-hook install-man install-periodic +install: install-lib install-bin install-cmd install-conf install-hook install-man install-periodic install-lib: all @echo 'create ${DESTDIR}${LIBDIR}'; install -d ${DESTDIR}${LIBDIR} @@ -110,6 +111,13 @@ install-bin: all install -C -m ${BINMODE} "$$bin" "${DESTDIR}${BINDIR}/$$basename"; \ done +install-cmd: all + @echo 'create ${DESTDIR}${LIBDIR}/command'; install -d ${DESTDIR}${LIBDIR}/command + @for cmd in ${CMD}; do \ + echo "install ${DESTDIR}${LIBDIR}/command/$$cmd"; \ + install -C -m ${BINMODE} "$$cmd" "${DESTDIR}${LIBDIR}/command/$$cmd"; \ + done + install-conf: all @echo 'create ${DESTDIR}${CONFDIR}'; install -d ${DESTDIR}${CONFDIR}; @for conf in ${CONF}; do \ diff --git a/README b/README index 6183550..620c48e 100644 --- a/README +++ b/README @@ -46,13 +46,13 @@ usage ----- + create the config files (see below) -+ run "lfacme-setup" to create an ACME account -+ run "lfacme-renew" to issue certificates -+ put "lfacme-renew" in cron if you want to renew certificates automatically. ++ run "lfacme setup" to create an ACME account ++ run "lfacme renew" to issue certificates ++ put "lfacme renew" in cron if you want to renew certificates automatically. it's fine to run this once a day, since it won't renew certificates unless they're going to expire soon. -the lfacme documentation is provided as online manual pages; see lfacme(7) +the lfacme documentation is provided as online manual pages; see lfacme(8) to begin with. known issues diff --git a/acme.conf.5.in b/acme.conf.5.in index 2210cad..d968c74 100644 --- a/acme.conf.5.in +++ b/acme.conf.5.in @@ -18,13 +18,12 @@ variable assignment, i.e. .Dq Ar option Ns = Ns Ar value . .Pp Alternatively, options may be set as environment variables prior to running -.Nm lfacme -utilities. -If all required options are set in the environment, then creating the +.Nm lfacme . +If all required options are set in the environment, then the .Nm file is not required. .Pp -The following configuration variables are supported: +The following configuration options are supported: .Bl -tag -width indent .It Va ACME_URL (Required.) @@ -32,13 +31,13 @@ The URL of the ACME server. .It Va ACME_DATADIR The path to the runtime data directory, where the ACME account key and any issued certificates will be stored. -The default value is +The default path is .Pa /var/db/lfacme . .It Va ACME_HOOKDIR The path to a directory containing hooks to invoke when issuing certificates (see .Xr domains.conf 5 ) . -The default value is +The default path is .Pa __CONFDIR__/hooks . .El .Pp @@ -46,5 +45,8 @@ Additional configuration variables may be used by the ACME validation hooks; refer to the manual page for each hook for more details. .Sh SEE ALSO .Xr domains.conf 5 , -.Xr lfacme-renew 8 , -.Xr lfacme-setup 8 +.Xr lfacme 8 , +.Xr lfacme-dns 7 , +.Xr lfacme-http 7 , +.Xr lfacme-kerberos 7 , +.Xr lfacme-ualpn 7 diff --git a/acme.conf.sample.in b/acme.conf.sample.in index d6cea21..3bf5df8 100644 --- a/acme.conf.sample.in +++ b/acme.conf.sample.in @@ -46,7 +46,7 @@ # When using the "http" challenge handler, this is the directory which contains # ACME challenges. This must be served at /.well-known/acme-challenge on any # domain using http validation. -# No default, you must set this if you use the "http" handler. +# No default; you must set this if you use the "http" handler. #ACME_HTTP_CHALLENGE_DIR="/var/www/acme-challenge" diff --git a/lfacme-dns.7.in b/lfacme-dns.7.in index 5e46215..441931f 100644 --- a/lfacme-dns.7.in +++ b/lfacme-dns.7.in @@ -56,6 +56,5 @@ to authenticate the DNS update. .Sh SEE ALSO .Xr acme.conf 5 , .Xr domains.conf 5 , -.Xr lfacme 7 , .Xr lfacme-renew 8 , .Xr nsupdate 1 diff --git a/lfacme-http.7.in b/lfacme-http.7.in index 25b7aac..4e853d5 100644 --- a/lfacme-http.7.in +++ b/lfacme-http.7.in @@ -53,5 +53,4 @@ on the web server for the domain to be validated. .Sh SEE ALSO .Xr acme.conf 5 , .Xr domains.conf 5 , -.Xr lfacme 7 , .Xr lfacme-renew 8 diff --git a/lfacme-kerberos.7.in b/lfacme-kerberos.7.in index 8b9f85e..a2bd73b 100644 --- a/lfacme-kerberos.7.in +++ b/lfacme-kerberos.7.in @@ -96,6 +96,5 @@ DNS server) is left as an exercise for the reader. .Xr acme.conf 5 , .Xr domains.conf 5 , .Xr kinit 1 , -.Xr lfacme 7 , .Xr lfacme-renew 8 , .Xr nsupdate 1 diff --git a/lfacme-renew.8.in b/lfacme-renew.8.in index 6e4ebd9..e2f7904 100644 --- a/lfacme-renew.8.in +++ b/lfacme-renew.8.in @@ -3,16 +3,17 @@ .Dt LFACME-RENEW 8 .Os .Sh NAME -.Nm lfacme-renew +.Nm lfacme renew .Nd issue or renew ACME certificates .Sh SYNOPSIS -.Nm -.Op Fl fv -.Op Fl c Ar confdir +.Nm lfacme +.Op opts +.Cm renew +.Op Fl f .Sh DESCRIPTION The .Nm -utility examines the ACME certificates configured in +command examines the ACME certificates configured in .Xr domains.conf 5 . If a certificate was previously issued and is still valid for longer than 30 days, it will be ignored. @@ -26,18 +27,8 @@ before running .Pp The follow options are accepted: .Bl -tag -width indent -.It Fl c Ar confdir -Use -.Ar confdir -as the configuration directory instead of the default -.Pa __CONFDIR__ . .It Fl f -Force renewal of certificates, even if they're not going to expire soon. -.It Fl v -Produce more output when running. -This also passes the -.Fl v -option to uacme. +Force renewal of certificates, even if the normal renewal date hasn't arrived. .El .Sh ENVIRONMENT Refer to @@ -46,5 +37,5 @@ for a list of environment variables which affect the operation of .Nm . .Sh SEE ALSO .Xr domains.conf 5 , -.Xr lfacme 7 , +.Xr lfacme 8 , .Xr lfacme-setup 8 diff --git a/lfacme-renew.sh.in b/lfacme-renew.sh.in deleted file mode 100644 index 34d2c01..0000000 --- a/lfacme-renew.sh.in +++ /dev/null @@ -1,272 +0,0 @@ -#! /bin/sh -# This source code is released into the public domain. - -# Parse command-line arguments. -args=$(getopt c:fv $*) -if [ $? -ne 0 ]; then - exit 1 -fi -set -- $args - -# ARI is broken due to https://github.com/ndilieto/uacme/issues/91 -_uacme_flags="--no-ari" - -while :; do - case "$1" in - -c) - _CONFDIR="$2" - shift; shift;; - -f) - _uacme_flags="$_uacme_flags -f" - shift;; - -v) - LFACME_VERBOSE=1 - shift;; - --) - shift; break;; - esac -done - -# Initialise. -. __LIBDIR__/init.sh - -if ! [ -f "$_UACME_DIR/private/key.pem" ]; then - _fatal "run lfacme-setup first" -fi - -if ! [ -f "$_DOMAINS" ]; then - _fatal "missing $_DOMAINS" -fi - -# Create a key if it doesn't already exist. It would be better to always -# create a new key here, but currently uacme doesn't have a way to tell us -# that we need to do that. -_make_key() { - local keytype="$1" - local keyfile="$2" - - if [ -s "$keyfile" ]; then - return 0 - fi - - local _umask=$(umask) - umask 077 - - case $keytype in - ec) openssl ecparam -name secp384r1 -genkey -noout -out "$keyfile";; - rsa) openssl genrsa -out "$keyfile" 3072;; - *) _error "%s: unknown key type %s?" "$keyfile" "$keytype" - return 1;; - esac - - local _ret=$? - umask $_umask - - return $_ret -} - -# Create a new CSR for a domain. -_make_csr() { - local csrfile="$1" - local keyfile="$2" - local domain="$3" - local altnames="$4" - local csrconf="${csrfile}.cnf" - - cat >"$csrconf" <>"$csrconf" 'DNS.%d = %s\n' "$_i" "$altname" - _i=$((_i + 1)) - done - - # Generate the CSR - openssl req -new -key "$keyfile" -out "$csrfile" -config "$csrconf" - return $? -} - -# Process a single cert. -_docert() { - local identifier="$1"; shift - - _verbose "checking certificate '%s'" "$identifier" - - # uacme creates the cert name by stripping the extension from the - # CSR filename, so the basename has to match the identifier. - local dir="${_UACME_DIR}/${identifier}" - local keyfile="${dir}/${identifier}-key.pem" - local csrfile="${dir}/${identifier}.csr" - local certfile="${dir}/${identifier}-cert.pem" - - # these can be overridden by args - local keytype="" - local altnames="" - local hooks="" - local domain="" - local challenge="" - - # parse arguments for this cert - while ! [ -z "$1" ]; do - case "$1" in - type=rsa) - keytype=rsa - ;; - type=ec) - keytype=ec - ;; - type=*) - _error "%s: unknown key type: %s" \ - "$identifier" "${1#type=*}" - return 1 - ;; - hook=*) - hooks="$hooks ${1#hook=*}" - ;; - challenge=*) - challenge="${1#challenge=*}" - ;; - *=*) - _error "%s: unknown option: %s" "$identifier" "$1" - return 1 - ;; - *.*) - altnames="$altnames $1" - # Take the domain from the first altname. - if [ -z "$domain" ]; then - domain="$1" - fi - ;; - *) - _error "%s: unknown option: %s" "$identifier" "$1" - return 1 - ;; - esac - shift - done - - # If no altnames were given, the identifier is the domain. - if [ -z "$domain" ]; then - domain="$identifier" - fi - - # Default key type is ec. - if [ -z "$keytype" ]; then - keytype="ec" - fi - - # Default challenge is http. - if [ -z "$challenge" ]; then - challenge="http" - fi - - # make sure the challenge is valid. - challenge_path="$(_findchallenge "$identifier" "$challenge")" - if [ -z "$challenge_path" ]; then - return 1 - fi - - # make sure all the hook scripts are valid. if the hook name - # begins with a '/' it's a full path, otherwise it's relative - # to ACME_HOOKDIR. - local _rhooks="" - for hook in $hooks; do - local _hookpath="$(_findhook "$identifier" "$hook")" - if [ -z "$_hookpath" ]; then - return 1 - fi - - _rhooks="$_rhooks $_hookpath" - done - - mkdir -p -m0700 "$dir" - - if ! _make_key "$keytype" "$keyfile"; then - _error "%s: could not create a new private key" "$identifier" - return 1 - fi - - if ! _make_csr "$csrfile" "$keyfile" "$domain" "$altnames"; then - _error "%s: could not create the certificate signing request" \ - "$identifier" - return 1 - fi - - _uacme $_uacme_flags \ - -h "$challenge_path" \ - issue "$csrfile" - _ret=$? - - # exit 1 means the cert wasn't reissued - if [ "$_ret" -eq 1 ]; then - return 0 - fi - - # exit 2 means an actual error - if [ "$_ret" -eq 2 ]; then - _error "%s: failed to issue certificate" "$identifier" - return 1 - fi - - # any other non-zero exit code is unexpected - if [ "$_ret" -ne 0 ]; then - _error "%s: unexpected exit code from uacme: %d" \ - "$identifier" "$_ret" - return 1 - fi - - # otherwise, exit code is 0 which means we (re)issued the cert, - # so run the hooks. - for hook in $_rhooks; do - _verbose "running hook: %s" "$hook" - env "LFACME_CONFDIR=${_CONFDIR}" \ - "LFACME_VERBOSE=${LFACME_VERBOSE}" \ - "LFACME_CERT=${identifier}" \ - "LFACME_KEYFILE=${keyfile}" \ - "LFACME_CERTFILE=${certfile}" \ - "$hook" newcert - if [ "$?" -ne 0 ]; then - _warn "%s: hook script '%s' failed" \ - "$identifier" "$hook" - fi - # should we do anything if the hook failed? - done - - return $? -} - -cat "$_DOMAINS" \ -| egrep -v '^(#|[[:space:]]*$)' \ -| ( - _default_args="" - _exit=0 - - while read identifier args; do - if [ "$identifier" = "*" ]; then - _default_args="$args" - continue - fi - - if ! _docert "$identifier" $_default_args $args; then - _exit=1 - fi - done - - exit $_exit -) - -exit $? diff --git a/lfacme-setup.8.in b/lfacme-setup.8.in index 893c0b2..afd50c9 100644 --- a/lfacme-setup.8.in +++ b/lfacme-setup.8.in @@ -3,16 +3,17 @@ .Dt LFACME-SETUP 8 .Os .Sh NAME -.Nm lfacme-setup +.Nm lfacme setup .Nd create a new ACME account .Sh SYNOPSIS -.Nm -.Op Fl vy -.Op Fl c Ar confdir +.Nm lfacme +.Op opts +.Cm setup +.Op Fl y .Sh DESCRIPTION The .Nm -utility will register a new account with the ACME provider configured in +command will register a new account with the ACME provider configured in .Xr acme.conf 5 . If the provider requires accepting terms of service to create an account, the ToS URL will be printed and @@ -21,19 +22,8 @@ will prompt the user to accept them. .Pp The follow options are accepted: .Bl -tag -width indent -.It Fl c Ar confdir -Use -.Ar confdir -as the configuration directory instead of the default -.Pa __CONFDIR__ . -.It Fl v -Produce more output when running. -This also passes the -.Fl v -option to uacme. .It Fl y -If the ACME provider requires accepting terms of service, -accept the provided terms automatically. +Accept any required terms of service automatically. .El .Sh ENVIRONMENT Refer to @@ -42,4 +32,4 @@ for a list of environment variables which affect the operation of .Nm . .Sh SEE ALSO .Xr acme.conf 5 , -.Xr lfacme 7 +.Xr lfacme 8 diff --git a/lfacme-setup.sh.in b/lfacme-setup.sh.in deleted file mode 100644 index 4b60263..0000000 --- a/lfacme-setup.sh.in +++ /dev/null @@ -1,34 +0,0 @@ -#! /bin/sh -# This source code is released into the public domain. - -# Parse command-line arguments. -args=$(getopt c:vy $*) -if [ $? -ne 0 ]; then - exit 1 -fi -set -- $args - -_uacme_flags="" - -while :; do - case "$1" in - -c) - _CONFDIR="$2" - shift; shift;; - -v) - LFACME_VERBOSE=1 - shift;; - -y) - _uacme_flags="$_uacme_flags $1" - shift;; - --) - shift; break;; - esac -done - -# Initialise. -. __LIBDIR__/init.sh - -# Run uacme. -mkdir -p "$_UACME_DIR" -_uacme $_uacme_flags new diff --git a/lfacme-ualpn.7.in b/lfacme-ualpn.7.in index dceaa8d..80efd2c 100644 --- a/lfacme-ualpn.7.in +++ b/lfacme-ualpn.7.in @@ -36,6 +36,5 @@ daemon must be configured and running for this challenge handler to work. .Sh SEE ALSO .Xr acme.conf 5 , .Xr domains.conf 5 , -.Xr lfacme 7 , .Xr lfacme-renew 8 , .Xr ualpn 1 diff --git a/lfacme.7.in b/lfacme.7.in deleted file mode 100644 index b6b9060..0000000 --- a/lfacme.7.in +++ /dev/null @@ -1,76 +0,0 @@ -.\" This source code is released into the public domain. -.Dd June 4, 2025 -.Dt LFACME 7 -.Os -.Sh NAME -.Nm lfacme -.Nd issue, renew and manage ACME certificates -.Sh SYNOPSIS -.Nm lfacme-setup -.Op opts -.Nm lfacme-renew -.Op opts -.Sh DESCRIPTION -The -.Nm -software package supports management of TLS certificates using an ACME server. -Certificates can be automatically issued and renewed, and a hook system allows -software using those certificates to be automatically (re)configured. -.Pp -Prior to using -.Nm , -two configuration files must be created: -.Pa __CONFDIR__/acme.conf -and -.Pa __CONFDIR__/domains.conf . -Samples of both files are provided in -.Pa __CONFDIR__ . -Refer to -.Xr acme.conf 5 -and -.Xr domains.conf 5 -for more detailed documentation on these files. -.Pp -To perform initial setup, run -.Xr lfacme-setup 8 . -This will register an account on the ACME server, and create any required -local data. -Running -.Xr lfacme-setup 8 -will not issue any certificates. -.Pp -To issue or renew certificates, run -.Xr lfacme-renew 8 . -This will examine the certificates configured in -.Xr domains.conf 5 ; -new certificates will be issued, while existing certificates will be renewed -if necessary. -To ensure certificates are automatically renewed when required, -.Xr lfacme-renew 8 -should be run regularly, e.g. using -.Xr cron 8 . -.Sh ENVIRONMENT -The following environment variables affect the executation of the -.Nm -utilities: -.Bl -tag -width LFACME_VERBOSE -.It Ev LFACME_CONFDIR -Override the default configuration directory. -This is equivalent to specifying the -.Fl c -flag on the command line. -.It Ev LFACME_VERBOSE -If set to a non-empty string, run in verbose mode. -This is equivalent to specifying the -.Fl v -flag on the command line. -.El -.Pp -Additionally, any configuration settings described in -.Xr acme.conf 5 -may also be set in the environment. -.Sh SEE ALSO -.Xr acme.conf 5 , -.Xr domains.conf 5 , -.Xr lfacme-renew 8 , -.Xr lfacme-setup 8 diff --git a/lfacme.8.in b/lfacme.8.in new file mode 100644 index 0000000..7dab735 --- /dev/null +++ b/lfacme.8.in @@ -0,0 +1,88 @@ +.\" This source code is released into the public domain. +.Dd June 4, 2025 +.Dt LFACME 7 +.Os +.Sh NAME +.Nm lfacme +.Nd issue, renew and manage ACME certificates +.Sh SYNOPSIS +.Nm lfacme +.Op Fl v +.Op Fl c Ar confdir +.Cm setup +.Op Fl y +.Nm lfacme +.Op Fl v +.Op Fl c Ar confdir +.Cm renew +.Op Fl f +.Sh DESCRIPTION +The +.Nm +command supports automated management of TLS certificates using an ACME server. +Certificates can be automatically issued and renewed, and a hook system allows +software using those certificates to be automatically (re)configured with the +new certificate. +.Pp +Prior to using +.Nm , +two configuration files must be created: +.Pa __CONFDIR__/acme.conf +and +.Pa __CONFDIR__/domains.conf . +Samples of both files are provided in +.Pa __CONFDIR__ . +Refer to +.Xr acme.conf 5 +and +.Xr domains.conf 5 +for more detailed documentation on these files. +.Pp +The following options are supported: +.Bl -tag -width Fl c Ar confdir +.It Fl c Ar confdir +Load the configuration from +.Ar confdir +instead of the default +.Pa __CONFDIR__ . +.It Fl v +Print more detailed output while running. +.El +.Pp +The following commands are supported: +.Bl -tag -width Cm setup +.It Cm setup +Create a new account on the ACME server. +This must be run prior to issuing any certificates. +See +.Xr lfacme-setup 8 . +.It Cm renew +Issue or renew any certificates based on the +.Xr domains.conf 5 +configuration file. +See +.Xr lfacme-renew 8 . +.Sh ENVIRONMENT +The following environment variables affect the executation of +.Nm : +.Bl -tag -width LFACME_VERBOSE +.It Ev LFACME_CONFDIR +Override the default configuration directory. +This is equivalent to specifying the +.Fl c +flag on the command line. +.It Ev LFACME_VERBOSE +If set to a non-empty string, run in verbose mode. +This is equivalent to specifying the +.Fl v +flag on the command line. +.El +.Pp +Additionally, any configuration settings described in +.Xr acme.conf 5 +may also be set in the environment. +.Sh SEE ALSO +.Xr acme.conf 5 , +.Xr domains.conf 5 , +.Xr lfacme-renew 8 , +.Xr lfacme-setup 8 diff --git a/lfacme.sh.in b/lfacme.sh.in new file mode 100644 index 0000000..7047097 --- /dev/null +++ b/lfacme.sh.in @@ -0,0 +1,44 @@ +#! /bin/sh +# This source code is released into the public domain. + +# Parse command-line arguments. +args=$(getopt c:v $*) +if [ $? -ne 0 ]; then + exit 1 +fi +set -- $args + +_uacme_flags="" + +while :; do + case "$1" in + -c) + _CONFDIR="$2" + shift; shift;; + -v) + LFACME_VERBOSE=1 + shift;; + -y) + _uacme_flags="$_uacme_flags $1" + shift;; + --) + shift; break;; + esac +done + +# Initialise. +. __LIBDIR__/init.sh + +# Run the command. + +_command="$1"; shift +if [ -z "$command" ]; then + _fatal "missing command" +fi + +_cscript="__LIBDIR__/command/${_command}.sh" +if ! [ -x "$_cscript" ]; then + _fatal "unknown command: %s" "$_command" +fi + +. "$_cscript" diff --git a/renew.sh.in b/renew.sh.in new file mode 100644 index 0000000..689f992 --- /dev/null +++ b/renew.sh.in @@ -0,0 +1,263 @@ +#! /bin/sh +# This source code is released into the public domain. + +# Parse command-line arguments. +args=$(getopt f $*) +if [ $? -ne 0 ]; then + exit 1 +fi +set -- $args + +# ARI is broken due to https://github.com/ndilieto/uacme/issues/91 +_uacme_flags="--no-ari" + +while :; do + case "$1" in + -f) + _uacme_flags="$_uacme_flags -f" + shift;; + --) + shift; break;; + esac +done + +if ! [ -f "$_UACME_DIR/private/key.pem" ]; then + _fatal "run 'lfacme setup' first" +fi + +if ! [ -f "$_DOMAINS" ]; then + _fatal "missing $_DOMAINS" +fi + +# Create a key if it doesn't already exist. It would be better to always +# create a new key here, but currently uacme doesn't have a way to tell us +# that we need to do that. +_make_key() { + local keytype="$1" + local keyfile="$2" + + if [ -s "$keyfile" ]; then + return 0 + fi + + local _umask=$(umask) + umask 077 + + case $keytype in + ec) openssl ecparam -name secp384r1 -genkey -noout -out "$keyfile";; + rsa) openssl genrsa -out "$keyfile" 3072;; + *) _error "%s: unknown key type %s?" "$keyfile" "$keytype" + return 1;; + esac + + local _ret=$? + umask $_umask + + return $_ret +} + +# Create a new CSR for a domain. +_make_csr() { + local csrfile="$1" + local keyfile="$2" + local domain="$3" + local altnames="$4" + local csrconf="${csrfile}.cnf" + + cat >"$csrconf" <>"$csrconf" 'DNS.%d = %s\n' "$_i" "$altname" + _i=$((_i + 1)) + done + + # Generate the CSR + openssl req -new -key "$keyfile" -out "$csrfile" -config "$csrconf" + return $? +} + +# Process a single cert. +_docert() { + local identifier="$1"; shift + + _verbose "checking certificate '%s'" "$identifier" + + # uacme creates the cert name by stripping the extension from the + # CSR filename, so the basename has to match the identifier. + local dir="${_UACME_DIR}/${identifier}" + local keyfile="${dir}/${identifier}-key.pem" + local csrfile="${dir}/${identifier}.csr" + local certfile="${dir}/${identifier}-cert.pem" + + # these can be overridden by args + local keytype="" + local altnames="" + local hooks="" + local domain="" + local challenge="" + + # parse arguments for this cert + while ! [ -z "$1" ]; do + case "$1" in + type=rsa) + keytype=rsa + ;; + type=ec) + keytype=ec + ;; + type=*) + _error "%s: unknown key type: %s" \ + "$identifier" "${1#type=*}" + return 1 + ;; + hook=*) + hooks="$hooks ${1#hook=*}" + ;; + challenge=*) + challenge="${1#challenge=*}" + ;; + *=*) + _error "%s: unknown option: %s" "$identifier" "$1" + return 1 + ;; + *.*) + altnames="$altnames $1" + # Take the domain from the first altname. + if [ -z "$domain" ]; then + domain="$1" + fi + ;; + *) + _error "%s: unknown option: %s" "$identifier" "$1" + return 1 + ;; + esac + shift + done + + # If no altnames were given, the identifier is the domain. + if [ -z "$domain" ]; then + domain="$identifier" + fi + + # Default key type is ec. + if [ -z "$keytype" ]; then + keytype="ec" + fi + + # Default challenge is http. + if [ -z "$challenge" ]; then + challenge="http" + fi + + # make sure the challenge is valid. + challenge_path="$(_findchallenge "$identifier" "$challenge")" + if [ -z "$challenge_path" ]; then + return 1 + fi + + # make sure all the hook scripts are valid. if the hook name + # begins with a '/' it's a full path, otherwise it's relative + # to ACME_HOOKDIR. + local _rhooks="" + for hook in $hooks; do + local _hookpath="$(_findhook "$identifier" "$hook")" + if [ -z "$_hookpath" ]; then + return 1 + fi + + _rhooks="$_rhooks $_hookpath" + done + + mkdir -p -m0700 "$dir" + + if ! _make_key "$keytype" "$keyfile"; then + _error "%s: could not create a new private key" "$identifier" + return 1 + fi + + if ! _make_csr "$csrfile" "$keyfile" "$domain" "$altnames"; then + _error "%s: could not create the certificate signing request" \ + "$identifier" + return 1 + fi + + _uacme $_uacme_flags \ + -h "$challenge_path" \ + issue "$csrfile" + _ret=$? + + # exit 1 means the cert wasn't reissued + if [ "$_ret" -eq 1 ]; then + return 0 + fi + + # exit 2 means an actual error + if [ "$_ret" -eq 2 ]; then + _error "%s: failed to issue certificate" "$identifier" + return 1 + fi + + # any other non-zero exit code is unexpected + if [ "$_ret" -ne 0 ]; then + _error "%s: unexpected exit code from uacme: %d" \ + "$identifier" "$_ret" + return 1 + fi + + # otherwise, exit code is 0 which means we (re)issued the cert, + # so run the hooks. + for hook in $_rhooks; do + _verbose "running hook: %s" "$hook" + env "LFACME_CONFDIR=${_CONFDIR}" \ + "LFACME_VERBOSE=${LFACME_VERBOSE}" \ + "LFACME_CERT=${identifier}" \ + "LFACME_KEYFILE=${keyfile}" \ + "LFACME_CERTFILE=${certfile}" \ + "$hook" newcert + if [ "$?" -ne 0 ]; then + _warn "%s: hook script '%s' failed" \ + "$identifier" "$hook" + fi + # should we do anything if the hook failed? + done + + return $? +} + +cat "$_DOMAINS" \ +| egrep -v '^(#|[[:space:]]*$)' \ +| ( + _default_args="" + _exit=0 + + while read identifier args; do + if [ "$identifier" = "*" ]; then + _default_args="$args" + continue + fi + + if ! _docert "$identifier" $_default_args $args; then + _exit=1 + fi + done + + exit $_exit +) + +exit $? diff --git a/setup.sh.in b/setup.sh.in new file mode 100644 index 0000000..a37a691 --- /dev/null +++ b/setup.sh.in @@ -0,0 +1,25 @@ +#! /bin/sh +# This source code is released into the public domain. + +# Parse command-line arguments. +args=$(getopt y $*) +if [ $? -ne 0 ]; then + exit 1 +fi +set -- $args + +_uacme_flags="" + +while :; do + case "$1" in + -y) + _uacme_flags="$_uacme_flags $1" + shift;; + --) + shift; break;; + esac +done + +# Run uacme. +mkdir -p "$_UACME_DIR" +_uacme $_uacme_flags new -- cgit v1.2.3