Let's Encrypt interface for Multi-tenancy applications which respond by on many domains/subdomains (like Apartment)
If you have a single server which responds to many different domains, getting Let's Encrypt to provide you with a multi-domain Certificate is possible, but a lot of work.
Note: This only works up to 100 domains (https://letsencrypt.org/docs/rate-limits/) Reference: https://community.letsencrypt.org/t/host-multiple-domains-with-a-single-certificate/20917/2
Note: Example usage with real server. Apartment gem with subdomains. Reference: https://github.com/influitive/apartment#switch-on-subdomain
The goal of this gem is to solve the following problems:
- Make it easy to start using let's encrypt for multiple domains on one server
- Make it easy to periodically refresh a certificate which handles many domains
- Make it possible to add a new custom DNS entry which refers to the server, and request a cert which now also covers that new domain.
- Make it easy to request a wildcard cert as well as individual domain certs
- Make it resilient, if a DNS record is removed, handle that by removing that domain from list requested for the cert.
Example Situation:
- Your application is known as site.example.com
- You allow users to create new accounts, and assign each account a separate subdomain,
- e.g. alice.site.example.com, bob.site.example.com, charlie.site.example.com
- You allow users to also whitelabel the service by buying their own domains, and setting up CNAME records:
- e.g. www.alice.com -> CNAME: alice.site.example.com
- e.g. bobrocks.com -> CNAME: bob.site.example.com
What can ApartmentAcmeClient do?
- Create a single Let's Encrypt SSL Certificate which covers all of:
- site.example.com
- *.site.example.com (which covers alice.site.example.com, bob.site.example.com, charlie.site.example.com)
- www.alice.com
- bobrocks.com
In order to provide a secure connection, we are using letsencrypt.org to
automatically create ssl certificates for the various domains which the server will run on. But, we are doing the validation/registration through the acme-client
gem instead of using the lets-encrypt binary.
Periodically, we check all configured domains, and re-configure the nginx server to properly respond to any newly configured domain names. If we have a new domain name, we also request a new SSL certificate, enabling HTTPS for that domain.
- A list of domains which are served by this server is created.
- The list of all these domains is used to determine which ones are properly configured in DNS.
- We
authorize
each domain with LetsEncrypt - A new SSL certificate is requested and installed3
- Nginx is restarted to pick up the new certificate.
rake encryption:create_crypto_client
- Register an account with LetsEncryptrake encryption:renew_and_update_certificate
- Authorize/create certificatesrake encryption:update_nginx_config
- re-write the nginx file to point at the certificates
At this point, the only thing necessary is to run rake encryption:renew_and_update_certificate
on a regular basis, which will find new domains, authorize them, and get new SSL certs for them.
See below for a detailed explanation of "First Time Setup"
When setting this up the first time, it is recommended that you enable test-mode:
# in config/initializers/apartment_acme_client.rb
ApartmentAcmeClient.lets_encrypt_test_server_enabled = true
so that all your requests are made against the test Let's Encrypt server.
This will also cause your DER and PEM files to be prefixed with "test_" to make it possible to have REAL and FAKE certs in parallel
Once you have an SSL Cert installed which is doing everything correctly (except not from the "REAL" server) you can restart the process.
- Set
ApartmentAcmeClient.lets_encrypt_test_server_enabled = false
start at step 1 (rake encryption:create_crypto_client
)....
In order for the application to function properly, it is assumed that the application is running in the following configuration:
- Nginx running as a service with a socket tunnel to the rails application
- Nginx can be restarted by
sudo service nginx restart
- Rails application running, which can serve files from a
/public
-like directory
Add this line to your application's Gemfile:
gem 'apartment_acme_client'
And then execute:
$ bundle
Or install it yourself as:
$ gem install apartment_acme_client
We do this so that we can verify the site responds to a URL before we ask Lets Encrypt to verify the site.
mount ApartmentAcmeClient::Engine => '/aac' # you can define whatever path you want to mount the engine
Create an initializer for the client. Usually config/initializers/apartment_acme_client.rb
Add the following configuration entries
Define the code which will list the domains to check.
# Should return an array of domains (without http/https prefixes)
# It can be a straight array, or a callable object
# These should be all of the domains which are NOT
# covered by the wildcard settings
ApartmentAcmeClient.domains_to_check = -> { SomeModel.all.map(&:custom_domain) }
ApartmentAcmeClient.wildcard_domain = "site.example.com" # optional element
# e.g.
# ApartmentAcmeClient.domains_to_check = ["example.com", "alice.example.com", "alice.com"]
You can request a wildcard certificate for a domain (or a subdomain). In order to do this, the system must be able to write to the DNS provider.
Currently, only Route53 is supported as a DNS provider, and we use an upsert
to write a TXT record to the system, in order to prove that we control the DNS for the domain.
If you specify wildcard_domain
(the domain on which to request a wildcard cert), we will request a wilcard cert for *.<wildcard_domain>
, and use AWS Route53 API to perform the domain-authorization.
The necessary permissions to be able to update the Route53 records for wildcard-cert update are:
- route53:ListHostedZones
- route53:ChangeResourceRecordSets
This is used to identify the certificate requested, and should be the same from week-to-week.
This should be a URL which you control the DNS for, ensuring that it will ALWAYS be pointing at your application. (ie: not subject to the whims of your users).
Note: The nginx configuration will be configured to respond to common_name
and *.common_name
sources.
ApartmentAcmeClient.common_name = "example.com"
Specify where to put the "challenge" files which can be fetched by let's encrypt when validating the domains
Note: this folder should be not be derived from Rails.root, because that is a sym-link, which changes release to release.
ApartmentAcmeClient.public_folder = "/home/ec2-user/app/current/public" # not: Rails.root.join('public')
Directory where to store certificates locally. This folder must persist between deployments, so that nginx can reference it permanently.
ApartmentAcmeClient.certificate_storage_folder = "/home/ec2-user/app/current/public/system" # not: Rails.root.join("public", "system")
If you are using capistrano for deployments, add public/system to your linked_dirs
# deploy.rb
set :linked_dirs, %w[public/system]
Each time a certificate is requested from Let's Encrypt, we also store it in S3 in case something happens to the server/filesystem.
In order for this to work, you must specify the aws_region and aws_bucket
ApartmentAcmeClient.aws_region = Rails.application.secrets.aws_region
ApartmentAcmeClient.aws_bucket = Rails.application.secrets.aws_bucket
It is assumed that the /etc/nginx/nginx.conf file has a line like:
http {
# Many lines....
include /etc/nginx/conf.d/*.conf;
}
Then, the site's configuration is actually stored in /etc/nginx/conf.d/site.conf
So, the nginx_config_path would be
ApartmentAcmeClient.nginx_config_path = "/etc/nginx/conf.d/site.conf"
Assuming that your application is running unicorn with a socket.
Example:
# workers
worker_processes 1
# listen
listen "/tmp/unicorn-application.socket", backlog: 64
# Many more lines....
ApartmentAcmeClient.socket_path = "/tmp/unicorn-application.socket"
If you ever choose to enable force-ssl on your server, you will need to set
the ApartmentAcmeClient.verify_over_https = true
so that verification checks occur
over https instead of http
- Register with Let's Encrypt
Before we can make requests to Let's encrypt, we need to create a private key, which we will use for all future requests to Let's encrypt. To do this, run rake encryption:create_crypto_client[my_email@example.com]
(replacing the email address with yours)
This will create a new private key, store it on S3, and register that key with let's encrypt for your e-mail address.
- Create your initial certificate
Initially, your nginx configuration will not reference any ssl certificate files, because you don't have any.
So the first thing you must do is request an initial certificate using rake encryption:renew_and_update_certificate
Once this is done, the newly acquired certificate will be stored on the server, for use by nginx in step 3.
- Tell Nginx where to get it's SSL certificates
The Nginx configuration must be updated to point to the SSL Certificate location.
run rake encryption:update_nginx_config
in order to write the ngnix configuration file, and restart the nginx service.
At this point, the only thing necessary is to run rake encryption:renew_and_update_certificate
on a regular basis, which will find new domains, authorize them, and get new SSL certs for them. It will also restart nginx, to have it pick up the new certificate.
Each week, the certificates should be renewed. We have provided 2 ways to do this.
straight invocation:
ApartmentAcmeClient::RenewalService.run!
we provide a helper rake task:
rake "encryption:renew_and_update_certificate"
Please use whatever scheduling service you wish in order to ensure that this runs periodically. e.g. whenever
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/rdunlop/apartment_acme_client.
The gem is available as open source under the terms of the MIT License.
- Depends on the
aws-sdk-s3
S3 gem version "~> 1". - It expects the hosting application has configured the AWS credentials.
e.g.:
Aws.config.update(
region: Rails.application.secrets.aws_region,
credentials: Aws::Credentials.new(
Rails.application.secrets.aws_access_key,
Rails.application.secrets.aws_secret_access_key
)
)