From 65e599aee29e57f8bc166bb6f1982b712f863936 Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Sun, 9 Aug 2020 18:15:36 +0100 Subject: [PATCH 1/9] LetsEncrypt support --- cookbooks/ey-base/recipes/custom.rb | 5 ++ cookbooks/ey-init/recipes/integrate.rb | 1 + cookbooks/letsencrypt/attributes/default.rb | 0 cookbooks/letsencrypt/metadata.rb | 1 + cookbooks/letsencrypt/recipes/default.rb | 48 +++++++++++++++++++ .../letsencrypt/templates/copycerts.sh.erb | 43 +++++++++++++++++ cookbooks/nginx/recipes/default.rb | 12 ++++- .../templates/default/nginx_app.conf.erb | 6 +++ 8 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 cookbooks/letsencrypt/attributes/default.rb create mode 100644 cookbooks/letsencrypt/metadata.rb create mode 100644 cookbooks/letsencrypt/recipes/default.rb create mode 100644 cookbooks/letsencrypt/templates/copycerts.sh.erb diff --git a/cookbooks/ey-base/recipes/custom.rb b/cookbooks/ey-base/recipes/custom.rb index 695f8048..c892960f 100644 --- a/cookbooks/ey-base/recipes/custom.rb +++ b/cookbooks/ey-base/recipes/custom.rb @@ -9,3 +9,8 @@ if fetch_env_var(node, "EY_SIDEKIQ_ENABLED") =~ /^TRUE$/i include_recipe 'sidekiq' end + +if fetch_env_var(node, "EY_LETSENCRYPT_ENABLED") =~ /^TRUE$/i + include_recipe 'letsencrypt' +end + diff --git a/cookbooks/ey-init/recipes/integrate.rb b/cookbooks/ey-init/recipes/integrate.rb index 3248d572..234eafde 100644 --- a/cookbooks/ey-init/recipes/integrate.rb +++ b/cookbooks/ey-init/recipes/integrate.rb @@ -18,3 +18,4 @@ include_recipe "db-ssl::setup" if is_db_master end include_recipe "ssh_keys" # CC-691 - update ssh whitelist after takeovers +include_recipe "ey-base::custom" diff --git a/cookbooks/letsencrypt/attributes/default.rb b/cookbooks/letsencrypt/attributes/default.rb new file mode 100644 index 00000000..e69de29b diff --git a/cookbooks/letsencrypt/metadata.rb b/cookbooks/letsencrypt/metadata.rb new file mode 100644 index 00000000..af6a24f4 --- /dev/null +++ b/cookbooks/letsencrypt/metadata.rb @@ -0,0 +1 @@ +name 'letsencrypt' diff --git a/cookbooks/letsencrypt/recipes/default.rb b/cookbooks/letsencrypt/recipes/default.rb new file mode 100644 index 00000000..3267da5a --- /dev/null +++ b/cookbooks/letsencrypt/recipes/default.rb @@ -0,0 +1,48 @@ +letsencrypt = node['letsencrypt'] + +package "certbot" do + action :install +end + +md = fetch_env_var(node, 'EY_LE_MAIN_DOMAIN') +domain = fetch_env_var(node, 'EY_LE_DOMAINS').nil? || (fetch_env_var(node, 'EY_LE_DOMAINS').gsub(" "," -d ")) +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 + +if Dir.exist?("/data/#{app}/current") && ['solo', 'app_master'].include?(node['dna']['instance_role']) + + execute "force start haproxy / nginx" do + command "/etc/init.d/haproxy start /etc/init.d/nginx start" + 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/ --dry-run" + not_if { ::Dir.exist? ("/etc/letsencrypt/live/#{md}/") } + end +end + + managed_template "/engineyard/bin/copycerts.sh" do + owner 'root' + group 'root' + mode 0700 + source "copycerts.sh.erb" + variables( + :app_name => app, + :instances => (node.cluster - node.db_slaves - node.db_master).join(' '), + :md => md + ) + end + + execute "push certificate" do + command "/engineyard/bin/copycerts.sh" + end + + cron "renew certificates" do + command "bash -c '/engineyard/bin/copycerts.sh'" + day '1,15,29' + hour '0' + minute '0' + end +end diff --git a/cookbooks/letsencrypt/templates/copycerts.sh.erb b/cookbooks/letsencrypt/templates/copycerts.sh.erb new file mode 100644 index 00000000..ccaffe8c --- /dev/null +++ b/cookbooks/letsencrypt/templates/copycerts.sh.erb @@ -0,0 +1,43 @@ +#!/bin/bash +instances='<%= @instances %>' +appname='<%= @app_name %>' +md='<%= @md %>' +ssh_options='ssh -i /root/.ssh/internal -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=10' + + +if [[ $(find "/etc/letsencrypt/live/${md}/fullchain.pem" -mtime +30 -print) ]]; then + +certbot --debug renew > /var/log/engineyard/le-renew.log + +fi + + +function push_cert { + local target=${1} +#Pushes LE certs + + rsync --copy-links -e "${ssh_options}" /etc/letsencrypt/live/${md}/fullchain.pem ${target}:/etc/nginx/ssl/${appname}/${appname}.crt + + rsync --copy-links -e "${ssh_options}" /etc/letsencrypt/live/${md}/privkey.pem ${target}:/etc/nginx/ssl/${appname}/${appname}.key + ${ssh_options} ${target} '/etc/init.d/nginx reload' + +} + + + +function is_up() { + eval ${ssh_options} $1 'date' + [[ $? -eq 0 ]] +} + +cd ${keypath} +# copy keys to other instances +for instance in ${instances} +do + if is_up ${instance} + then + push_cert ${instance} + else + echo "Instance ${instance} is not available. Skipping." + fi +done diff --git a/cookbooks/nginx/recipes/default.rb b/cookbooks/nginx/recipes/default.rb index 7fda08b9..bf1086b9 100644 --- a/cookbooks/nginx/recipes/default.rb +++ b/cookbooks/nginx/recipes/default.rb @@ -18,6 +18,9 @@ nginx_haproxy_https_port = 8092 nginx_xlb_http_port = 8081 nginx_xlb_https_port = 8082 +letsencrypt = fetch_env_var(node, 'EY_LETSENCRYPT_ENABLED') || false + +Chef::Log.info "LetsEncrypt Enabled: #{letsencrypt}" base_port = node['passenger5']['port'].to_i stepping = 200 @@ -27,6 +30,7 @@ is_passenger = false is_unicorn = false is_puma = false +is_app_master = ['app_master'].include?(node['dna']['instance_role']) || false if stack.match(/nginx_passenger5/) @@ -135,6 +139,7 @@ :vhost => app.vhosts.first, :haproxy_nginx_port => nginx_haproxy_http_port, :xlb_nginx_port => nginx_xlb_http_port, + :app_instance => is_app_master, :upstream_port => app_base_port, :http2 => false }) @@ -151,6 +156,7 @@ :webroot => php_webroot, :vhost => app.vhosts.first, :env_name => node.engineyard.environment[:name], + :app_instance => is_app_master, :haproxy_nginx_port => nginx_haproxy_http_port, :xlb_nginx_port => nginx_xlb_http_port, :http2 => false, @@ -295,6 +301,10 @@ end end + if (!letsencrypt || !File.exists?("/data/nginx/ssl/#{app.name}/#{app.name}.key")) + + + template "/data/nginx/ssl/#{app.name}/#{app.name}.key" do owner node['owner_name'] group node['owner_name'] @@ -319,7 +329,7 @@ ) notifies node['nginx'][:action], resources(:service => "nginx"), :delayed end - + end # Add Cipher chain template "/data/nginx/servers/#{app.name}/default.ssl_cipher" do owner node['owner_name'] diff --git a/cookbooks/nginx/templates/default/nginx_app.conf.erb b/cookbooks/nginx/templates/default/nginx_app.conf.erb index b14fe6a0..7b1241db 100644 --- a/cookbooks/nginx/templates/default/nginx_app.conf.erb +++ b/cookbooks/nginx/templates/default/nginx_app.conf.erb @@ -156,6 +156,12 @@ server { # 5. Failing any caching or system maintenance, pass the request to the # application. # + + <% if !@app_instance %> + location /.well-known { + proxy_pass http://ey-app-master:8081; + } + <% end %> location / { if (-f $document_root/system/maintenance.html) { return 503; } <% if @ssl %> From 528079e6ff7aa0198d3f211d416723929587adc8 Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Sun, 9 Aug 2020 18:28:27 +0100 Subject: [PATCH 2/9] forgot to remove --dry-run --- cookbooks/letsencrypt/recipes/default.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbooks/letsencrypt/recipes/default.rb b/cookbooks/letsencrypt/recipes/default.rb index 3267da5a..38246ea5 100644 --- a/cookbooks/letsencrypt/recipes/default.rb +++ b/cookbooks/letsencrypt/recipes/default.rb @@ -18,7 +18,7 @@ if !wc execute "issue certificate" do - command "certbot certonly --noninteractive --agree-tos --register-unsafely-without-email -d #{domain} --webroot -w /data/#{app}/current/public/ --dry-run" + 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 From a68c64ad9c5160afab08be029821c5d3f19f468c Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Sun, 9 Aug 2020 20:04:59 +0100 Subject: [PATCH 3/9] added a README --- cookbooks/letsencrypt/README.md | 34 +++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 cookbooks/letsencrypt/README.md diff --git a/cookbooks/letsencrypt/README.md b/cookbooks/letsencrypt/README.md new file mode 100644 index 00000000..43bfe86a --- /dev/null +++ b/cookbooks/letsencrypt/README.md @@ -0,0 +1,34 @@ +**letsencrypt** +========== + +This installs and sets up the optional LetsEncrypt recipe on the engineyard stack. + + +**Installation** +========= + +**Prerequisites** + +* 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). + + +**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. + +To automatically create a certificate or SAN certiciate: + +* Set the following environmenta variables. `EY_LE_MAIN_DOMAIN` as your main domain e.g. `Engineyard.com` +* 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 Example.com`. + +To create a wildcard certificate: +* Set environment variable `EY_LE_DOMAINS` and `EY_LE_MAIN_DOMAIN` as the domain you wish to create wildcard certificate for +* Set environment variable `EY_LE_USE_WILDCARD` `TRUE`. +* Apply the changes to the environment to install certbot +* SSH into the solo or application master instance and run the following `certbot certonly --manual --manual-public-ip-logging-ok --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory -d *.yourdomain.com` +* Press Apply. + +**Notes** + +* Upon a takeover you will need to run the two final steps if you're using a wildcard certificate +* 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. From 87007de4b9c198b6e10079920cdb5ab068f9b83e Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Thu, 13 Aug 2020 16:08:05 +0100 Subject: [PATCH 4/9] fixes issues addressed in first comment --- cookbooks/ey-base/metadata.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/cookbooks/ey-base/metadata.rb b/cookbooks/ey-base/metadata.rb index f3779362..cca9d11a 100644 --- a/cookbooks/ey-base/metadata.rb +++ b/cookbooks/ey-base/metadata.rb @@ -30,3 +30,4 @@ depends 'redis' depends 'memcached' depends 'sidekiq' +depends 'letsencrypt' From 2c0ddd229b1cfa6ccb846aeab2537494b002128f Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Wed, 19 Aug 2020 14:44:24 +0100 Subject: [PATCH 5/9] fixes solo nginx conf --- cookbooks/nginx/recipes/default.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cookbooks/nginx/recipes/default.rb b/cookbooks/nginx/recipes/default.rb index bf1086b9..1566a6cc 100644 --- a/cookbooks/nginx/recipes/default.rb +++ b/cookbooks/nginx/recipes/default.rb @@ -30,7 +30,7 @@ is_passenger = false is_unicorn = false is_puma = false -is_app_master = ['app_master'].include?(node['dna']['instance_role']) || false +is_app_master = ['app_master','solo'].include?(node['dna']['instance_role']) || false if stack.match(/nginx_passenger5/) From 35796798735c5f78cf223c7f9fd8f2bbd64d2803 Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Sat, 15 May 2021 20:33:39 +0100 Subject: [PATCH 6/9] modified: default.rb fixed directory by formatting lowercase --- cookbooks/letsencrypt/recipes/default.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbooks/letsencrypt/recipes/default.rb b/cookbooks/letsencrypt/recipes/default.rb index 38246ea5..f4f605f1 100644 --- a/cookbooks/letsencrypt/recipes/default.rb +++ b/cookbooks/letsencrypt/recipes/default.rb @@ -4,8 +4,8 @@ action :install end -md = fetch_env_var(node, 'EY_LE_MAIN_DOMAIN') -domain = fetch_env_var(node, 'EY_LE_DOMAINS').nil? || (fetch_env_var(node, 'EY_LE_DOMAINS').gsub(" "," -d ")) +md = 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 From bd8be06dae3a0699467fd183c22a9609fc2bd781 Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Mon, 24 May 2021 18:34:03 +0100 Subject: [PATCH 7/9] Updates readme to remove wildcard --- cookbooks/letsencrypt/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cookbooks/letsencrypt/README.md b/cookbooks/letsencrypt/README.md index 43bfe86a..d2cb8ac6 100644 --- a/cookbooks/letsencrypt/README.md +++ b/cookbooks/letsencrypt/README.md @@ -21,14 +21,15 @@ To automatically create a certificate or SAN certiciate: * Set the following environmenta variables. `EY_LE_MAIN_DOMAIN` as your main domain e.g. `Engineyard.com` * 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 Example.com`. -To create a wildcard certificate: -* Set environment variable `EY_LE_DOMAINS` and `EY_LE_MAIN_DOMAIN` as the domain you wish to create wildcard certificate for -* Set environment variable `EY_LE_USE_WILDCARD` `TRUE`. -* Apply the changes to the environment to install certbot -* SSH into the solo or application master instance and run the following `certbot certonly --manual --manual-public-ip-logging-ok --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory -d *.yourdomain.com` -* Press Apply. + + **Notes** * Upon a takeover you will need to run the two final steps if you're using a wildcard certificate * 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. + + +**Upcoming Changes** + +Wildcard integration From 7641d1248db7538fd4ff0e45dc0fcf98d2734c6c Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Mon, 31 May 2021 16:46:47 +0100 Subject: [PATCH 8/9] Update README.md Updated readme with more in-depth documentation --- cookbooks/letsencrypt/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cookbooks/letsencrypt/README.md b/cookbooks/letsencrypt/README.md index d2cb8ac6..0967976d 100644 --- a/cookbooks/letsencrypt/README.md +++ b/cookbooks/letsencrypt/README.md @@ -18,8 +18,9 @@ There are several environmental variables needed to be used with the LetsEncrypt To automatically create a certificate or SAN certiciate: +* Make sure a certificate is applied to the environment using the documentation above. From example you can use a self-signed one called `using-letsencrypt` * Set the following environmenta variables. `EY_LE_MAIN_DOMAIN` as your main domain e.g. `Engineyard.com` -* 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 Example.com`. +* 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`. @@ -28,6 +29,7 @@ To automatically create a certificate or SAN certiciate: * Upon a takeover you will need to run the two final steps if you're using a wildcard certificate * 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` **Upcoming Changes** From 582456074724ae520601d710957f32cdcf628da5 Mon Sep 17 00:00:00 2001 From: Callum Pease Date: Thu, 3 Jun 2021 17:16:35 +0100 Subject: [PATCH 9/9] Update README.md --- cookbooks/letsencrypt/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/cookbooks/letsencrypt/README.md b/cookbooks/letsencrypt/README.md index 0967976d..5143f446 100644 --- a/cookbooks/letsencrypt/README.md +++ b/cookbooks/letsencrypt/README.md @@ -9,26 +9,25 @@ This installs and sets up the optional LetsEncrypt recipe on the engineyard stac **Prerequisites** -* 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 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) **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. +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 To automatically create a certificate or SAN certiciate: -* Make sure a certificate is applied to the environment using the documentation above. From example you can use a self-signed one called `using-letsencrypt` -* Set the following environmenta variables. `EY_LE_MAIN_DOMAIN` as your main domain e.g. `Engineyard.com` -* 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`. +* 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` +* 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` **Notes** -* Upon a takeover you will need to run the two final steps if you're using a wildcard certificate -* 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. +* 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`