How to fully automate renewing of Let’s Encrypt certificates for multiple sites with Ruby and Let’s Encrypt ACMEv2 protocol

Published on, Mar 04

Let’s Encrypt has announced in the beginning of year, that it will no longer support ACMEv1 protocol for certificate renewal after June 1. 2020. At my work I run 4 Rails application instances with 12 web sites (domains) and have been using Let’s Encrypt certificates for years. They are automatically renewed every month. All I get is a mail indicating that certificates have been renewed.
 

There are enough differences from ACMEv1 to ACMEv2 protocol, that I had to go back to school and rewrite the whole procedure. It took me few hours to figure it all out and maybe it will save some time for you.
 

Requirements

 

All examples have been tested and they run on Ubuntu Linux. Since Ruby runs the same on all platforms, there shouldn't be any problem running it on other platforms.


Ruby interpreter must be installed. If not already installed it can be installed by entering this command in console:

sudo apt install ruby


acme-client gem is also required. Install it by entering this command in console:

sudo gem install acme-client


Register with Let’s Encrypt certification authority

 

First we have to register with Let’s Encrypt. We do this by generating private key and registering key with Let’s Encrypt. We need to register only once. Save code below to register.rb and run it with
 

ruby register.rb

 

require 'acme-client'
require 'openssl'

client_key = OpenSSL::PKey::RSA.new(4096)
client = Acme::Client.new(private_key: client_key, directory: 'https://acme-v02.api.letsencrypt.org/directory')
account = client.new_account(contact: 'mailto:mail@drgcms.org', terms_of_service_agreed: true)

File.write("lets-encrypt.key", client_key.to_pem )

 

At the end of the procedure, key used to register account, is saved to file. It will be used for future conversations with certification authority.

 

Renew certificate

 

Below is the code for certificate renewal. Save it to renew_certificate.rb and run it with:

 

ruby renew_certificate.rb

 

require 'fileutils'
require 'acme-client'
require 'openssl'
apps =
  { 'drgcms' => {
      dir: '/path_to/drgcms', domains: %w[www.drgcms.org tulips.drgcms.org] }
  }

client_key = OpenSSL::PKey::RSA.new( File.read('lets-encrypt.key') )
client = Acme::Client.new(private_key: client_key, directory: 'https://acme-v02.api.letsencrypt.org/directory')

apps.each  do| app, domains |
  p '',"Renewing APP: #{app}"
  order = client.new_order(identifiers: domains[:domains])
  order.authorizations.each do |authorization|
    p ['validating', authorization.domain]
    challenge = authorization.http
 
    # write challange data to challenge.filename
    FileUtils.mkdir_p( File.join( domains[:dir], 'public', File.dirname( challenge.filename ) ) )
    File.write( File.join( domains[:dir], 'public', challenge.filename), challenge.file_content )

    # validate single domain
    challenge.request_validation
    while challenge.status == 'pending'
      p ['challenge.status', challenge.status]
      sleep(2)
      challenge.reload
    end
    p challenge.status # => 'valid'
  end

  # get certificate
  private_key = OpenSSL::PKey::RSA.new(4096)
  csr         = Acme::Client::CertificateRequest.new(private_key: private_key, names: domains[:domains])
  order.finalize(csr: csr)
  while order.status == 'processing'
    sleep(1)
    challenge.reload
  end
  # save certificate and private key
  if (certificate = order.certificate)
    File.write("#{app}-pkey.pem", private_key.to_pem )
    File.write("#{app}-cert.pem", certificate)
  else
    send_mail "Error renewing Lets Encrypt certificates for application #{app}"
  end
end

# move certificates and restart web service
system "mv *.pem /etc/ssl/nginx"
system 'service nginx restart'
send_mail 'Lets Encrypt certificates renewed succesfully'

First we have apps variable, which defines single application name (drgcms) and holds two options. Path to application root (:dir) and domains list for the application (:domains).

 

After that, we introduce program to service and get client variable for communicating with service.

 

Now we make authorization loop. We provide certificate for each application by placing a certificate order. On every order each domain must be authorized. Authorization can be done either by dns or http. Our example uses http authorization. 

 

Http authorization validation is done by putting challenge filename with challenge data into application’s public directory. Certificate authority checks, if challenge filename exists and it contains provided data.

 

When all domains are validated, we create new private key and make certificate request. Again when everything is OK, certificate and private key are saved to file system.

 

At the end, if everything is OK, I move all certificate files to /etc/ssl/nginx directory, restart nginx service and send information mail, that certificates have been renewed.

 

That’s it. Don’t forget that if you are still using ACMEv1 protocol your certificate renewal procedure will fail after june 1.

 

Comments