aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLexi Winter <ivy@FreeBSD.org>2025-06-03 10:49:05 +0100
committerLexi Winter <ivy@FreeBSD.org>2025-06-03 10:49:05 +0100
commit99151a2db842a850a2860af3e77532370802ca69 (patch)
treea43f4ff44edd47a267a1a991046b26412dab00c0
parent29d14ef9b7b4c116e3cce031150d848d8e1c14eb (diff)
downloadlfacme-99151a2db842a850a2860af3e77532370802ca69.tar.gz
lfacme-99151a2db842a850a2860af3e77532370802ca69.tar.bz2
make the challenge handler configurable
perhaps one day we'll even support something other than Kerberos!
-rw-r--r--.gitignore1
-rw-r--r--Makefile38
-rw-r--r--acme.conf.54
-rw-r--r--domains.conf.519
-rw-r--r--domains.conf.sample10
-rw-r--r--init.sh54
-rw-r--r--kerberos.sh (renamed from kerberos-challenge.sh)0
-rw-r--r--lfacme-renew.sh38
8 files changed, 130 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore
index 3268211..89c1dbe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
.*.sw?
+/dist
diff --git a/Makefile b/Makefile
index 1e43e45..f9ffa1c 100644
--- a/Makefile
+++ b/Makefile
@@ -9,25 +9,27 @@ MAN5DIR?= ${MANDIR}/man5
MAN8DIR?= ${MANDIR}/man8
HOOKDIR?= ${CONFDIR}/hooks
-LIBMODE?= 0755
-LIB_FILES= init.sh \
- kerberos-challenge.sh
+LIBMODE?= 0644
+LIB= init.sh
+
+CHALLENGEMODE?= 0755
+CHALLENGE= kerberos.sh
BINMODE?= 0755
-BIN_FILES= lfacme-renew.sh \
+BIN= lfacme-renew.sh \
lfacme-setup.sh
CONFMODE?= 0644
-CONF_FILES= acme.conf.sample \
+CONF= acme.conf.sample \
domains.conf.sample
HOOKMODE?= 0755
-HOOK_FILES= example-hook.sh
+HOOK= example-hook.sh
MANMODE?= 0644
-MAN5FILES= acme.conf.5 \
+MAN5= acme.conf.5 \
domains.conf.5
-MAN8FILES= lfacme-renew.8 \
+MAN8= lfacme-renew.8 \
lfacme-setup.8
default: all
@@ -37,26 +39,34 @@ all:
install:
@echo 'create ${LIBDIR}'; install -d ${LIBDIR}; \
- for lib in ${LIB_FILES}; do \
+ for lib in ${LIB}; do \
echo "install ${LIBDIR}/$$lib"; \
install -C -m ${LIBMODE} "$$lib" "${LIBDIR}/$$lib"; \
done; \
\
+ echo 'create ${LIBDIR}/challenge'; install -d ${LIBDIR}/challenge; \
+ for challenge in ${CHALLENGE}; do \
+ basename=$${challenge%*.sh}; \
+ echo "install ${LIBDIR}/challenge/$$basename"; \
+ install -C -m ${CHALLENGEMODE} "$$challenge" \
+ "${LIBDIR}/challenge/$$basename"; \
+ done; \
+ \
echo 'create ${BINDIR}'; install -d ${BINDIR}; \
- for bin in ${BIN_FILES}; do \
+ for bin in ${BIN}; do \
basename=$${bin%*.sh}; \
echo "install ${BINDIR}/$$basename"; \
install -C -m ${BINMODE} "$$bin" "${BINDIR}/$$basename"; \
done; \
\
echo 'create ${CONFDIR}'; install -d ${CONFDIR}; \
- for conf in ${CONF_FILES}; do \
+ for conf in ${CONF}; do \
echo "install ${CONFDIR}/$$conf"; \
install -C -m ${CONFMODE} "$$conf" "${CONFDIR}/$$conf"; \
done; \
\
echo 'create ${HOOKDIR}'; install -d ${HOOKDIR}; \
- for hook in ${HOOK_FILES}; do \
+ for hook in ${HOOK}; do \
basename=$${hook%*.sh}; \
echo "install ${HOOKDIR}/$$basename"; \
install -C -m ${HOOKMODE} "$$hook" "${HOOKDIR}/$$basename"; \
@@ -65,13 +75,13 @@ install:
echo 'create ${MANDIR}'; install -d ${MANDIR}; \
\
echo 'create ${MAN5DIR}'; install -d ${MAN5DIR}; \
- for man in ${MAN5FILES}; do \
+ for man in ${MAN5}; do \
echo "install ${MAN5DIR}/$$man"; \
install -C -m ${MANMODE} "$$man" "${MAN5DIR}/$$man"; \
done; \
\
echo 'create ${MAN8DIR}'; install -d ${MAN8DIR}; \
- for man in ${MAN8FILES}; do \
+ for man in ${MAN8}; do \
echo "install ${MAN8DIR}/$$man"; \
install -C -m ${MANMODE} "$$man" "${MAN8DIR}/$$man"; \
done; \
diff --git a/acme.conf.5 b/acme.conf.5
index f03f777..8643d55 100644
--- a/acme.conf.5
+++ b/acme.conf.5
@@ -31,7 +31,9 @@ The default value is
.It Va ACME_KERBEROS_PRINCIPAL
The Kerberos principal to use when responding to a
.Dq dns-01
-challenge.
+challenge with the
+.Dq kerberos
+challenge handler.
The default value is
.Dq host/$(hostname) .
.El
diff --git a/domains.conf.5 b/domains.conf.5
index 0f937a6..1ad0e03 100644
--- a/domains.conf.5
+++ b/domains.conf.5
@@ -44,6 +44,23 @@ to generate a secp384r1 ECDSA key, or
to generate a 3072-bit RSA key.
If not specified, the default value is
.Dq ec .
+.It Sy challenge Ns Li = Ns Ar filename
+Invoke
+.Ar filename
+to handle ACME challenges for this certificate.
+If
+.Ar filename
+begins with a
+.Sq /
+character, then it is assumed to be an absolute path,
+otherwise it will be searched for in
+.Pa /usr/local/share/lfacme/challenge
+and
+.Pa /usr/local/etc/lfacme/challenge .
+.Pp
+The challenge script is passed to
+.Xr uacme 1 ;
+see the uacme documentation for details on the calling convention.
.It Sy hook Ns Li = Ns Ar filename
Invoke
.Ar filename
@@ -66,7 +83,7 @@ which may be one of the following:
A certificate has been issued or renewed.
.El
.Pp
-The following environment variables will be when running the hook script:
+The following environment variables will be set when running the hook script:
.Bl -tag -width LFACME_CERTFILE
.It Sy LFACME_CERT
The identifier of the certificate, i.e. the first field in
diff --git a/domains.conf.sample b/domains.conf.sample
index 6dace98..41de581 100644
--- a/domains.conf.sample
+++ b/domains.conf.sample
@@ -31,6 +31,16 @@
# If <name> begins with a '/' then it is an absolute path,
# otherwise it is relative to $ACME_HOOKDIR.
# This option may be given multiple times.
+#
+# challenge=<name>
+# Use <name> as the challenge handler. If <name> begins
+# with '/' then it is an absolute path, otherwise it will
+# be searched for in /usr/local/share/lfacme/challenge/
+# then /usr/local/etc/lfacme/challenge/.
+#
+# One challenge script is supplied with lfacme, "kerberos",
+# which uses Kerberized nsupdate(1) to respond to dns-01
+# challenges.
# A certificate name of "*" can be used to set the default options for any
# following certificates. For example, to use RSA (instead of the default
diff --git a/init.sh b/init.sh
index 3c9de04..9674bc1 100644
--- a/init.sh
+++ b/init.sh
@@ -25,6 +25,7 @@ _warn() {
_BASEDIR="/usr/local"
# Where the internal scripts are.
_SHARE="${_BASEDIR}/share/lfacme"
+_CHALLENGE="${_SHARE}/challenge"
# Our configuration directory. This might be overridden by command-line
# arguments.
@@ -71,3 +72,56 @@ _UACME=/usr/local/bin/uacme
_uacme() {
"$_UACME" -a "$ACME_URL" -c "$_UACME_DIR" "$@"
}
+
+# Find a challenge script and make sure it's valid. If the challenge name
+# begins with a '/' it's a full path, otherwise we search $_CHALLENGE and
+# $_CONFDIR/challenge.
+_findchallenge() {
+ local identifier="$1"
+ local challenge="$2"
+ local path=""
+
+ if [ "${challenge#/*}" != "$challenge" ]; then
+ path="${challenge}"
+ elif [ -f "${_CHALLENGE}/${challenge}" ]; then
+ path="${_CHALLENGE}/${challenge}"
+ elif [ -f "${_CONFDIR}/challenge/${challenge}" ]; then
+ path="${_CONFDIR}/challenge/${challenge}"
+ else
+ _error "%s: could not find challenge script '%s'" \
+ "$identifier" "$challenge"
+ return 1
+ fi
+
+ if ! [ -x "$path" ]; then
+ _error "%s: challenge is not executable: %s" \
+ "$identifier" "$path"
+ return 1
+ fi
+
+ echo "$path"
+}
+
+# Find a hook script and make sure it's valid. If the hook name begins with a
+# '/' it's a full path, otherwise it's relative to ACME_HOOKDIR.
+_findhook() {
+ hook="$1"
+
+ if [ "${hook#/*}" = "$hook" ]; then
+ hook="${ACME_HOOKDIR}/$hook"
+ fi
+
+ if ! [ -f "$hook" ]; then
+ _error "%s: hook does not exist: %s" \
+ "$identifier" "$hook"
+ return 1
+ fi
+
+ if ! [ -x "$hook" ]; then
+ _error "%s: hook is not executable: %s" \
+ "$identifier" "$hook"
+ return 1
+ fi
+
+ echo "$hook"
+}
diff --git a/kerberos-challenge.sh b/kerberos.sh
index bd9d9e4..bd9d9e4 100644
--- a/kerberos-challenge.sh
+++ b/kerberos.sh
diff --git a/lfacme-renew.sh b/lfacme-renew.sh
index 0a487d8..787d8da 100644
--- a/lfacme-renew.sh
+++ b/lfacme-renew.sh
@@ -113,6 +113,7 @@ _docert() {
local altnames=""
local hooks=""
local domain=""
+ local challenge=""
# parse arguments for this cert
while ! [ -z "$1" ]; do
@@ -123,6 +124,7 @@ _docert() {
"$identifier" "${1#type=*}"
return 1;;
hook=*) hooks="$hooks ${1#hook=*}";;
+ challenge=*) challenge="${1#challenge=*}";;
*=*) _error "%s: unknown option: %s" \
"$identifier" "$1"
return 1;;
@@ -149,28 +151,28 @@ _docert() {
keytype="ec"
fi
+ # Default challenge is kerberos.
+ if [ -z "$challenge" ]; then
+ challenge="kerberos"
+ fi
+
+ # make sure the challenge is valid.
+ challenge_path="$(_findchallenge "$identifier" "$challenge")"
+ if [ "$?" -ne 0 ]; 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 related to
- # ACME_HOOKDIR.
+ # begins with a '/' it's a full path, otherwise it's relative
+ # to ACME_HOOKDIR.
local _rhooks=""
for hook in $hooks; do
- if [ "${hook#/*}" = "$hook" ]; then
- hook="${ACME_HOOKDIR}/$hook"
- fi
-
- if ! [ -f "$hook" ]; then
- _error "%s: hook does not exist: %s" \
- "$identifier" "$hook"
- return 1
- fi
-
- if ! [ -x "$hook" ]; then
- _error "%s: hook is not executable: %s" \
- "$identifier" "$hook"
+ local _hookpath="$(_findhook "$identifier" "$hook")"
+ if [ "$?" -ne 0 ]; then
return 1
fi
- _rhooks="$_rhooks $hook"
+ _rhooks="$_rhooks $_hookpath"
done
mkdir -p -m0700 "$dir"
@@ -186,8 +188,8 @@ _docert() {
return 1
fi
- _uacme $_uacme_flags \
- -h "${_SHARE}/kerberos-challenge.sh" \
+ _uacme $_uacme_flags \
+ -h "$challenge_path" \
issue "$csrfile"
_ret=$?