Skip to content

Instantly share code, notes, and snippets.

@teamblack-ci
Last active April 4, 2023 20:27
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save teamblack-ci/b853bf2c360f7e528e6b1b0aad9995f6 to your computer and use it in GitHub Desktop.
Save teamblack-ci/b853bf2c360f7e528e6b1b0aad9995f6 to your computer and use it in GitHub Desktop.
Let's Encrypt certificate management using Certbot and Vault
#!/bin/sh
#
# Perform certificate updates in Vault.
set -eo pipefail
if ! vault token lookup > /dev/null; then
echo "Login to Vault first."
exit 1
fi
# Certificate renewal might take some time,
# so renew the token if possible
vault token renew > /dev/null
for domain in $RENEWED_DOMAINS; do
# Wildcard certificates lead to domains like *.example.com
# which should become example.com
target=$domain
case $target in \*\.*)
target=${target#*.}
esac
vault kv put \
"secret/lets-encrypt/certificates/$target" \
"cert=@$RENEWED_LINEAGE/cert.pem" \
"chain=@$RENEWED_LINEAGE/chain.pem" \
"privkey=@$RENEWED_LINEAGE/privkey.pem"
# In case of a multiple-domain certificate, there is no need to re-run this for next domains
exit 0
done
FROM certbot/dns-dnsimple:v0.33.1
ARG VAULT_VERSION=1.1.3
RUN set -eux; \
apk add --no-cache curl gnupg jq tzdata && \
apkArch="$(apk --print-arch)"; \
case "$apkArch" in \
armhf) ARCH='arm' ;; \
aarch64) ARCH='arm64' ;; \
x86_64) ARCH='amd64' ;; \
x86) ARCH='386' ;; \
*) echo >&2 "error: unsupported architecture: $apkArch"; exit 1 ;; \
esac && \
VAULT_GPGKEY=91A6E7F85D05C65630BEF18951852D87348FFC4C; \
found=''; \
for server in \
hkp://p80.pool.sks-keyservers.net:80 \
hkp://keyserver.ubuntu.com:80 \
hkp://pgp.mit.edu:80 \
; do \
echo "Fetching GPG key $VAULT_GPGKEY from $server"; \
gpg --batch --keyserver "$server" --recv-keys "$VAULT_GPGKEY" && found=yes && break; \
done; \
test -z "$found" && echo >&2 "error: failed to fetch GPG key $VAULT_GPGKEY" && exit 1; \
mkdir -p /tmp/vault && \
cd /tmp/vault && \
wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_${ARCH}.zip && \
wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_SHA256SUMS && \
wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_SHA256SUMS.sig && \
gpg --batch --verify vault_${VAULT_VERSION}_SHA256SUMS.sig vault_${VAULT_VERSION}_SHA256SUMS && \
grep vault_${VAULT_VERSION}_linux_${ARCH}.zip vault_${VAULT_VERSION}_SHA256SUMS | sha256sum -c && \
unzip -d /bin vault_${VAULT_VERSION}_linux_amd64.zip && \
cd /tmp && \
rm -rf /tmp/vault && \
gpgconf --kill dirmngr && \
gpgconf --kill gpg-agent && \
apk del gnupg && \
rm -rf /root/.gnupg
COPY entrypoint.sh /usr/local/bin/
COPY initialize.sh /usr/local/bin/
COPY 00-update-vault.sh /etc/letsencrypt/renewal-hooks/deploy/
#!/bin/sh
set -e
if [ "$1" = 'renew' ]; then
initialize.sh
certbot renew
else
exec "$@"
fi
#!/bin/sh
# Initialize the environment for certbot
# Requires vault and jq
set -eo pipefail
if ! vault token lookup > /dev/null; then
echo "Login to Vault first."
exit 1
fi
# Get account path
ACCOUNT_PARENT_PATH=/etc/letsencrypt/accounts/acme-v02.api.letsencrypt.org/directory
ACCOUNT_ID=$(vault kv get --format=json secret/lets-encrypt/account/extra_details | jq -r '.data.data.account_id')
ACCOUNT_PATH="$ACCOUNT_PARENT_PATH/$ACCOUNT_ID"
mkdir -p "$ACCOUNT_PATH"
for i in meta private_key regr; do
vault kv get --format=json "secret/lets-encrypt/account/$i" | \
jq -c '.data.data' \
> "$ACCOUNT_PATH/$i.json"
done
echo "dns_dnsimple_token = $(vault kv get --format=json secret/dnsimple | jq -r '.data.data.token')" > /etc/letsencrypt/dnsimple.ini
chmod 600 /etc/letsencrypt/dnsimple.ini
CERTIFICATES_TO_CHECK=$(vault kv list --format=json secret/lets-encrypt/certificates | jq -r '.[]')
mkdir -p /etc/letsencrypt/renewal
for certificate in $CERTIFICATES_TO_CHECK; do
CERTIFICATE_DATA=$(vault kv get --format=json "secret/beyond/lets-encrypt-certificates/certificates/${certificate}")
mkdir -p "/etc/letsencrypt/archive/${certificate}"
mkdir -p "/etc/letsencrypt/live/${certificate}"
for field in cert chain privkey; do
cat > "/etc/letsencrypt/archive/${certificate}/${field}1.pem" <<EOF
$(echo "${CERTIFICATE_DATA}" | jq -r ".data.data.${field}")
EOF
ln \
-s "../../archive/${certificate}/${field}1.pem" \
"/etc/letsencrypt/live/${certificate}/${field}.pem"
done
cat \
"/etc/letsencrypt/archive/${certificate}/cert1.pem" \
"/etc/letsencrypt/archive/${certificate}/chain1.pem" \
> "/etc/letsencrypt/archive/${certificate}/fullchain1.pem"
ln \
-s "../../archive/${certificate}/fullchain1.pem" \
"/etc/letsencrypt/live/${certificate}/fullchain.pem"
cat > "/etc/letsencrypt/renewal/$certificate.conf" <<EOF
version = 0.33.1
archive_dir = /etc/letsencrypt/archive/$certificate
cert = /etc/letsencrypt/live/$certificate/cert.pem
privkey = /etc/letsencrypt/live/$certificate/privkey.pem
chain = /etc/letsencrypt/live/$certificate/chain.pem
fullchain = /etc/letsencrypt/live/$certificate/fullchain.pem
# Options used in the renewal process
[renewalparams]
authenticator = dns-dnsimple
account = $ACCOUNT_ID
dns_dnsimple_credentials = /etc/letsencrypt/dnsimple.ini
server = https://acme-v02.api.letsencrypt.org/directory
EOF
done
@optiz0r
Copy link

