Skip to content

Instantly share code, notes, and snippets.

@cendyne
Created August 7, 2021 22:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cendyne/83b8dd6f050b20cfee430e3b4f94577e to your computer and use it in GitHub Desktop.
Save cendyne/83b8dd6f050b20cfee430e3b4f94577e to your computer and use it in GitHub Desktop.
tls-rotate-cloudflare.sh
#!/bin/bash
HOST="mail.cendyne.dev"
ZONE_ID=REDACTED
TOKEN=REDACTED
PEM="/etc/letsencrypt/live/$HOST/cert.pem"
PORTS="25 443"
PROTOCOL="tcp"
NAMES=""
for port in $PORTS; do
NAMES="$NAMES _$port._$PROTOCOL.$HOST"
done
function hex() {
echo "$1" | xxd -p -c 1000000
}
function gettlsa() {
curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=TLSA&match=all" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
}
function digestpem() {
openssl x509 -in "$1" -noout -pubkey | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | xxd -p -c 100000
}
DIGEST="$(digestpem "$PEM")"
EXPECTED="3 1 1 $DIGEST"
ZONES="$(gettlsa)"
echo $EXPECTED
function extractentries() {
echo "$1" | jq -r ".result[] | {content: .content, id: .id, name: .name} | @base64"
}
TODELETE=""
for row in $(extractentries "$ZONES"); do
ROW="$(echo "$row" | base64 --decode)"
CONTENT="$(echo "$ROW" | jq -r ".content")"
ID="$(echo "$ROW" | jq -r ".id")"
NAME="$(echo "$ROW" | jq -r ".name")"
if echo "$NAMES" | grep -w "$NAME" > /dev/null; then
echo "id: $ID name: $NAME content: $CONTENT"
if [[ "$CONTENT" != "$EXPECTED" ]]; then
TODELETE="$TODELETE $ID"
fi
fi
done
echo "EXPECTED: $EXPECTED"
echo "TO Delete $TODELETE"
certbot renew --quiet
NEWDIGEST="$(digestpem "$PEM")"
NEWCONTENT="3 1 1 $NEWDIGEST"
if [[ "$NEWCONTENT" == "$CONTENT" ]]; then
echo "Renewall is a no-op, no changes to be done"
exit
fi
function puttlsa() {
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"TLSA\",\"name\":\"$1\",\"content\":\"$2\",\"ttl\":1,\"data\":{\"usage\":3,\"selector\":1,\"matching_type\":1,\"certificate\":\"$3\"}}"
}
for name in $NAMES; do
echo "create dns record for $name with $NEWCONTENT"
RESULT="$(puttlsa "$name" "$NEWCONTENT" "$NEWDIGEST")"
echo $RESULT
SUCCESS="$(echo "$RESULT" | jq -r '.success')"
if [[ "$SUCCESS" != "true" ]]; then
ERROR="$(echo "$RESULT" | jq -r '.errors[0].message')"
if [[ "$ERROR" != "Record already exists." ]]; then
echo "Failure in applying '$name' with '$NEWCONTENT' due to $ERROR"
exit
fi #end error != ...
fi #end success != true
done
echo "Sleeping for 5 minutes"
sleep 300
# Reload applications that rely on certificate
echo "Reloading"
systemctl reload postfix dovecot nginx
echo "Sleeping for 5 minutes"
sleep 300
function deletedns() {
curl -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$1" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json"
echo ""
}
echo "Deleting old records"
for id in $TODELETE; do
echo "Deleting dns record $id with old content $CONTENT"
deletedns $id
done
echo "Done!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment