cdist/cdist/conf/type/__letsencrypt_cert/explorer/certificate-data
Evil Ham a696f3cf00 [__letsencrypt_cert] Revamp explorers, add locking.
This would fix #839

Certbot uses locking [1] even for read-only operations and does not properly
use exit codes, which means that sometimes it would print:
"Another instance of Certbot is already running" and exit with success.

However, the previous explorers would take that as the certificate being absent
and would trigger code generation.

The issue was made worse by having many explorers running certbot, so for N
certificates, we'd run certbot N*4 times, potentially "in parallel".

[1]: https://certbot.eff.org/docs/using.html#id5

This patch joins all explorers in one to avoid starting multiple remote python
processes and uses a cdist-specific lock in /tmp/certbot.cdist.lock with a
60 seconds timeout.

It has been tested with certbot 0.31.0 and 0.17 that the:

    from certbot.main import main

trick works. It is somewhat well documented so it can be somewhat relied upon.
2021-05-10 12:10:00 +02:00

78 lines
1.9 KiB
Bash
Executable file

#!/bin/sh -e
certbot_path="$(command -v certbot 2>/dev/null || true)"
# Defaults
certificate_exists="no"
certificate_is_test="no"
if [ -n "${certbot_path}" ]; then
# Find python executable that has access to certbot's module
python_path=$(sed -n '1s/^#! *//p' "${certbot_path}")
# Use a lock for cdist due to certbot not exiting with failure
# or having any flags for concurrent use.
_certbot() {
${python_path} - 2>/dev/null <<EOF
from certbot.main import main
import fcntl
lock_file = "/tmp/certbot.cdist.lock"
timeout=60
with open(lock_file, 'w') as fd:
for i in range(timeout):
try:
# Get exclusive lock
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except:
# Wait if that fails
import time
time.sleep(1)
else:
# Timed out, exit with failure
import sys
sys.exit(1)
# Do list certificates
main(["certificates", "--cert-name", "${__object_id:?}"])
EOF
}
_certificate_exists() {
if grep -q " Certificate Name: ${__object_id:?}$"; then
echo yes
else
echo no
fi
}
_certificate_is_test() {
if grep -q 'INVALID: TEST_CERT'; then
echo yes
else
echo no
fi
}
_certificate_domains() {
grep ' Domains: ' | cut -d ' ' -f 6- | tr ' ' '\n'
}
# Get data about all available certificates
certificates="$(_certbot)"
# Check whether or not the certificate exists
certificate_exists="$(echo "${certificates}" | _certificate_exists)"
# Check whether or not the certificate is for testing
certificate_is_test="$(echo "${certificates}" | _certificate_is_test)"
# Get domains for certificate
certificate_domains="$(echo "${certificates}" | _certificate_domains)"
fi
# Return received data
cat <<EOF
certbot_path:${certbot_path}
certificate_exists:${certificate_exists}
certificate_is_test:${certificate_is_test}
${certificate_domains}
EOF