diff --git a/cookbooks/letsencrypt/README.md b/cookbooks/letsencrypt/README.md index 5143f446..e09670aa 100644 --- a/cookbooks/letsencrypt/README.md +++ b/cookbooks/letsencrypt/README.md @@ -11,26 +11,68 @@ This installs and sets up the optional LetsEncrypt recipe on the engineyard stac * Have a [certificate](https://support.cloud.engineyard.com/hc/en-us/articles/205407488-Obtain-and-Install-SSL-Certificates-for-Applications#topic8) applied to the [environment](https://support.cloud.engineyard.com/hc/en-us/articles/205407488-Obtain-and-Install-SSL-Certificates-for-Applications#topic12) +* Have the application deployed successfully **Environment Variables** There are several environmental variables needed to be used with the LetsEncrypt recipe. To enable the recipe set `EY_LETSENCRYPT_ENABLED` to `TRUE`. This will install certbot on all application instances + +**SAN Certificate Setup** + To automatically create a certificate or SAN certiciate: -* Make sure a certificate is applied to the environment using the documentation above. This certificate will be for configuration reasons only and not seen by visitors, so can be a self-signed one, with a name that highlights letsencrypt's use, e.g. `using-letsencrypt` -* Set the environmental variable `EY_LE_MAIN_DOMAIN` as your main domain e.g. `Engineyard.com` +* Make sure a certificate is applied to the environment using the documentation above. This certificate will be for configuration reasons only and not seen by visitors, so can be a self-signed one, with a name that highlights letsencrypts use, e.g. `using-letsencrypt` * Set the environment variable `EY_LE_DOMAINS` with any additional domains seperated with a space. The main domain **must** come first e.g. `Engineyard.com www.Engineyard.com Example.com` +* + +**Wildcard Certificate Setup** +To automatically create a wildcard certificate + +* Make sure a certificate is applied to the environment using the documentation above. This certificate will be for configuration reasons only and not seen by visitors, so can be a self-signed one, with a name that highlights letsencrypts use, e.g. `using-letsencrypt` +* Set the environment variable `EY_LE_DOMAINS` with the domain you wish to set up e.g. `*.engineyard.com` +* Set the environment variable `EY_LE_USE_WILDCARD` to `TRUE` +* Set the environment variable `EY_LE_DNS_TYPE` to one of the supported DNS providers listed below +* Set the environemnt variable `EY_LE_DNS_AUTH_INFO` to the API information for said DNS provider. The formatting can be found under the third party documentation link **Notes** * If you're using a multi-application environment setup set the variable `EY_LE_MAIN_APP_NAME` to the application you wish to use LetsEncrypt with -* `www` is not included so you may wish to use `www.example.com` and `example.com` +* `www` is not included so you may wish to use `www.example.com` and `example.com` if you're using a SAN certificate + +* Route53 provider (AWS). Requires the custom variable `AWS_CONFIG_FILE` to be set as `/opt/.letsencrypt-secrets` + + +**Supported DNS Providers** + +* cloudflare - https://certbot-dns-cloudflare.readthedocs.io/en/stable/ + +* cloudxns - https://certbot-dns-cloudxns.readthedocs.io/en/stable/ + +* digitalocean - https://certbot-dns-digitalocean.readthedocs.io/en/stable/ + +* dnsimple - https://certbot-dns-dnsimple.readthedocs.io/en/stable/ + +* dnsmadeeasy - https://certbot-dns-dnsmadeeasy.readthedocs.io/en/stable/ + +* gehirn - https://certbot-dns-gehirn.readthedocs.io/en/stable/ + +* google - https://certbot-dns-google.readthedocs.io/en/stable/ + +* linode - https://certbot-dns-linode.readthedocs.io/en/stable/ + +* luadns - https://certbot-dns-luadns.readthedocs.io/en/stable/ + +* nsone - https://certbot-dns-nsone.readthedocs.io/en/stable/ + +* ovh - https://certbot-dns-ovh.readthedocs.io/en/stable/ + +* rfc2136 - https://certbot-dns-rfc2136.readthedocs.io/en/stable/ -**Upcoming Changes** +* route53 - https://certbot-dns-route53.readthedocs.io/en/stable/ -Wildcard integration +* sakuracloud - https://certbot-dns-sakuracloud.readthedocs.io/en/stable/ diff --git a/cookbooks/letsencrypt/recipes/default.rb b/cookbooks/letsencrypt/recipes/default.rb index f4f605f1..0d210593 100644 --- a/cookbooks/letsencrypt/recipes/default.rb +++ b/cookbooks/letsencrypt/recipes/default.rb @@ -1,13 +1,24 @@ letsencrypt = node['letsencrypt'] -package "certbot" do +package "python3-pip" do action :install end -md = fetch_env_var(node, 'EY_LE_MAIN_DOMAIN').downcase +execute "ensure pip3 is up-to-date" do + command "pip3 install pip --upgrade" +end + +execute "install certbot" do + command "pip3 install certbot" +end + +md = fetch_env_var(node, 'EY_LE_DOMAINS').split[0] #= fetch_env_var(node, 'EY_LE_MAIN_DOMAIN').downcase domain = fetch_env_var(node, 'EY_LE_DOMAINS').nil? || (fetch_env_var(node, 'EY_LE_DOMAINS').gsub(" "," -d ")).downcase app = fetch_env_var(node, 'EY_LE_MAIN_APP_NAME') || node['dna']['applications'].keys.first wc = fetch_env_var(node, 'EY_LE_USE_WILDCARD') || false +type = fetch_env_var(node, 'EY_LE_DNS_TYPE') || "" + +Chef::Log.info "LetsEncrypt Widlcard: #{wc}" if Dir.exist?("/data/#{app}/current") && ['solo', 'app_master'].include?(node['dna']['instance_role']) @@ -15,13 +26,43 @@ command "/etc/init.d/haproxy start /etc/init.d/nginx start" end -if !wc + if wc + + execute "Install plugin for certbot" do + command "pip3 install certbot-dns-#{type}" + end + + managed_template "/opt/.letsencrypt-secrets" do + mode 0700 + source "letsencrypt.secrets.erb" + variables( + :data => fetch_env_var(node, 'EY_LE_DNS_AUTH_INFO') + ) + end + + case type + when /route53/ + dns_type = "" + else + dns_type = "--dns-#{type}-credentials /opt/.letsencrypt-secrets" + end + + execute "Issue certiciate initially" do + command ". /data/#{app}/shared/config/env.cloud && certbot certonly --dns-#{type} #{dns_type} -d #{domain} --non-interactive --agree-tos --register-unsafely-without-email --dry-run" + not_if { File.exist?("/etc/letsencrypt/live/#{md}/privkey.pem") } + end - execute "issue certificate" do - command "certbot certonly --noninteractive --agree-tos --register-unsafely-without-email -d #{domain} --webroot -w /data/#{app}/current/public/" - not_if { ::Dir.exist? ("/etc/letsencrypt/live/#{md}/") } end -end + + if !wc + + execute "issue certificate" do + command "certbot certonly --noninteractive --agree-tos --register-unsafely-without-email -d #{domain} --webroot -w /data/#{app}/current/public/" + not_if { ::Dir.exist? ("/etc/letsencrypt/live/#{md}/") } + end + end + + managed_template "/engineyard/bin/copycerts.sh" do owner 'root' @@ -30,11 +71,24 @@ source "copycerts.sh.erb" variables( :app_name => app, - :instances => (node.cluster - node.db_slaves - node.db_master).join(' '), + :instances => (node.cluster - node.db_slaves - node.db_master - node.util_servers).join(' '), :md => md ) end + managed_template "/engineyard/bin/check_le.sh" do + owner 'deploy' + group 'deploy' + mode 0700 + source 'check_le.sh.erb' + variables( + :md => md + ) + end + execute "update collectd" do + command 'sed -i \'81 i \ \ Exec \"deploy\" \"/engineyard/bin/check_le.sh\"\' /etc/engineyard/collectd.conf && /etc/init.d/collectd restart' + end + execute "push certificate" do command "/engineyard/bin/copycerts.sh" end diff --git a/cookbooks/letsencrypt/templates/.check_le.sh.erb.swp b/cookbooks/letsencrypt/templates/.check_le.sh.erb.swp new file mode 100644 index 00000000..7bfd238b Binary files /dev/null and b/cookbooks/letsencrypt/templates/.check_le.sh.erb.swp differ diff --git a/cookbooks/letsencrypt/templates/check_le.sh.erb b/cookbooks/letsencrypt/templates/check_le.sh.erb new file mode 100644 index 00000000..0809abfb --- /dev/null +++ b/cookbooks/letsencrypt/templates/check_le.sh.erb @@ -0,0 +1,40 @@ +#!/bin/bash + +umask 022 + +# make tmp dir +status_dir="/tmp/check_le_status" +mkdir -p "${status_dir}" + +# alert +alert () +{ + # params + device="${1}"; severity="${2}" + timestamp=$(date '+%s') + + # load previous status + status_file="${status_dir}/le-status" + previous_severity=$(cat "${status_file}") 2>/dev/null + + # send notification + if [[ $severity != $previous_severity ]]; then + case "${severity}" in + OKAY) message="Certificate is valid" ;; + FAILURE) message="Certificate will expire in 7 days or less. Something has gone wrong." ;; + esac + + echo "PUTNOTIF Type=custom-certificate Time=${timestamp} Severity=${severity} Message=\"raw_message: ${message}\"" + fi + + # write current status to status file + echo "${severity}" > "${status_file}" +} + + + +if [[ $(sudo openssl x509 -in /etc/letsencrypt/live/<%= @md %>/cert.pem -checkend 604800 -noout |grep "Certificate will expire" --ignore-case) ]]; then + alert "Certificate is invalid" "FAILURE" +else + alert "Certificate is valid" "OKAY" +fi diff --git a/cookbooks/letsencrypt/templates/copycerts.sh.erb b/cookbooks/letsencrypt/templates/copycerts.sh.erb index ccaffe8c..57e2a3d1 100644 --- a/cookbooks/letsencrypt/templates/copycerts.sh.erb +++ b/cookbooks/letsencrypt/templates/copycerts.sh.erb @@ -4,6 +4,7 @@ appname='<%= @app_name %>' md='<%= @md %>' ssh_options='ssh -i /root/.ssh/internal -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10' +source /data/${appname}/shared/config/env.coud if [[ $(find "/etc/letsencrypt/live/${md}/fullchain.pem" -mtime +30 -print) ]]; then diff --git a/cookbooks/letsencrypt/templates/letsencrypt.secrets.erb b/cookbooks/letsencrypt/templates/letsencrypt.secrets.erb new file mode 100644 index 00000000..6dab89be --- /dev/null +++ b/cookbooks/letsencrypt/templates/letsencrypt.secrets.erb @@ -0,0 +1,2 @@ +# Configured letsencrypt secrets for automated renewal +<%= @data %>