From c1e2145ba10b8e9e672df4e56e586653b9401b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Kar=C3=A9kinian?= Date: Wed, 12 Jul 2023 20:35:15 +0200 Subject: [PATCH] Create a resource to get a Let's Encrypt cert with DNS validation --- .../kosmos-base/recipes/letsencrypt.rb | 21 ++++--- .../kosmos-base/resources/tls_cert_for.rb | 46 ++++++++++++++ .../default/gandi_dns_certbot_hook.sh.erb | 63 +++++++++++++++++++ 3 files changed, 120 insertions(+), 10 deletions(-) create mode 100644 site-cookbooks/kosmos-base/resources/tls_cert_for.rb create mode 100755 site-cookbooks/kosmos-base/templates/default/gandi_dns_certbot_hook.sh.erb diff --git a/site-cookbooks/kosmos-base/recipes/letsencrypt.rb b/site-cookbooks/kosmos-base/recipes/letsencrypt.rb index ce65d33..e13c3db 100644 --- a/site-cookbooks/kosmos-base/recipes/letsencrypt.rb +++ b/site-cookbooks/kosmos-base/recipes/letsencrypt.rb @@ -52,16 +52,17 @@ end end end -# TODO check if nginx is installed/running on the node -file "/etc/letsencrypt/renewal-hooks/deploy/nginx" do - content <<-EOF -#!/usr/bin/env bash -# Reloading nginx is enough to read the new certificates -systemctl reload nginx - EOF - mode 0755 - owner "root" - group "root" +if node.run_list.roles.include?("openresty_proxy") + file "/etc/letsencrypt/renewal-hooks/post/openresty" do + content <<-EOF + #!/usr/bin/env bash + # Reloading openresty is enough to read the new certificates + systemctl reload openresty + EOF + mode 0755 + owner "root" + group "root" + end end # include_recipe 'kosmos-base::systemd_emails' diff --git a/site-cookbooks/kosmos-base/resources/tls_cert_for.rb b/site-cookbooks/kosmos-base/resources/tls_cert_for.rb new file mode 100644 index 0000000..426743b --- /dev/null +++ b/site-cookbooks/kosmos-base/resources/tls_cert_for.rb @@ -0,0 +1,46 @@ +resource_name :tls_cert_for +provides :tls_cert_for + +property :domain, [String, Array], name_property: true +property :auth, [String, NilClass], default: nil + +default_action :create + +action :create do + include_recipe 'kosmos-base::letsencrypt' + + domains = Array(new_resource.domain) + + case new_resource.auth + when "gandi_dns" + gandi_api_data_bag_item = data_bag_item('credentials', 'gandi_api_5apps') + + hook_path = "/root/gandi_dns_certbot_hook.sh" + template hook_path do + cookbook "kosmos-base" + variables gandi_api_key: gandi_api_data_bag_item["key"] + mode 0770 + 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 #{domains.join(', ')}" do + command <<-CMD + certbot certonly --manual -n \ + --preferred-challenges dns \ + --manual-public-ip-logging-ok \ + --agree-tos \ + --manual-auth-hook '#{hook_path} auth' \ + --manual-cleanup-hook '#{hook_path} cleanup' \ + --deploy-hook /etc/letsencrypt/renewal-hooks/post/openresty \ + --email ops@kosmos.org \ + #{domains.map {|d| "-d #{d}" }.join(" ")} + CMD + not_if do + ::File.exist?("/etc/letsencrypt/live/#{domains.first}/fullchain.pem") + end + end + else + # regular http auth + end +end 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 new file mode 100755 index 0000000..1a73c8e --- /dev/null +++ b/site-cookbooks/kosmos-base/templates/default/gandi_dns_certbot_hook.sh.erb @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# + +set -euf -o pipefail + +# ************** USAGE ************** +# +# Example usage (with this hook file saved in /root/): +# +# 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 +# +# PROVIDER_UPDATE_DELAY: +# How many seconds to wait after updating your DNS records. This may be required, +# depending on how slow your DNS host is to begin serving new DNS records after updating +# them via the API. 30 seconds is a safe default, but some providers can be very slow +# (e.g. Linode). +# +# Defaults to 30 seconds. +# +GANDI_API_KEY="<%= @gandi_api_key %>" +PROVIDER_UPDATE_DELAY=2 + +regex='.*\.(.*\..*)' +if [[ $CERTBOT_DOMAIN =~ $regex ]] +then + DOMAIN="${BASH_REMATCH[1]}" +else + DOMAIN="${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" + + + 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 +} + +HANDLER=$1; shift; +if [ -n "$(type -t $HANDLER)" ] && [ "$(type -t $HANDLER)" = function ]; then + $HANDLER "$@" +fi