Skip to content

Latest commit

 

History

History
312 lines (252 loc) · 15.3 KB

advanced_usage.md

File metadata and controls

312 lines (252 loc) · 15.3 KB

Advanced Usage

This document contains information about features that are deemed "advanced", and will most likely require that you read some of the actual code to fully understand what is happening.

Signal handling

The container configures handlers for the following signals:

Signals can be sent to the container by using the docker kill command (or docker compose kill when using Docker Compose). For example, to send a SIGHUP signal to the container run:

docker kill --signal=HUP <container_name>

Manual/Force Renewal

It might be of interest to manually trigger a renewal of the certificates, and that is why the run_certbot.sh script is possible to run standalone at any time from within the container.

However, the preferred way of requesting a reload of all the configuration files is to send in a SIGHUP to the container:

docker kill --signal=HUP <container_name>

This will terminate the sleep timer and make the renewal loop start again from the beginning, which includes a lot of other checks than just the certificates.

While this will be enough in the majority of the cases, it might sometimes be necessary to force a renewal of the certificates even though certbot thinks it could keep them for a while longer (like when this happened). It is therefore possible to add "force" as an argument, when calling the run_certbot.sh script, to have it append the --force-renewal flag to the requests made.

docker exec -it <container_name> /scripts/run_certbot.sh force

This will request new certificates regardless of when they are set to expire.

⚠️ Using "force" will make new requests for all you certificates, so don't run it too often since there are some limits to requesting production certificates.

Override server_name

Nginx allows you to to do a lot of stuff in the server_name declaration, but since the scripts inside this image compose certificate requests from the same lines we are severely limited in what is possible to define on those lines. For example the line server_name mail.* would produce a certificate request for the domain name mail.*, which is not valid.

However, to combat this limitation it is possible to define a special comment on the same line in order to override what the scripts will pick up. So in this contrived example

server {
    listen              443 ssl;
    ssl_certificate_key /etc/letsencrypt/live/test-name/privkey.pem;

    server_name         yourdomain.org;
    server_name         www.yourdomain.org; # certbot_domain:*.yourdomain.org
    server_name         sub.yourdomain.org; # certbot_domain:*.yourdomain.org
    server_name         mail.*;             # certbot_domain:*.yourdomain.org
    server_name         ~^(?<user>.+)\.yourdomain\.org$;
    ...
}

we will end up with a certificate request which looks like this:

certbot --cert-name "test-name" ... -d yourdomain.org -d *.yourdomain.org

The fist server name will be picked up as usual, while the following three will be shadowed by the domain in the comment, i.e. *.yourdomain.org (and duplicate names will be removed in the final request).

The last server name is special, in that it is a regex and those always start with a ~. Since we know we will never be able to create a valid request from a name which start with that character they will always be ignored by the script (a trailing comment will take precedence instead of being ignored). A more detailed example of this can be viewed in example_server_overrides.conf.

Important to remember is that here we define a wildcard domain name (the * in the the *.yourdomain.org), and that requires you to use an authenticator capable of DNS-01 challenges, and more info about that may be found in the certbot_authenticators.md document.

Multi-Certificate Setup

This is a continuation of the RSA and ECDSA section from the Good to Know document, where it was briefly mentioned that it is actually possible to have Nginx serve both of these certificate types at the same time, thus expanding support for semi-old devices again while also allowing the most up to date encryption to be used. The setup is a bit more complicated, but the example_server_multicert.conf file should be configured so you should only have to edit the "yourdomain.org" statements at the top.

How this works is that Nginx is able to load multiple certificate files for each server block, and you then configure the cipher suites in an order that prefers ECDSA certificates. The scripts running inside the container then looks for some (case insensitive) variant of these strings in the --cert-name argument:

  • -rsa
  • .rsa
  • -ecc
  • .ecc
  • -ecdsa
  • .ecdsa

and makes a certificate request with the correct type set. See the actual commit for more details, but what you need to know is that if these options are found they override the USE_ECDSA environment variable.

Use Custom ACME URL

There are two variables available at the top of the run_certbot.sh script:

  • CERTBOT_PRODUCTION_URL
  • CERTBOT_STAGING_URL

which are used to define which server certbot will try to contact when requesting new certificates. These variables have default values, but it is possible to override them by defining environment vairables with the same name. This then enables you to redirect certbot to another custom URL if you, for example, are running your own custom ACME server.

Local CA

During the development phase of a website you might be testing stuff on a computer that either does not have a DNS record pointing to itself or perhaps it does not have internet access at all. Since certbot has both of these as requirements to function properly it was previously impossible to use this image during those particular situations.

