Let's Encrypt Zertifikate mit Vault synchronisieren
Die Scripts benötigen einen gültigen Vault Token (siehe Vorbereitung)! Außerdem muss ein KV-Secret Store eingerichtet sein. In diesem Beispiel lautet der Secret-Store certbot.
Vorbereitung
Installation Komponenten
Für das Script werden JQ und Vault (CLI Client) benötigt:
pacman -Syu vault jq
Offizielle Anleitung: https://developer.hashicorp.com/vault/tutorials/getting-started/getting-started-install
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt install vault jq
Vault Login
Durch den Login wird der Token in die Datei /root/.vault-token
geschrieben. Dieser wird von den Scripts und den weiteren Vorbereitungen benötigt.
export VAULT_ADDR=https://vault.domain.com
vault login
KV Store
Vault muss einen KV-Store bereitstellen, dieser lautet in den Scripts certbot. Ist ein anderer Namen gewünscht, müssen alle Pfade angepasst werden.
Nun wird der Secret Store angelegt und die Rotation von maximal 7 Versionen eingestellt, wodurch nur insgesamt 7 Versionen behalten werden und ältere automatisch gelöscht werden.
vault secrets enable -path=certbot kv
vault write certbot/config max_versions=7
Cert Download
Script zum downloaden der Zertifikate
Script auf dem Server anlegen /usr/local/sbin/update_cert.sh
:
#!/bin/bash
# cancel if any error occurs
set -e
if [[ $# -ne 1 ]];then
echo "usage: $0 DOMAIN"
exit 1
fi
DOMAIN="${1}"
CERT_DIR="/etc/nginx/ssl/${DOMAIN}/"
export VAULT_ADDR
# load certificate, chain + key from vault
json=$(vault kv get -format=json certbot/certificates/${DOMAIN})
mkdir -p ${CERT_DIR}
# join cert + chain -> (fullchain)
jq -r '.data.data.cert + .data.data.chain' <(echo $json) > ${CERT_DIR}/fullchain.pem
jq -r '.data.data.privkey' <(echo $json) > ${CERT_DIR}/privkey.pem
# OPTIONAL: reload nginx if config is okay
#nginx -t && nginx -s reload
Berechtigung korrigieren, damit das Script ausführbar ist
chown -R root:root /usr/local/sbin/update_cert.sh
chmod 700 /usr/local/sbin/update_cert.sh
Cronjob / Systemd Timer
Unbedingt den Hostname vault.domain.com anpassen!
Cronjob
crontab -e
0 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh cert.mydomain.com
1 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh mydomain.com
Systemd Unit / Timer
Service Unit /etc/systemd/system/cert_update@.service
[Unit]
Description=Download Certbot files
Requires=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/update_cert.sh %i
Environment="VAULT_ADDR=https://vault.domain.com"
Service Timer /etc/systemd/system/cert_update@.timer
[Unit]
Description=Daily renewal of cert
[Timer]
OnCalendar=01:00:00
Persistent=true
RandomizedDelaySec=60
[Install]
WantedBy=timers.target
Danach kann der Timer für jedes Zertifikat aktiviert werden:
systemctl daemon-reload
systemctl enable --now cert_update@my-domain.com.timer
systemctl enable --now cert_update@cert2.my-domain.com.timer
Cert Upload
Script zum uploaden der Zertifikate
Das Script kann mehrere Zertifikate mit einem Aufruf verarbeiten, diese müssen lediglich als Parameter übergeben werden.
In diesem Beispiel wird allerdings für jedes Zertifikat ein eigener Cronjob / Systemd-Timer angelegt, geht aber auch anders …
Das Script unter /usr/local/sbin/push_certs_to_vault.sh
anlegen:
#!/bin/bash
export VAULT_ADDR
# script supports multiple domains via parameter
DOMAINS="$*"
for domain in ${DOMAINS};do
vault kv put "certbot/certificates/${domain}" "cert=@/etc/letsencrypt/live/${domain}/cert.pem" "chain=@/etc/letsencrypt/live/${domain}/chain.pem" "privkey=@/etc/letsencrypt/live/${domain}/privkey.pem"
done
Berechtigung korrigieren, damit das Scripts ausführbar ist:
chown -R root:root /usr/local/sbin/push_certs_to_vault.sh
chmod 700 /usr/local/sbin/push_certs_to_vault.sh
Cronjob
Unbedingt den Hostname vault.domain.com anpassen!
crontab -e
0 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh cert.mydomain.com
1 2 * * * VAULT_ADDR=https://vault.domain.com /usr/local/sbin/update_cert.sh mydomain.com
Systemd Unit / Timer
Service Unit /etc/systemd/system/push_cert@.service
[Unit]
Description=Upload Certbot files
Requires=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/push_certs_to_vault.sh %i
Environment="VAULT_ADDR=https://vault.domain.com"
Service Timer /etc/systemd/system/push_cert@.timer
[Unit]
Description=Daily upload of certs
[Timer]
OnCalendar=00:00:00
Persistent=true
RandomizedDelaySec=30
[Install]
WantedBy=timers.target
Danach kann der Timer für jedes Zertifikat aktiviert werden:
systemctl daemon-reload
systemctl enable --now push_cert@my-domain.com.timer
systemctl enable --now push_cert@cert2.my-domain.com.timer
Anmerkung
Wieso das Script nicht als Certbot Hook eingebunden wird
Während des Cert Renewals muss die Vault verfügbar sein. Ist dies nicht gegeben, würde der Upload nicht funktionieren und auch nicht nachgeholt.
Deshalb habe ich mich dazu entschieden diese einfach täglich hochzuladen.
In Vault werden inerhalb der Secret Engine rotierte Inhalte gelöscht, sodass der tägliche Upload nicht problematisch ist und man Gefahr läuft die Vault vollzumüllen.
Wenn die Vault zusätzlich mit Basic Auth geschützt ist
Wie in der Anleitung zur Einrichtung beschrieben, muss hier nur die URL ergänzt werden.
Aus VAULT_ADDR=https://vault.domain.com
wird dann VAULT_ADDR=https://USERNAME:PASSWORD@vault.domain.com
.