Add rendered example

This commit is contained in:
Daniel Berteaud 2024-01-05 12:12:47 +01:00
parent 483df60b2c
commit c0705dd940
9 changed files with 309 additions and 0 deletions

View File

@ -0,0 +1,102 @@
job "acme-to-vault" {
type = "service"
datacenters = ["dc1"]
group "acme-to-vault" {
network {
mode = "bridge"
}
ephemeral_disk {
size = 101
}
service {
name = "acme-to-vault"
port = 8787
connect {
sidecar_service {
}
sidecar_task {
resources {
cpu = 50
memory = 64
}
}
}
tags = [
"traefik.enable=true",
# Note : add a fake host in the rule to prevent warnings in Traefik logs
"traefik.http.routers.acme-to-vault.rule=Host(`fake-acme-host`) || PathPrefix(`/.well-known/acme-challenge/`)",
"traefik.http.routers.acme-to-vault.priority=2000",
"traefik.http.routers.acme-to-vault.entrypoints=http,https",
"traefik.http.routers.acme-to-vault.middlewares=rate-limit-std@file,inflight-std@file,security-headers@file,hsts@file,compression@file,csp-relaxed@file"
]
}
task "acme-to-vault" {
driver = "docker"
user = 8787
config {
image = "danielberteaud/acme-to-vault:23.12-1"
readonly_rootfs = true
pids_limit = 20
mount {
type = "tmpfs"
target = "/data"
tmpfs_options {
size = 10000000
}
}
}
vault {
policies = ["acme-to-vault"]
}
env {
}
# Use a template block instead of env {} so we can fetch values from vault
template {
data = <<_EOT
LANG=fr_FR.utf8
LEGO_DISABLE_CNAME_SUPPORT=true
TZ=Europe/Paris
_EOT
destination = "secrets/.env"
perms = 400
env = true
}
template {
data = <<_EOT
VAULT_ADDR=http://localhost:8200
ACME_CRON=22 0 * * *
ACME_KV_ACCOUNT_ROOT=kv/service/acme-to-vault/account
_EOT
destination = "secrets/acm-to-vault.env"
perms = 0400
env = true
}
resources {
cpu = 10
memory = 128
}
}
}
}
# vim: syntax=hcl

View File

@ -0,0 +1,3 @@
Kind = "service-defaults"
Name = "acme-to-vault"
Protocol = "http"

View File

@ -0,0 +1,16 @@
Kind = "service-intentions"
Name = "acme-to-vault"
Sources = [
{
Name = "traefik"
Permissions = [
{
Action = "allow"
HTTP {
Methods = ["GET", "HEAD"]
PathPrefix = "/.well-known/acme-challenge"
}
}
]
}
]

View File

@ -0,0 +1,36 @@
FROM danielberteaud/alpine:24.1-1
MAINTAINER Daniel Berteaud <dbd@ehtrace.com>
ENV ACME_HTTP_PORT=8787 \
ACME_0_CHALLENGE=http-01 \
ACME_0_DNS_PROVIDER=gandiv5 \
ACME_0_EMAIL=no-reply@example.org \
ACME_0_CA=https://acme-staging-v02.api.letsencrypt.org/directory \
ACME_0_KV_CERT_ROOT=kv/service/traefik/certs \
ACME_0_KV_ACCOUNT_ROOT=kv/service/acme-to-vault/account \
VAULT_ADDR=https://vault.service.example.org:8200
RUN set -eu &&\
apk --no-cache upgrade &&\
apk --no-cache add \
curl \
openssl \
ca-certificates \
tini \
lego \
vault \
jq \
supercronic \
libcap-utils \
&&\
mkdir -p /data &&\
# Use very open permissions so we can easily mount a tmpfs over /data \
# A /data/acme subdir will be created with restricted permissions anyway \
chmod 777 /data &&\
setcap -r /usr/sbin/vault
COPY root/ /
EXPOSE ${ACME_HTTP_PORT}
ENTRYPOINT ["tini", "--", "/entrypoint.sh"]
CMD ["acme-to-vault.sh"]