That is why the run_local_ca.sh script was created, since this makes it possible to use a local (self-signed) certificate authority that can issue website certificates without relying on any external service or internet connection. It also enables us to issue certificates that are valid for localhost and/or IP addresses like ::1, which are otherwise impossible for certbot to create.

You can also use this solution if your intend to deploy behind a CDN and you do not need a "real" certificate in order to encrypt the communication between the origin server and the CDN's infrastructure. But please read this entire section before going forward with that kind of setup.

To enable the usage of this local CA you just set USE_LOCAL_CA=1, and this will then trigger the execution of the run_local_ca.sh script instead of the run_certbot.sh one when it is time to renew the certificates. This script, when run, will always overwrite any previous keys and certificates, so alternating between the use of a local CA and certbot without first emptying the /etc/letsencrypt folder is not supported.

The script is designed to mimic certbot as closely as reasonable, so the keys/certs created are placed in the same locations as certbot would have. This means that you only have to edit the server_name in your server configuration files to include the variant that you want for your local instance (e.g. localhost) and you should be all set.

However, if you navigate to your site at this point you will run into an error named similar to Firefox's SEC_ERROR_UNKNOWN_ISSUER, which just means that your browser does not recognize the CA that has signed your site's certificate. This is expected (since we just created our own local CA), but at this point the connection is using all the fancy HTTPS stuff, and is thus "secure", so you can just ignore this warning if you want.

Another solution is to import the local CA's certificate created by this script into your browser, thus making this a known certificate authority and any of its signed certs trusted. What this file is, and how to obtain it, is explained further in the next section.

Files and Folders

For the local CA to operate a couple of things are needed:

  • caPrivkey.pem: The private key used by the CA -> this is the most secret thing that (in a real CA's case) must be protected at all costs.
  • caCert.pem: The public certificate part of the CA -> needed by all clients in order to trust any other certificates this CA signs.
  • serial.txt: A long random hexadecimal number that is incremented by one every time a new certificate is signed by the CA.
  • index.txt: Keeps a record of all certificates that have been issued by this CA.
  • new_certs/: Folder where a copy of all the newly signed certificates are placed.

All of these are created automatically by the script inside the folder defined by LOCAL_CA_DIR (which defaults to /etc/local_ca), so by host mounting this folder you will be able to see all these files. By then taking the caCert.pem and importing it in your browser you will be able to visit these sites without the error stating that the certificate is signed by an unknown authority.

An important thing to know is that these files are only created if they do not exist. What this enables is an even more advanced usecase where you might already have a private key and certificate that you trust on your devices, and you would like to continue using it for the websites you host as well. Read more about this in the next section.

Creating a Custom CA

The validity period for the automatically created CA is only 30 days by default, and the reason for this is to deter people from using this solution blindly in production. While it is possible to quickly change this through the LOCAL_CA_ROOT_CERT_VALIDITY variable, there are security concerns regarding managing your own certificate authority which should be thought through before doing this in a production environment.

Nevertheless, as was mentioned in the previous section it is possible to supply the run_local_ca.sh script with a local certificate authority that has been created manually by you that you want to trust on multiple other devices. Basically all you need to do is to host mount your custom private key and certificate to the LOCAL_CA_DIR, and the script will use these instead of the short lived automatically created ones. Just make sure the files are named in accordance to what the script expects, and if any one of these components are missing they will be created the first time the service is started.

As of now a password protected private key is not supported.

I did not find it trivial to create a well configured CA, so if you want to go this route I really suggest that you read up on what you are doing and making sure all settings are correctly tuned for your usecase.

There is a lot of high-level information available in regards to how to create your own CA, but what I found most confusing was exactly what was expected to be inside the openssl.cnf file that is necessary to have when running most of the OpenSSL commands. The configuration that is present inside the run_local_ca.sh script should be quite minimalistic for what we need, while still providing the strict settings that some clients need else they will reject these custom certificates.

The most comprehensive guide I have found is the OpenSSL Cookbook, which goes into great detail about basically everything OpenSSL is able to do, along with this post which summarizes the settings needed for different certificate types. With these two you should be able to make an informed configuration in case you want to create your own custom certificate authority, and you may of course take a look at the commands used in the generate_ca() function to help you on your way of creating your own files.

When the long term CA is in place you can probably tune the RENEWAL_INTERVAL variable to equal something slightly less than the hard-coded 90d expiry time of the leaf certificates created. This is the expiry time recommended by Let's Encrypt (and perhaps soon mandated), and since the renewal script should always be successful you only need to run it just before the expiry time. You can, of course, run it more often than that, but it will not yield and special benefits.