From 061cf5bc6ce164b31c18bad942c1469a0386e088 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 20 Feb 2023 15:50:20 +0000 Subject: [PATCH] Add certificate management via Mythic Beasts DNSAPI. --- pillar/secrets/dnsapi.sls.sample | 3 + pillar/top.sls | 4 + states/certificates/dehydrated/conf.d/hook.sh | 3 + states/certificates/dehydrated/conf.d/mail.sh | 1 + states/certificates/dehydrated/cron.daily | 2 + .../dehydrated-mythic-dns01/challenge.sh | 2 + .../clean-challenge/mythic-dns01 | 10 +++ .../common/mythic-dns01 | 73 +++++++++++++++++++ .../dehydrated-mythic-dns01.sh | 20 +++++ .../deploy-challenge/mythic-dns01 | 14 ++++ .../certificates/dehydrated/dnsapi.config.txt | 23 ++++++ states/certificates/dehydrated/domains.txt | 8 ++ states/certificates/dehydrated/logrotate | 9 +++ states/certificates/init.sls | 48 ++++++++++++ states/top.sls | 1 + 15 files changed, 221 insertions(+) create mode 100644 pillar/secrets/dnsapi.sls.sample create mode 100644 pillar/top.sls create mode 100644 states/certificates/dehydrated/conf.d/hook.sh create mode 100644 states/certificates/dehydrated/conf.d/mail.sh create mode 100644 states/certificates/dehydrated/cron.daily create mode 100644 states/certificates/dehydrated/dehydrated-mythic-dns01/challenge.sh create mode 100755 states/certificates/dehydrated/dehydrated-mythic-dns01/clean-challenge/mythic-dns01 create mode 100644 states/certificates/dehydrated/dehydrated-mythic-dns01/common/mythic-dns01 create mode 100755 states/certificates/dehydrated/dehydrated-mythic-dns01/dehydrated-mythic-dns01.sh create mode 100755 states/certificates/dehydrated/dehydrated-mythic-dns01/deploy-challenge/mythic-dns01 create mode 100644 states/certificates/dehydrated/dnsapi.config.txt create mode 100644 states/certificates/dehydrated/domains.txt create mode 100644 states/certificates/dehydrated/logrotate create mode 100644 states/certificates/init.sls diff --git a/pillar/secrets/dnsapi.sls.sample b/pillar/secrets/dnsapi.sls.sample new file mode 100644 index 0000000..dfef574 --- /dev/null +++ b/pillar/secrets/dnsapi.sls.sample @@ -0,0 +1,3 @@ +dnsapi: + keyid: 'xyzzy' + secret: 'plugh' diff --git a/pillar/top.sls b/pillar/top.sls new file mode 100644 index 0000000..4668d6f --- /dev/null +++ b/pillar/top.sls @@ -0,0 +1,4 @@ +base: + 'scabbers.lunch.org.uk': + - secrets/dnsapi + diff --git a/states/certificates/dehydrated/conf.d/hook.sh b/states/certificates/dehydrated/conf.d/hook.sh new file mode 100644 index 0000000..e647c51 --- /dev/null +++ b/states/certificates/dehydrated/conf.d/hook.sh @@ -0,0 +1,3 @@ +HOOK=/etc/dehydrated/dehydrated-mythic-dns01/dehydrated-mythic-dns01.sh +CHALLENGETYPE=dns-01 +HOOK_CHAIN=yes diff --git a/states/certificates/dehydrated/conf.d/mail.sh b/states/certificates/dehydrated/conf.d/mail.sh new file mode 100644 index 0000000..366c136 --- /dev/null +++ b/states/certificates/dehydrated/conf.d/mail.sh @@ -0,0 +1 @@ +CONTEXT_EMAIL=jim@lunch.org.uk diff --git a/states/certificates/dehydrated/cron.daily b/states/certificates/dehydrated/cron.daily new file mode 100644 index 0000000..943b6dd --- /dev/null +++ b/states/certificates/dehydrated/cron.daily @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/bin/dehydrated --cron >> /var/log/dehydrated.log 2>&1 diff --git a/states/certificates/dehydrated/dehydrated-mythic-dns01/challenge.sh b/states/certificates/dehydrated/dehydrated-mythic-dns01/challenge.sh new file mode 100644 index 0000000..de10116 --- /dev/null +++ b/states/certificates/dehydrated/dehydrated-mythic-dns01/challenge.sh @@ -0,0 +1,2 @@ +CHALLENGETYPE=dns-01 +HOOK_CHAIN=yes diff --git a/states/certificates/dehydrated/dehydrated-mythic-dns01/clean-challenge/mythic-dns01 b/states/certificates/dehydrated/dehydrated-mythic-dns01/clean-challenge/mythic-dns01 new file mode 100755 index 0000000..d49108e --- /dev/null +++ b/states/certificates/dehydrated/dehydrated-mythic-dns01/clean-challenge/mythic-dns01 @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +# Copyright (C) 2018 Mythic Beasts Ltd + +. "$(dirname "$0")"/../common/mythic-dns01 + +echo -ne "$ARGS" | while read domain filename token; do + echo " ++ cleaning DNS for $domain" + call_api DELETE _acme-challenge.$domain $token +done diff --git a/states/certificates/dehydrated/dehydrated-mythic-dns01/common/mythic-dns01 b/states/certificates/dehydrated/dehydrated-mythic-dns01/common/mythic-dns01 new file mode 100644 index 0000000..1ca9c06 --- /dev/null +++ b/states/certificates/dehydrated/dehydrated-mythic-dns01/common/mythic-dns01 @@ -0,0 +1,73 @@ +# Copyright (C) Mythic Beasts Ltd 2018 + +CONFIG=${MYTHIC_DNS_CONFIG:-/etc/dehydrated/dnsapi.config.txt} + +# configure the busy wait loop; max time is $SLEEP * $MAXTRIES +SLEEP=5 +MAXTRIES=60 + +# all our public authoritative servers +SERVERS="ns1.mythic-beasts.com ns2.mythic-beasts.com" + +# dig options +DIGOPT='+time=1 +tries=1 +short' + +wait_for_dns() { + local key val i s + key="$1" val="$2" + for i in $(seq $MAXTRIES); do + for s in $SERVERS; do + if ! dig $DIGOPT @$s $key txt | grep -q -e $val; then + sleep $SLEEP + continue 2 + fi + done + break + done + if [ "$i" -eq "$MAXTRIES" ]; then + echo challenge record not found in DNS >&2 + exit 1 + fi +} + +call_api() { + local action key val dns_domain dns_api_pass rr_part dns_api_secret + action="$1" key="$2" val="$3" + while read dns_domain dns_api_pass dns_api_secret; do + case $key in + *$dns_domain) + rr_part=$(basename $key .$dns_domain) + + # If we have a third component in the config file then use the v2 API. + if [ -z "${dns_api_secret}" ]; then + # use DNS API v1 + echo -n "$dns_api_pass" | + curl --data-urlencode "domain=$dns_domain" --data-urlencode "password@-" --data-urlencode "command=$action $rr_part 30 TXT $val" https://dnsapi.mythic-beasts.com/ + else + # use DNS API v2 + case $action in + ADD) + # Use POST here rather than PUT as we may handle multiple challenges in a single run - for example both '*' and bare domain. + echo -n "user = ${dns_api_pass}:${dns_api_secret}" | + curl -f -K - -X POST "https://api.mythic-beasts.com/dns/v2/zones/${dns_domain}/records/${rr_part}/TXT" -d data="${val}" + if [ $? -ne 0 ]; then + echo "Error adding DNS records" + exit 2 + fi + ;; + DELETE) + echo -n "user = ${dns_api_pass}:${dns_api_secret}" | + curl -f -K - -X DELETE "https://api.mythic-beasts.com/dns/v2/zones/${dns_domain}/records/${rr_part}/TXT" + if [ $? -ne 0 ]; then + echo "Error cleaning up DNS records" + exit 3 + fi + ;; + esac + fi + + break + ;; + esac + done < $CONFIG +} diff --git a/states/certificates/dehydrated/dehydrated-mythic-dns01/dehydrated-mythic-dns01.sh b/states/certificates/dehydrated/dehydrated-mythic-dns01/dehydrated-mythic-dns01.sh new file mode 100755 index 0000000..ddb4ac9 --- /dev/null +++ b/states/certificates/dehydrated/dehydrated-mythic-dns01/dehydrated-mythic-dns01.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +# Copyright (c) 2018 Mythic Beasts Ltd + +group_args() { + local nl=$'\n' + ARGS='' + while [ "$1" ]; do ARGS="$ARGS$1 $2 $3$nl"; shift 3; done + export ARGS +} + +action=$1 +shift + +case $action in + deploy_challenge|clean_challenge) + group_args "$@" + "$(dirname "$0")"/${action/_/-}/mythic-dns01 + ;; +esac diff --git a/states/certificates/dehydrated/dehydrated-mythic-dns01/deploy-challenge/mythic-dns01 b/states/certificates/dehydrated/dehydrated-mythic-dns01/deploy-challenge/mythic-dns01 new file mode 100755 index 0000000..e0e8134 --- /dev/null +++ b/states/certificates/dehydrated/dehydrated-mythic-dns01/deploy-challenge/mythic-dns01 @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +# Copyright (C) 2018 Mythic Beasts Ltd + +. "$(dirname "$0")"/../common/mythic-dns01 + +echo -ne "$ARGS" | while read domain filename token; do + echo " ++ setting DNS for $domain" + call_api ADD _acme-challenge.$domain $token +done +echo -ne "$ARGS" | while read domain filename token; do + echo " ++ waiting DNS for $domain" + wait_for_dns _acme-challenge.$domain $token +done diff --git a/states/certificates/dehydrated/dnsapi.config.txt b/states/certificates/dehydrated/dnsapi.config.txt new file mode 100644 index 0000000..45f9063 --- /dev/null +++ b/states/certificates/dehydrated/dnsapi.config.txt @@ -0,0 +1,23 @@ +{%- set keyid = pillar['dnsapi']['keyid'] -%} +{%- set secret = pillar['dnsapi']['secret'] -%} +{#- -#} +{#- bear-cave.org.uk domains -#} +{#- -#} +bear-cave.org.uk {{ keyid }} {{ secret }} +www.bear-cave.org.uk {{ keyid }} {{ secret }} +{# -#} +{#- lunch.org.uk domains -#} +{#- -#} +lunch.org.uk {{ keyid }} {{ secret }} +www.lunch.org.uk {{ keyid }} {{ secret }} +hg.lunch.org.uk {{ keyid }} {{ secret }} +jenkins.lunch.org.uk {{ keyid }} {{ secret }} +lists.lunch.org.uk {{ keyid }} {{ secret }} +mail.lunch.org.uk {{ keyid }} {{ secret }} +{# -#} +{#- cryhavoc.org.uk domains -#} +{#- -#} +cryhavoc.org.uk {{ keyid }} {{ secret }} +www.cryhavoc.org.uk {{ keyid }} {{ secret }} +dottes.cryhavoc.org.uk {{ keyid }} {{ secret }} +lists.cryhavoc.org.uk {{ keyid }} {{ secret }} diff --git a/states/certificates/dehydrated/domains.txt b/states/certificates/dehydrated/domains.txt new file mode 100644 index 0000000..86efae8 --- /dev/null +++ b/states/certificates/dehydrated/domains.txt @@ -0,0 +1,8 @@ +bear-cave.org.uk www.bear-cave.org.uk +lunch.org.uk www.lunch.org.uk +hg.lunch.org.uk +jenkins.lunch.org.uk +mail.lunch.org.uk +lists.lunch.org.uk lists.cryhavoc.org.uk +cryhavoc.org.uk www.cryhavoc.org.uk +dottes.cryhavoc.org.uk diff --git a/states/certificates/dehydrated/logrotate b/states/certificates/dehydrated/logrotate new file mode 100644 index 0000000..910a951 --- /dev/null +++ b/states/certificates/dehydrated/logrotate @@ -0,0 +1,9 @@ +/var/log/dehydrated.log +{ + rotate 12 + monthly + missingok + notifempty + delaycompress + compress +} diff --git a/states/certificates/init.sls b/states/certificates/init.sls new file mode 100644 index 0000000..21dc3bd --- /dev/null +++ b/states/certificates/init.sls @@ -0,0 +1,48 @@ +dehydrated: + pkg.installed: + - pkgs: + - dehydrated + - dnsutils + +dehydrated_domains: + file.managed: + - name: /etc/dehydrated/domains.txt + - source: salt://certificates/dehydrated/domains.txt + - mode: '0644' + +dehydrated_dnsapi: + file.managed: + - name: /etc/dehydrated/dnsapi.config.txt + - source: salt://certificates/dehydrated/dnsapi.config.txt + - mode: '0600' + - template: jinja + +dehydrated_mythic_dns01: + file.recurse: + - name: /etc/dehydrated/dehydrated-mythic-dns01 + - source: salt://certificates/dehydrated/dehydrated-mythic-dns01 + - dir_mode: '0755' + - file_mode: '0755' + - exclude_pat: + - "debian*" + - README.md + +dehydrated_hooks: + file.recurse: + - name: /etc/dehydrated/conf.d + - source: salt://certificates/dehydrated/conf.d + - dir_mode: '0755' + - file_mode: '0644' + +dehydrated_cron: + file.managed: + - name: /etc/cron.daily/dehydrated + - source: salt://certificates/dehydrated/cron.daily + - mode: '0755' + +dehydrated_logrotate: + file.managed: + - name: /etc/logrotate.d/dehydrated + - source: salt://certificates/dehydrated/logrotate + - mode: '0644' + diff --git a/states/top.sls b/states/top.sls index 06b6149..7612598 100644 --- a/states/top.sls +++ b/states/top.sls @@ -7,3 +7,4 @@ base: 'scabbers.lunch.org.uk': - debian + - certificates