From 4a8ab3abe346456d0766a387c930fb307103afad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Mon, 11 Mar 2024 16:15:12 +0100 Subject: [PATCH] Support letsencrypt proxy validation via CNAMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows to point other domains' `_acme-challenge.example.com` entries at `example.com.letsencrypt.kosmos.chat` so we can validate from our side without access to the other domain's DNS records. Used for 5apps.com XMPP for now. Can be used for others later. Co-authored-by: Greg Karékinian --- data_bags/credentials/gandi_api_5apps.json | 16 +++++-- .../kosmos-ejabberd/recipes/letsencrypt.rb | 8 ++-- .../templates/gandi_dns_certbot_hook.sh.erb | 48 ++++++++++++------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/data_bags/credentials/gandi_api_5apps.json b/data_bags/credentials/gandi_api_5apps.json index e3d7256..6e42ccc 100644 --- a/data_bags/credentials/gandi_api_5apps.json +++ b/data_bags/credentials/gandi_api_5apps.json @@ -1,9 +1,17 @@ { "id": "gandi_api_5apps", "key": { - "encrypted_data": "+tcD9x5MkNpf2Za5iLM7oTGrmAXxuWFEbyg4xrcWypSkSTjdIncOfD1UoIoS\nGzy1\n", - "iv": "ymls2idI/PdiRZCgsulwrA==\n", - "version": 1, - "cipher": "aes-256-cbc" + "encrypted_data": "AGYIkLdbnU3+O6OxGsFyLpZtTw531s2dbRC4Lik+8NYp3l4P0UMM2Pqf0g==\n", + "iv": "kPRHGpLwNIC3MpES\n", + "auth_tag": "wKth2tA+JxILFIKppHLDJg==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "access_token": { + "encrypted_data": "+tKKFcWV0CZ5wEB/No5hou5+p1llsUkq7AXBvfnA7xsgbpa2q8AX/2UFf9Cf\nGtd9om1CeJJtz+o4ceA=\n", + "iv": "hLJSV77DQtqXZDbV\n", + "auth_tag": "8xgyudyDk4hq16LRkykGhQ==\n", + "version": 3, + "cipher": "aes-256-gcm" } } \ No newline at end of file diff --git a/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb b/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb index 77aea83..408bb7a 100644 --- a/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb +++ b/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb @@ -33,11 +33,11 @@ file "/etc/letsencrypt/renewal-hooks/post/ejabberd" do group "root" end -gandi_api_data_bag_item = data_bag_item('credentials', 'gandi_api_5apps') +gandi_api_credentials = data_bag_item('credentials', 'gandi_api_5apps') template "/root/gandi_dns_certbot_hook.sh" do - variables gandi_api_key: gandi_api_data_bag_item["key"] - mode 0770 + variables access_token: gandi_api_credentials["access_token"] + mode 0700 end # Generate a Let's Encrypt cert (only if no cert has been generated before). @@ -52,7 +52,7 @@ end # Generate a Let's Encrypt cert (only if no cert has been generated before). # The systemd timer will take care of renewing execute "letsencrypt cert for 5apps xmpp" do - command "certbot certonly --manual --preferred-challenges dns --manual-public-ip-logging-ok --agree-tos --manual-auth-hook \"/root/gandi_dns_certbot_hook.sh auth\" --manual-cleanup-hook \"/root/gandi_dns_certbot_hook.sh cleanup\" --deploy-hook \"/etc/letsencrypt/renewal-hooks/post/ejabberd\" --email ops@5apps.com -d 5apps.com -d muc.5apps.com -d xmpp.5apps.com -d uploads.xmpp.5apps.com -n" + command "certbot certonly --manual --preferred-challenges dns --manual-public-ip-logging-ok --agree-tos --manual-auth-hook \"/root/gandi_dns_certbot_hook.sh auth letsencrypt.kosmos.chat\" --manual-cleanup-hook \"/root/gandi_dns_certbot_hook.sh cleanup letsencrypt.kosmos.chat\" --deploy-hook \"/etc/letsencrypt/renewal-hooks/post/ejabberd\" --email ops@5apps.com -d 5apps.com -d muc.5apps.com -d xmpp.5apps.com -d uploads.xmpp.5apps.com -n" not_if do File.exist?("/etc/letsencrypt/live/5apps.com/fullchain.pem") end diff --git a/site-cookbooks/kosmos-ejabberd/templates/gandi_dns_certbot_hook.sh.erb b/site-cookbooks/kosmos-ejabberd/templates/gandi_dns_certbot_hook.sh.erb index d0ed9dc..7bf1a84 100755 --- a/site-cookbooks/kosmos-ejabberd/templates/gandi_dns_certbot_hook.sh.erb +++ b/site-cookbooks/kosmos-ejabberd/templates/gandi_dns_certbot_hook.sh.erb @@ -1,6 +1,4 @@ #!/usr/bin/env bash -# - set -euf -o pipefail # ************** USAGE ************** @@ -25,10 +23,12 @@ set -euf -o pipefail # # Defaults to 30 seconds. # -GANDI_API_KEY="<%= @gandi_api_key %>" -PROVIDER_UPDATE_DELAY=30 +ACCESS_TOKEN="<%= @access_token %>" +PROVIDER_UPDATE_DELAY=10 +VALIDATION_DOMAIN="${2:-}" regex='.*\.(.*\..*)' + if [[ $CERTBOT_DOMAIN =~ $regex ]] then DOMAIN="${BASH_REMATCH[1]}" @@ -36,25 +36,41 @@ else DOMAIN="${CERTBOT_DOMAIN}" fi +if [[ -n "$VALIDATION_DOMAIN" ]] +then + if [[ $VALIDATION_DOMAIN =~ $regex ]] + then + ACME_BASE_DOMAIN="${BASH_REMATCH[1]}" + else + echo "Validation domain has to be a subdomain, but it is not: \"${VALIDATION_DOMAIN}\"" + exit 1 + fi + ACME_DOMAIN="${CERTBOT_DOMAIN}.${VALIDATION_DOMAIN}" +else + ACME_BASE_DOMAIN="${DOMAIN}" + ACME_DOMAIN="_acme-challenge.${CERTBOT_DOMAIN}" +fi + # To be invoked via Certbot's --manual-auth-hook function auth { - curl -s -D- -H "Content-Type: application/json" \ - -H "X-Api-Key: ${GANDI_API_KEY}" \ - -d "{\"rrset_name\": \"_acme-challenge.${CERTBOT_DOMAIN}.\", - \"rrset_type\": \"TXT\", - \"rrset_ttl\": 3600, - \"rrset_values\": [\"${CERTBOT_VALIDATION}\"]}" \ - "https://dns.api.gandi.net/api/v5/domains/${DOMAIN}/records" + curl -s -D- \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + -d "{\"rrset_name\": \"${ACME_DOMAIN}.\", + \"rrset_type\": \"TXT\", + \"rrset_ttl\": 300, + \"rrset_values\": [\"${CERTBOT_VALIDATION}\"]}" \ + "https://api.gandi.net/v5/livedns/domains/${ACME_BASE_DOMAIN}/records" - - sleep ${PROVIDER_UPDATE_DELAY} + sleep ${PROVIDER_UPDATE_DELAY} } # To be invoked via Certbot's --manual-cleanup-hook function cleanup { - curl -s -X DELETE -H "Content-Type: application/json" \ - -H "X-Api-Key: ${GANDI_API_KEY}" \ - https://dns.api.gandi.net/api/v5/domains/${DOMAIN}/records/_acme-challenge.${CERTBOT_DOMAIN}./TXT + curl -s -X DELETE \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${ACCESS_TOKEN}" \ + "https://api.gandi.net/v5/livedns/domains/${ACME_BASE_DOMAIN}/records/${ACME_DOMAIN}./TXT" } HANDLER=$1; shift;