From 4cbda69a6b1b5c43654464900ae6e1774746dce1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Fri, 26 Apr 2024 12:24:17 +0200 Subject: [PATCH 1/2] Add support for proxy domain validation to tls_cert resource --- .../kosmos-base/resources/tls_cert_for.rb | 20 +++++-- .../default/gandi_dns_certbot_hook.sh.erb | 57 ++++++++++++------- .../templates/gandi_dns_certbot_hook.sh.erb | 11 ++-- .../kosmos-mastodon/recipes/nginx.rb | 1 + 4 files changed, 59 insertions(+), 30 deletions(-) diff --git a/site-cookbooks/kosmos-base/resources/tls_cert_for.rb b/site-cookbooks/kosmos-base/resources/tls_cert_for.rb index 02e73d2..b1f92fc 100644 --- a/site-cookbooks/kosmos-base/resources/tls_cert_for.rb +++ b/site-cookbooks/kosmos-base/resources/tls_cert_for.rb @@ -3,6 +3,7 @@ provides :tls_cert_for property :domain, [String, Array], name_property: true property :auth, [String, NilClass], default: nil +property :acme_domain, [String, NilClass], default: nil default_action :create @@ -17,13 +18,22 @@ action :create do case new_resource.auth when "gandi_dns" - gandi_api_data_bag_item = data_bag_item('credentials', 'gandi_api_5apps') + gandi_api_credentials = data_bag_item('credentials', 'gandi_api_5apps') hook_path = "/root/gandi_dns_certbot_hook.sh" + hook_auth_command = "#{hook_path} auth" + hook_cleanup_command = "#{hook_path} cleanup" + + if new_resource.acme_domain + hook_auth_command += " #{new_resource.acme_domain}" + hook_cleanup_command += " #{new_resource.acme_domain}" + end + template hook_path do cookbook "kosmos-base" - variables gandi_api_key: gandi_api_data_bag_item["key"] - mode 0770 + variables access_token: gandi_api_credentials["access_token"] + mode 0700 + sensitive true end # Generate a Let's Encrypt cert (only if no cert has been generated before). @@ -34,8 +44,8 @@ action :create do --preferred-challenges dns \ --manual-public-ip-logging-ok \ --agree-tos \ - --manual-auth-hook '#{hook_path} auth' \ - --manual-cleanup-hook '#{hook_path} cleanup' \ + --manual-auth-hook '#{hook_auth_command}' \ + --manual-cleanup-hook '#{hook_cleanup_command}' \ --email ops@kosmos.org \ #{node.run_list.roles.include?("openresty_proxy") ? '--deploy-hook /etc/letsencrypt/renewal-hooks/post/openresty' : nil } \ #{domains.map {|d| "-d #{d}" }.join(" ")} diff --git a/site-cookbooks/kosmos-base/templates/default/gandi_dns_certbot_hook.sh.erb b/site-cookbooks/kosmos-base/templates/default/gandi_dns_certbot_hook.sh.erb index 4c59a11..2a5f0eb 100755 --- a/site-cookbooks/kosmos-base/templates/default/gandi_dns_certbot_hook.sh.erb +++ b/site-cookbooks/kosmos-base/templates/default/gandi_dns_certbot_hook.sh.erb @@ -1,21 +1,16 @@ #!/usr/bin/env bash -# - set -euf -o pipefail # ************** USAGE ************** # -# Example usage (with this hook file saved in /root/): +# Example usage: # -# sudo su - # certbot certonly --manual --preferred-challenges dns --manual-public-ip-logging-ok --agree-tos -d "5apps.com" -d muc.5apps.com -d "xmpp.5apps.com" \ # --manual-auth-hook "/root/letsencrypt_hook.sh auth" --manual-cleanup-hook "/root/letsencrypt_hook.sh cleanup" # -# This hook requires configuration, continue reading. -# # ************** CONFIGURATION ************** # -# GANDI_API_KEY: Your Gandi Live API key +# ACCESS_TOKEN: Your Gandi Live API key # # PROVIDER_UPDATE_DELAY: # How many seconds to wait after updating your DNS records. This may be required, @@ -25,10 +20,16 @@ set -euf -o pipefail # # Defaults to 30 seconds. # -GANDI_API_KEY="<%= @gandi_api_key %>" +# VALIDATION_DOMAIN: +# Domain to create ACME DNS entries on. Use this when redirecting ACME subdomains +# from the original domain to a proxy validation domain that we control. +# +ACCESS_TOKEN="<%= @access_token %>" PROVIDER_UPDATE_DELAY=10 +VALIDATION_DOMAIN="${2:-}" regex='.*\.(.*\..*)' + if [[ $CERTBOT_DOMAIN =~ $regex ]] then DOMAIN="${BASH_REMATCH[1]}" @@ -36,25 +37,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; 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 7bf1a84..2a5f0eb 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 @@ -3,17 +3,14 @@ set -euf -o pipefail # ************** USAGE ************** # -# Example usage (with this hook file saved in /root/): +# Example usage: # -# sudo su - # certbot certonly --manual --preferred-challenges dns --manual-public-ip-logging-ok --agree-tos -d "5apps.com" -d muc.5apps.com -d "xmpp.5apps.com" \ # --manual-auth-hook "/root/letsencrypt_hook.sh auth" --manual-cleanup-hook "/root/letsencrypt_hook.sh cleanup" # -# This hook requires configuration, continue reading. -# # ************** CONFIGURATION ************** # -# GANDI_API_KEY: Your Gandi Live API key +# ACCESS_TOKEN: Your Gandi Live API key # # PROVIDER_UPDATE_DELAY: # How many seconds to wait after updating your DNS records. This may be required, @@ -23,6 +20,10 @@ set -euf -o pipefail # # Defaults to 30 seconds. # +# VALIDATION_DOMAIN: +# Domain to create ACME DNS entries on. Use this when redirecting ACME subdomains +# from the original domain to a proxy validation domain that we control. +# ACCESS_TOKEN="<%= @access_token %>" PROVIDER_UPDATE_DELAY=10 VALIDATION_DOMAIN="${2:-}" diff --git a/site-cookbooks/kosmos-mastodon/recipes/nginx.rb b/site-cookbooks/kosmos-mastodon/recipes/nginx.rb index 405be3a..bcb1d83 100644 --- a/site-cookbooks/kosmos-mastodon/recipes/nginx.rb +++ b/site-cookbooks/kosmos-mastodon/recipes/nginx.rb @@ -34,6 +34,7 @@ end tls_cert_for server_name do auth "gandi_dns" + acme_domain "letsencrypt.kosmos.org" action :create end From 989185f95123eb521f9c6eec5fb211067e5641aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A2u=20Cao?= Date: Tue, 30 Apr 2024 12:23:36 +0200 Subject: [PATCH 2/2] Support proxy domain validation for Garage web domains Also rename the data bag item --- data_bags/credentials/gandi_api.json | 24 +++++++++++++++++++ data_bags/credentials/gandi_api_5apps.json | 17 ------------- .../kosmos-base/resources/tls_cert_for.rb | 2 +- .../kosmos-ejabberd/recipes/letsencrypt.rb | 2 +- .../kosmos_garage/recipes/nginx_web.rb | 6 +++++ site-cookbooks/sockethub/recipes/proxy.rb | 4 ++-- 6 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 data_bags/credentials/gandi_api.json delete mode 100644 data_bags/credentials/gandi_api_5apps.json diff --git a/data_bags/credentials/gandi_api.json b/data_bags/credentials/gandi_api.json new file mode 100644 index 0000000..cf08cef --- /dev/null +++ b/data_bags/credentials/gandi_api.json @@ -0,0 +1,24 @@ +{ + "id": "gandi_api", + "key": { + "encrypted_data": "d3/rJMX6B9GuzUt0/mIk/lgQ3qGyQdbNXH6UEm3ZX7DeSl+rbW9FPJCRWg==\n", + "iv": "15YVAYla7PqqVOab\n", + "auth_tag": "xQSq+ld6SDOAER07N4ZkUQ==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "access_token": { + "encrypted_data": "geQwcNosiJZmqbbMpD/I+a2yueBzpV6C8Rb7vrCD8kR161ZRjvqLe+g/1XpT\n2/65wKYDMTrdto1I030=\n", + "iv": "1sj58eyooOZ8FTYn\n", + "auth_tag": "yBNfgWXaToc06VDLly/HUw==\n", + "version": 3, + "cipher": "aes-256-gcm" + }, + "domains": { + "encrypted_data": "p5rIQTyCE+0d4HIuA4GKEAFekh7qEC4xe9Rm/kP0DyzY83FO0/4uKIvYoZRB\n", + "iv": "LWlx98NSS1/ngCH1\n", + "auth_tag": "FID+x/LjTZ3cgQV5U2xZLA==\n", + "version": 3, + "cipher": "aes-256-gcm" + } +} \ No newline at end of file diff --git a/data_bags/credentials/gandi_api_5apps.json b/data_bags/credentials/gandi_api_5apps.json deleted file mode 100644 index 6e42ccc..0000000 --- a/data_bags/credentials/gandi_api_5apps.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "gandi_api_5apps", - "key": { - "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-base/resources/tls_cert_for.rb b/site-cookbooks/kosmos-base/resources/tls_cert_for.rb index b1f92fc..0f56f29 100644 --- a/site-cookbooks/kosmos-base/resources/tls_cert_for.rb +++ b/site-cookbooks/kosmos-base/resources/tls_cert_for.rb @@ -18,7 +18,7 @@ action :create do case new_resource.auth when "gandi_dns" - gandi_api_credentials = data_bag_item('credentials', 'gandi_api_5apps') + gandi_api_credentials = data_bag_item('credentials', 'gandi_api') hook_path = "/root/gandi_dns_certbot_hook.sh" hook_auth_command = "#{hook_path} auth" diff --git a/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb b/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb index 408bb7a..872f060 100644 --- a/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb +++ b/site-cookbooks/kosmos-ejabberd/recipes/letsencrypt.rb @@ -33,7 +33,7 @@ file "/etc/letsencrypt/renewal-hooks/post/ejabberd" do group "root" end -gandi_api_credentials = data_bag_item('credentials', 'gandi_api_5apps') +gandi_api_credentials = data_bag_item('credentials', 'gandi_api') template "/root/gandi_dns_certbot_hook.sh" do variables access_token: gandi_api_credentials["access_token"] diff --git a/site-cookbooks/kosmos_garage/recipes/nginx_web.rb b/site-cookbooks/kosmos_garage/recipes/nginx_web.rb index 27919e3..f7a6823 100644 --- a/site-cookbooks/kosmos_garage/recipes/nginx_web.rb +++ b/site-cookbooks/kosmos_garage/recipes/nginx_web.rb @@ -3,6 +3,8 @@ # Recipe:: nginx_web # +gandi_api_credentials = data_bag_item('credentials', 'gandi_api') + file "#{node['openresty']['dir']}/conf.d/garage.conf" do content <<-EOF upstream garage_web { @@ -40,8 +42,12 @@ end # node['garage']['s3_web_domains'].each do |domain_name| + second_level_domain = domain_name.match(/(?:.*\.)?([^.]+\.[^.]+)$/) { $1 } + proxy_validation = !gandi_api_credentials["domains"].include?(second_level_domain) + tls_cert_for domain_name do auth "gandi_dns" + acme_domain "letsencrypt.kosmos.org" if proxy_validation action :create end diff --git a/site-cookbooks/sockethub/recipes/proxy.rb b/site-cookbooks/sockethub/recipes/proxy.rb index a62b5fd..17744f7 100644 --- a/site-cookbooks/sockethub/recipes/proxy.rb +++ b/site-cookbooks/sockethub/recipes/proxy.rb @@ -24,10 +24,10 @@ file "/etc/letsencrypt/renewal-hooks/post/nginx" 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') template "/root/gandi_dns_certbot_hook.sh" do - variables gandi_api_key: gandi_api_data_bag_item["key"] + variables gandi_api_key: gandi_api_credentials["key"] mode 0770 end