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

Published on, March 04, 2020

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 websites (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 a 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 the console:

sudo apt install ruby


acme-client Ruby gem is also required. Install it by entering this command in the 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 a private key and registering the 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, the key used to register the 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 a 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 the 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