Security in one's information system has always been among the most critical Non-Functional Requirements. Transport Secure Layer, aka TLS, formerly SSL, is among its many pillars. In this post, I'll show how to configure TLS for Apache .
TLS in a few words
TLS offers several capabilities:
- Server authentication: the client is confident that the server it exchanges data with is the right one. It avoids sending data, which might be confidential, to the wrong actor
- Optional client authentication: the other way around, the server only allows clients whose identity can be verified
- Confidentiality: no third party can read the data exchanged between the client and the server
- Integrity: no third party can tamper with the data
TLS works through certificates. A certificate is similar to an ID, proving the certificate's holder identity. Just like an ID, you need to trust who delivered it. Trust is established through a chain: if I trust Alice, who trusts Bob, who in turn trusts Charlie, who delivered the certificate, then I trust the latter. In this scenario, Alice is known as the root certificate authority.
TLS authentication is based on public key cryptography. Alice generates a public key/private key pair and publishes the public key. If one encrypts data with the public key, only the private key that generated the public key can decrypt them. The other usage is for one to encrypt data with the private key and everybody with the public key to decrypt it, thus proving their identity.
Finally, mutual TLS, aka mTLS, is the configuration of two-way TLS: server authentication to the client, as usual, but also the other way around, client authentication to the server.
We now have enough understanding of the concepts to get our hands dirty.
Generating certificates with cert-manager
A couple of root CA are installed in browsers by default. That's how we can browse HTTPS websites safely, trusting that https://apache.org is the site they pretend to be. The infrastructure has no pre-installed certificates, so we must start from scratch.
We need at least one root certificate. In turn, it will generate all other certificates. While it's possible to do every manually, I'll rely on cert-manager in Kubernetes. As its name implies, cert-manager is a solution to manage certificates.
Installing it with Helm is straightforward:
helm repo add jetstack https://charts.jetstack.io #1helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ #2 --create-namespace \ #2 --version v1.11.0 \ --set installCRDs=true \ --set prometheus.enabled=false #3
- Add the charts' repository
- Install the objects in a dedicated namespace
- Don't monitor, in the scope of this post
We can make sure that everything works as expected by looking at the pods:
kubectl get pods -n cert-manager
cert-manager-cainjector-7f694c4c58-fc9bk 1/1 Running 2 (2d1h ago) 7dcert-manager-cc4b776cf-8p2t8 1/1 Running 1 (2d1h ago) 7dcert-manager-webhook-7cd8c769bb-494tl 1/1 Running 1 (2d1h ago) 7d
cert-manager can sign certificates from multiple sources: HashiCorp Vault, Let's Encrypt, etc. To keep things simple:
- We will generate our dedicated root certificate, i.e.,
Self-Signed
- We won't handle certificates rotation
Let's start with the following:
apiVersion: cert-manager.io/v1kind: ClusterIssuer #1metadata: name: selfsigned-issuerspec: selfSigned: {}---apiVersion: v1kind: Namespacemetadata: name: tls #2---apiVersion: cert-manager.io/v1kind: Certificate #3metadata: name: selfsigned-ca namespace: tlsspec: isCA: true commonName: selfsigned-ca secretName: root-secret issuerRef: name: selfsigned-issuer kind: ClusterIssuer group: cert-manager.io---apiVersion: cert-manager.io/v1kind: Issuer #4metadata: name: ca-issuer namespace: tlsspec: ca: secretName: root-secret
- Certificate authority that generates certificates cluster-wide
- Create a namespace for our demo
- Namespaced root certificate using the cluster-wide issuer. Only used to create a namespaced issuer
- Namespaced issuer. Used to create all other certificates in the post
After applying the previous manifest, we should be able to see the single certificate that we created:
kubectl get certificate -n tls
NAME READY SECRET AGEselfsigned-ca True root-secret 7s
The certificate infrastructure is ready; let's look at Apache .
Quick overview of a sample Apache architecture
Apache is an API Gateway. By default, it stores its configuration in etcd, a distributed key-value store - the same one used by Kubernetes. Note that in real-world scenarios, we should set up etcd clustering to improve the resiliency of the solution. For this post, we will limit ourselves to a single etcd instance. Apache offers an admin API via HTTP endpoints. Finally, the gateway forwards calls from the client to an upstream. Here's an overview of the architecture and the required certificates:
Let's start with the foundational bricks: etcd and Apache . We need two certificates: one for etcd, in the server role, and one for Apache , as the etcd client.
Let's set up certificates from our namespaced issuer:
apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: etcd-server #1 namespace: tlsspec: secretName: etcd-secret #2 isCA: false usages: - client auth #3 - server auth #3 dnsNames: - etcd #4 issuerRef: name: ca-issuer #5 kind: Issuer---apiVersion: cert-manager.io/v1kind: Certificatemetadata: name: -client #6 namespace: tlsspec: secretName: <span class="token punctuation" style="color: rg