View File

@ -0,0 +1,29 @@
#!/bin/sh
set -u
mkdir -p /data/acme
chmod 700 /data/acme
for IDX in $(printenv | grep -E '^ACME_\d+_EMAIL=' | sed -E 's/ACME_(\d+)_EMAIL.*/\1/'); do
for CA in $(vault kv list -format json ${ACME_KV_ACCOUNT_ROOT} | jq -r ".[]"); do
CA_FQDN=$(echo -n ${CA} | sed -E 's|^(https?://)?([^/]+).*|\2|')
for EMAIL in $(vault kv list -format json ${ACME_KV_ACCOUNT_ROOT}/${CA_FQDN} | jq -r ".[]"); do
echo "Restoring ACME account ${EMAIL} for ${CA_FQDN}"
mkdir -p /data/acme/accounts/${CA_FQDN}/${EMAIL}/keys
vault kv get -field metadata ${ACME_KV_ACCOUNT_ROOT}/${CA_FQDN}/${EMAIL} > /data/acme/accounts/${CA_FQDN}/${EMAIL}/account.json
vault kv get -field key ${ACME_KV_ACCOUNT_ROOT}/${CA_FQDN}/${EMAIL} > /data/acme/accounts/${CA_FQDN}/${EMAIL}/keys/${EMAIL}.key
done
done
KV_CERT_ROOT=$(printenv ACME_${IDX}_KV_CERT_ROOT)
mkdir -p /data/acme/certificates
for CERT in $(vault kv list -format json ${KV_CERT_ROOT} | jq -r ".[]"); do
echo "Restoring cert ${CERT} to /data/acme/certificates/"
vault kv get -field cert ${KV_CERT_ROOT}/${CERT} > /data/acme/certificates/${CERT}.crt
vault kv get -field key ${KV_CERT_ROOT}/${CERT} > /data/acme/certificates/${CERT}.key
vault kv get -field metadata ${KV_CERT_ROOT}/${CERT} > /data/acme/certificates/${CERT}.json
vault kv get -field issuer ${KV_CERT_ROOT}/${CERT} > /data/acme/certificates/${CERT}.issuer.crt
done
done
exit 0

View File

@ -0,0 +1,74 @@
#!/bin/sh
set -u
log_and_run(){
local CMD=$1
echo "${CMD}"
${CMD}
}
main(){
mkdir -p /data/acme
for IDX in $(printenv | grep -E '^ACME_\d+_EMAIL=' | sed -E 's/ACME_(\d+)_EMAIL.*/\1/'); do
export ACME_IDX=${IDX}
export ACME_CA=$(printenv ACME_${IDX}_CA)
EMAIL=$(printenv ACME_${IDX}_EMAIL)
CHALLENGE=$(printenv ACME_${IDX}_CHALLENGE)
LEGO_OPTS="--path /data/acme --server ${ACME_CA} --email ${EMAIL} --accept-tos"
if [ "${CHALLENGE}" = "http-01" ]; then
LEGO_OPTS="${LEGO_OPTS} --http --http.port 127.0.0.1:${ACME_HTTP_PORT}"
elif [ "${CHALLENGE}" = "dns-01" ]; then
LEGO_OPTS="${LEGO_OPTS} --dns $(printenv ACME_${IDX}_DNS_PROVIDER)"
export $(printenv ACME_${IDX}_DNS_KEY_ENV)=$(printenv ACME_${IDX}_DNS_KEY_VALUE)
if printenv ACME_${IDX}_DNS_RESOLVERS; then
LEGO_OPTS="${LEGO_OPTS} --dns.resolvers $(printenv ACME_${IDX}_DNS_RESOLVERS)"
fi
fi
if printenv ACME_${IDX}_KEY_TYPE; then
LEGO_OPTS="${LEGO_OPTS} --key-type $(printenv ACME_${IDX}_KEY_TYPE)"
fi
for CERT in $(printenv | grep -E "^ACME_${IDX}_CERT_\d+" | sed -E 's/([^=]+)=.*/\1/'); do
# Use the first hostname as CN
CN=$(printenv ${CERT} | sed -E 's/([^,]+).*/\1/')
ACTION=run
if [ -e /data/acme/certificates/${CN}.crt -a -e /data/acme/certificates/${CN}.key ]; then
local CONF_DOMAIN=$(printenv ${CERT} | tr "," "\n" | sort | tr "\n" "," | sed -E 's/,$/\n/')
local CUR_DOMAIN=$(openssl x509 -in /data/acme/certificates/${CN}.crt -noout -ext subjectAltName | tail -1 | sed -E 's/\s+DNS://g' | tr "," "\n" | sort | tr "\n" "," | sed -E 's/,$/\n/')
if [ "${CUR_DOMAIN}" = "${CONF_DOMAIN}" ]; then
echo "Certificate for ${CN} already exists, trying to renew (if needed)"
ACTION=renew
else
echo "Certificate for ${CN} changed subjectAltName, trying to get a new cert"
fi
else
echo "Certificate for ${CN} doesn't exist, trying to get it"
fi
log_and_run "lego ${LEGO_OPTS} --domains=$(printenv ${CERT}) ${ACTION} --${ACTION}-hook /usr/local/bin/upload-to-vault.sh"
done
echo "Saving ACME account ${EMAIL} for ${ACME_CA} on vault"
CA=$(echo ${ACME_CA} | sed -E 's|^https?://([^/:]+).*|\1|')
vault kv put ${ACME_KV_ACCOUNT_ROOT}/${CA}/${EMAIL} \
metadata=@/data/acme/accounts/${CA}/${EMAIL}/account.json \
key=@/data/acme/accounts/${CA}/${EMAIL}/keys/${EMAIL}.key
done
}
# Run once at start of the container
main
# If a cron expression is defined, run a cron daemon
if [ -n "${ACME_CRON}" -a -z "${ACME_CRON_RUNNING:-}" ]; then
echo "Running using cron with expression ${ACME_CRON}"
cat <<_EOF > /data/crontab
${ACME_CRON} /usr/local/bin/acme-to-vault.sh
_EOF
export ACME_CRON_RUNNING=true
supercronic /data/crontab
fi

View File

@ -0,0 +1,13 @@
#!/bin/sh
set -eo pipefail
KV_CERT_ROOT=$(printenv ACME_${ACME_IDX}_KV_CERT_ROOT)
echo "Pushing ${LEGO_CERT_DOMAIN} certificate on vault (${VAULT_ADDR}/${KV_CERT_ROOT}/${LEGO_CERT_DOMAIN})"
METADATA=$(echo -n ${LEGO_CERT_PATH} | sed -E 's/\.crt$/.json/')
ISSUER=$(echo -n ${LEGO_CERT_PATH} | sed -E 's/\.crt$/.issuer.crt/')
vault kv put ${KV_CERT_ROOT}/${LEGO_CERT_DOMAIN} \
cert=@${LEGO_CERT_PATH} \
key=@${LEGO_CERT_KEY_PATH} \
metadata=@${METADATA} \
issuer=@${ISSUER}

19
example/prep.d/mv_conf.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/sh
set -eu
if [ "acme-to-vault" != "acme-to-vault" ]; then
for DIR in vault consul nomad; do
if [ -d output/${DIR} ]; then
for FILE in $(find output/${DIR} -name "*acme-to-vault*.hcl" -type f); do
NEW_FILE=$(echo "${FILE}" | sed -E "s/acme-to-vault/acme-to-vault/g")
mv "${FILE}" "${NEW_FILE}"
done
fi
done
fi

View File

@ -0,0 +1,17 @@
path "kv/data/service/+/certs/*" {
capabilities = ["read","create","update"]
}
path "kv/metadata/service/+/certs" {
capabilities = ["list","read"]
}
path "kv/data/service/acme-to-vault/account/*" {
capabilities = ["read","create","update"]
}
path "kv/metadata/service/acme-to-vault/account/*" {
capabilities = ["list","read"]
}
path "kv/data/service/acme-to-vault" {
capabilities = ["read"]
}