I go out of my way to secure all my sites with a valid https certificate. I’m also fairly cheap, this left me with a dilemma and AWS’s certificate manager used to be my only option. It was handy but I was effectively stuck on AWS and had to use an Elastic Load Balancer, ELB, to terminate my TLS connections. Before we go too deep here if any of the terms I’m using are a little ambiguous I’d recommend checking out this article from symantec on the meaning of SSL, TLS, and https. The first section covers the definitions well enough that hopefully you feel comfortable with the difference between the terms.
Concepts
Let’s Encrypt
Let’s Encrypt is a free service that allows you to programmatically generate TLS certificates. It’s not always super well documented or easy to use but once you get it in place you can generate, use, and renew certificates on a regular basis for free. On top of that Let’s Encrypt pioneered a new IETF standard for programmatically doing Domain Validation. I’ll get into how and why that works right now.
Cert-Manager
cert-manager is a kubernetes service that will interact with LetsEncrypt, or another CA, for you to programmatically request, generate, and renew certificates. We’re going to limit our scope today to working with Let’sEncrypt. Go here to ignore some of this guide and just install the latest cert-manager. They’re moving to a non default helm repo and have their own getting started instructions. I’ll also cover it in more detail in the Getting to the Code section.
ACME and ACMEv2
Let’s Encrypt originally came out with a protocol they called ACME, which was a mechanism for programmatically doing domain validation. This year they got ACME accepted as an IETF standard and they called it ACMEv2. From a practical standpoint ACME/ACMEv2 supports two mechanisms for domain validation.
For Let’sEncrypt we want to be careful to manage our interactions with the API. Let’sEncrypt provides a free service to anyone that wants certificates. Awesome right? The downside is they throttle requests against their production APIs. Let’sEncrypt has been changing their rate limits as they mature their infrastructure, you can keep up to date with their rate limits here. The impact of that is that you don’t want to test that your issuer and request are valid against the production API. In order to validate your issuer and request before hitting the production API Let’sEncrypt provides a staging API that allows you to happily mess up as much as you like. Clarification the staging API is also rate limited but it’s much more forgiving than the prod API, please don’t spam the staging API.
DNS
DNS Validation, covered in section 8.4 of the IETF RFC, is when you prove you’re authoritative for the domain by creating a custom DNS entry.
HTTP
HTTP Validation, covered in section 8.3 of the IETF RFC, involves putting a specific file on a web server at a specific URL.
Issuers
Issuers refer to the service that will act to issue a given certificate. Ultimately your CA issues the actual certificate. The issuer in the context of cert-manager refers to the broker between your kubernetes cluster and the CA.
In order to get your issuer up and running correctly you need to pick a domain validation method. Let’sEncrypt offers free Domain Validation (DV) certs which basically just check that the entity requesting the certificate has effective control of the domain in question. If that doesn’t sound like a whole ton of validation you’re right, it’s not. Troy Hunt among others actually has a lot of good talk tracks on what exactly SSL/TLS do, and more importantly don’t do, to secure a given site. The long and the short of it being a TLS connection makes it really hard to snoop on the traffic between a browser and a site. That’s it.
Back to DV: Let’sEncrypt has been pioneering programatic ways to do Domain Validation and they recently got ACMEv2 adopted as an IETF standard. With cert-manager you have two options for DV, HTTP and DNS.
Certificates
Certificates refer to the actual certificate you intend to generate. Basically what URL/Host are you trying to secure and where do you want to store your secret? I honestly prefer to think of these objects as requests as the actual certificate is stored in kubernetes as a kubernetes secret but that doesn’t really matter.
Code
This guide is working with cert-manager version 0.7.0, for the latest docs checkout cert-manager’s site.
Installing cert-manager
The helm chart has a good installation guide, which I wont spend a ton of time talking about here.
We need to first apply the cert-manager CRDs.
kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.7/deploy/manifests/00-crds.yaml
Next create the cert-manager namespace, kubectl create namespace cert-manager
, although personally I create and maintain a yaml version of the namespace that I can apply in bulk as necessary. Once the namespace is up you need to apply a label to disable cert-manager validation. kubectl label namespace cert-manager certmanager.k8s.io/disable-validation="true"
For the namespace definition I use:
---
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
labels:
certmanager.k8s.io/disable-validation: "true"
If you’re using helm you can complete the install with this:
## Add the Jetstack Helm repository
helm repo add jetstack https://charts.jetstack.io
# Update your local Helm chart repository cache
helm repo update
## Install the cert-manager helm chart
helm install \
--name cert-manager \
--namespace cert-manager \
--version v0.7.0 \
jetstack/cert-manager
Configuring a Cluster Issuer
I start from the assumption that I won’t run multi tenant clusters so I always build out cluster issuers as opposed to creating individual issuers by namespace.
In order to test out my issuer I create a ClusterIssuer that will connect to the staging API. I modify the spec.acme.server
to use the staging API, https://acme-staging-v02.api.letsencrypt.org/directory. The full staging issuer is included below.
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-stage # Arbitrary string, you'll reference this when you create a certificate
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory # LetsEncrypt API URL
email: jason@59s.io # Your email address
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-stage # Arbitrary string
# ACME DNS-01 provider configurations
dns01:
# Here we define a list of DNS-01 providers that can solve DNS challenges
providers:
- name: dns # arbitrary string, you'll reference this later in your request.
route53:
region: us-east-1 # Region of the zone
hostedZoneID: IMAVALIDHOSTEDZONEID
# optional if ambient credentials are available; see ambient credentials documentation
accessKeyID: IMAVALIDAWSACCESSKEY
secretAccessKeySecretRef: # Because we're using a cluster issuer this secret, with the properties you see below, need to be placed in the cert-manager namespace.
name: secret-name
key: property-name
Using Staging
Use staging. Until you’re super comfortable that your certificate request and issuer work well stick with the staging API. The staging API will generate an invalid certificate and store it in your kubernetes cluster. You don’t actually want to use the staging certificate for anything other than to validate that your issuer and certificate request are valid.
Configuring a certificate request
Once your issuer is up and running you’ll request a certificate. Again, we start with the staging API then swap over to the prod API later.
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: 59s-io-stage # Arbitrary string
namespace: n1
spec:
secretName: 59s-io-tls-stage # Arbitrary string, name of the secret you want to create
issuerRef:
name: letsencrypt-stage
kind: ClusterIssuer
commonName: '*.59s.io'
dnsNames:
- 59s.io
acme:
config:
- dns01:
provider: dns # use the provider name from your issuer
domains:
- '*.59s.io' # Super sweet wildcard certificates let me use a single ingress for all my sites. LetsEncrypt started issuing wildcard certs back in 2018.
- 59s.io
I like to watch the logs at this point to see what cert-manager is doing and ensure it’s able to issue my certificate. Once it’s issued you can begin migrating over to the production API.
Moving to the Production API
We’re going to effectively copy the issuer and certificate request from above. Swap out the names so that prod replaces staging. Also we need to update the URL.
Cluster Issuer
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod # Arbitrary string, you'll reference this when you create a certificate
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory # LetsEncrypt API URL
email: jason@59s.io # Your email address
# Name of a secret used to store the ACME account private key
privateKeySecretRef:
name: letsencrypt-prod # Arbitrary string
# ACME DNS-01 provider configurations
dns01:
# Here we define a list of DNS-01 providers that can solve DNS challenges
providers:
- name: dns # arbitrary string, you'll reference this later in your request.
route53:
region: us-east-1 # Region of the zone
hostedZoneID: IMAVALIDHOSTEDZONEID
# optional if ambient credentials are available; see ambient credentials documentation
accessKeyID: IMAVALIDAWSACCESSKEY
secretAccessKeySecretRef: # Because we're using a cluster issuer this secret, with the properties you see below, need to be placed in the cert-manager namespace.
name: secret-name
key: property-name
Certificate Request
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
name: 59s-io-prod # Arbitrary string
namespace: n1
spec:
secretName: 59s-io-tls-prod # Arbitrary string, name of the secret you want to create
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: '*.59s.io'
dnsNames:
- 59s.io
acme:
config:
- dns01:
provider: dns # use the provider name from your issuer
domains:
- '*.59s.io' # Super sweet wildcard certificates let me use a single ingress for all my sites. LetsEncrypt started issuing wildcard certs back in 2018.
- 59s.io
Conclusion
Use certificates to secure your sites. They’re free, the technical burden of requesting and maintaining them is relatively low, and the process can be fully automated using tools like cert-manager. Also Kubernetes has mechanisms to simplify the process of issuing, maintaining, and requesting certificates.
I’ll do a follow on post on using a wildcard certificate with an NGINX ingress and a wildcard DNS entry and wildcard certificate to automatically secure any sites you want to build on a given cluster.