Creating and Using openssl certificates
When dealing with securing connections and utilizing that desired https connection there are a few options.
- Use self-signed certificates
- Use a Certificate Authority (CA)
- Use a reverse proxy from a CA
Option 1 is easy, just create the certificate and sign it with the key and distribute to other machines, however, when it comes to replacing these certificates and managing multiple services the overhead builds up very fast.
Option 2 is more advanced because you can either create your own CA then use the root certificate to distribute only one certificate as services can have their own certificates signed by the CA. Or you can just use a tool like Let's Encrypt to handle the certificate creation, signing, and deployment for you.
Option 3 is very aligned with option 2 except you don't have to make a certificate for every service as all connections are shielded by the reverse proxy, therefore, the only place you have to secure are the connections to the reverse proxy. Obviously this is a gross exaggeration of what it actually does but that's the gest of it.
The best way to implement certificates from my understanding is utilizing a reverse proxy to frontload all the https connection burdens then use a CA from Let's Encrypt to allow your sites to be verified by the greater internet. However, there are scenarios where this isn't feasible, specifically the use of Let's Encrypt and tools like it that require an internet connection.
I'm currently working on an air-gapped cluster and came across this issue of needing an https connection. I couldn't just use a tool like Let's Encrypt because that requires an internet connection, something my cluster will not have. At least for the automation to work it must have some type of https connection. Therefore, I had to get my hands dirty with openssl and its commands to create an https connection. Now I have a pretty good grasp of certificates and hopefully this documentation allows you to do the same.
Creating the root Certificate Authority (CA)
There are two steps, creating the root key for the CA and the root certificate of the CA. The key is how we generate root certifcates and sign other certificates. The key is essential to this system and should be PROTECTED at all costs or else it allows bad actors to compromise/mimic your domain.
This is how you generate a key, where CA is the name of the key:
openssl genrsa -out {CA}.key 4096
- Note: -aes256 asks for a passphrase which will be required to use, everytime the key is used. This provides extra security if a key is to be compromised somehow, this can also be used to lockdown certificates following this.
- Relevant commands to lockdown these certficates with password files are:
- -passout
- -passin
This is generating a root certificate with the root key:
openssl req -x509 -new -nodes -key {server}.key -sha256 -days 1825 -out {CA}.crt
- req is what creates self-signed certificates, in this case we making a self-signed CA
- -x509 is the standard we use to manage certificates
- -new specifies we're generating a new certificate, ensures unique
- -nodes specifies we're not using a passphrase; DO NOT USE THIS IN PRODUCTION
- A passphrase provides extra protection if a root certificate is compromised, please use one in production
- -key specifies hte location of the key
- -sha256 is the algorithm encrypting the certficate
- -days is the amount of days until expireation date
- -out is the name of the root certificate
Creating the Service Certificate
To create a service certificate we generate its key and certificate with the following:
openssl genrsa -out {service}.key 4096
openssl req -new -key {service}.key -out {service}.csr
CSR stands for certificate signing request, which we use to sign with a root CA.
Before we sign this certificate, we have to know our domains. There are a lot of magic that could be done with a config file, but I used it to specify the domain and ip to recognize the root certificate.
San.conf:
[ req_ext ]
subjectAltName = @alt_names
[ alt_names ]
IP.1 = 192.168.1.1
DNS.1 = node1
Here I'm defining a field called [req_ext] which we use to speify later in the command so we only have to reference that field, this is useful if we have multiple values or fields. I'm sure this can be used to substitute many of hte command line options, but for now I only expirmeneted with this to give subjectAltName. I use an ip and hostname to specify the service, however, it's always better to use a FQDN, since I haven't implemented a FQDN I'm using the ip and hostname instead.
openssl x509 -req -in {service}.csr \
-CA {CA}.crt \
-CAkey {CA}.key \
-CAcreateserial \
-extfile san.conf \
-extensions req_ext \
-out {service}.crt \
-days 365 \
-subj "/CN=192.168.1.1"
Command breakdown:
- x509 -req is what we use to manage our certificates
- -in is inputting our certificate signing request (csr) for our service
- -CA specifies the root certificate
- -CAkey specifies the root key
- -CAcreateserial guarantees a unique id for this certificate, so conflicting ids won't occur
- -extfile specifies the config file that we created earlier
- -extensions specifies the section we need to use in the config file like [req_ext]
- -out is the output file
- -days is the amount of days before expiration
- -subj is how we specify the CN field, this must be used with the san.conf file to work properly
Distributing the CA Certificate
For any system to recognize the validity of our services, we need to install the CA certificate to the client so it's used to verify the service.
We do this by copying our root certificate to the following directory: /usr/local/share/ca-certificates
sudo cp {CA}.crt /usr/local/share/ca-certificates
Then we update the certificates trusted by the client system by running:
sudo update-ca-certificates
Testing
- The main way of testing is using the curl command tool with the verbose option. If your certificate works in the verbose output it should mention the use of a certificate and even show details that it saw.
curl -v https://[YOUR IP HERE]
- For me I inserted the certificate into a private registry to host my own images without using DockerHub. I then opened the registry on port 443 and used the following to test if I can access the registry:
curl -v https://192.168.1.1/v2/_catalog
If the CA certificate is installed on the client, the service certificate signed and installed properly in the server, then the command above should state a success and show content queried, in my case I was able to see the registries hosted by my private registry using the https connection.
- Note, there are different methods of installing a certificate for the server to present. On some applications you simply need to place it in a directory like my private registry where I simply placed it in a directory that the registry will look in to present necessary certificates. On others, you may have to specify where to find it, or configure the application to use it. Please research on how to deploy it as I only cover how to make certificates, and get the client to recognize the certificate by installing the root certificate.
Conclusion
-
I learned a lot from getting this to work. For the longest time, I wanted to figure out how to enable a https connection, and thought of using Let's Encrypt so I can host my own websites using a https connection. However, being forced to do it manually within an air-gapped environment for a service that is not a website has taught me a lot about certificates and being able to make them, and how they work. This makes me more curious on how larger systems handle this type of work, or if its usually offloaded to another company. Either way, I have a strong grasp of how the certificates are used to enable and verify connections via https now.
-
I also want to explore a reverse proxy in the future, where instead of deploying on the service I instead deploy only on this reverse proxy endpoint so all services can use it to enable https traffic without having to issue a certificate for every single service I have deployed.