optiz0r commented Nov 2, 2020

@justinh24
Copy link

https://gist.github.com/teamblack-ci/b853bf2c360f7e528e6b1b0aad9995f6#file-dockerfile-L33
I think this should be:

unzip -d /bin vault_${VAULT_VERSION}_linux_${ARCH}.zip && \

?

@ikoutras-epages
Copy link

https://gist.github.com/teamblack-ci/b853bf2c360f7e528e6b1b0aad9995f6#file-00-update-vault-sh-L25
This should be vault kv put rather than vault kv get, right?

That's right!

@ikoutras-epages
Copy link

https://gist.github.com/teamblack-ci/b853bf2c360f7e528e6b1b0aad9995f6#file-dockerfile-L33
I think this should be:

unzip -d /bin vault_${VAULT_VERSION}_linux_${ARCH}.zip && \

?

Another good suggestion. For the time being I have only tested this on amd64, therefore I will leave it so until someone tests this out.

@coltonhughes
Copy link

If anyone finds this and chooses to use it Hashicorp has a new GPG key located here: https://www.hashicorp.com/security

@gchazot
Copy link

gchazot commented Jan 5, 2023

In order to start a shell in the container (or any other command as allowed by entrypoint.sh), I had to add this to Dockerfile.

[...]

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment