Add rendered example
This commit is contained in:
parent
483df60b2c
commit
c0705dd940
|
@ -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
|
|
@ -0,0 +1,3 @@
|
|||
Kind = "service-defaults"
|
||||
Name = "acme-to-vault"
|
||||
Protocol = "http"
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -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"]
|
|
@ -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
|
|
@ -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
|
|
@ -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}
|
|
@ -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
|
||||
|
||||
|
||||
|
|
@ -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"]
|
||||
}
|
Loading…
Reference in New Issue