Secure k3s Cluster with cert-manager using arkade
In previous post I described how you could setup your own k3s cluster using a few Raspberry Pis. In this post I will show you how to secure the cluster by adding TLS to the ingress routes.
This will be achieved by setting up our own certificate authority and using cert-manager to issue certificates for the ingress routes.
Step 1, Install cert-manager
cert-manager is a tool that automates certificate management for Kubernetes clusters. It supports CA, Hashicorp Vault, Venafi and ACME based signing authorities. We will be using the CA issuer.
There are a bunch of different ways to install cert-manager, basic manifests and helm charts to name a couple. We will be using arkade, a super-simple utility written by Alex Ellis for installing helm charts and applications to Kubernetes clusters.
arkade can be installed using the following command, beware it uses sudo
to move the binary to /usr/local/bin
.
If you don’t like to run arbitrary scripts from the internet as root then you can run it unprivileged and move the binary manually yourself.
curl -sLS https://dl.get-arkade.dev | sudo sh
Then install cert-manager as follows:
arkade install cert-manager
Step 2, Create a Certificate Authority
To make this as simple as possible we will be using a utility described as a “PKI/TLS swiss army knife”, cfssl.
cfssl releases can be found on Github. Below are some instructions on installing version 1.4.1 (the most up to date as of writing) cfssl for Linux.
sudo curl hhttps://github.com/cloudflare/cfssl/releases/download/v1.4.1/cfssl_1.4.1_linux_amd64 -o /usr/local/bin/cfssl
sudo curl https://github.com/cloudflare/cfssl/releases/download/v1.4.1/cfssljson_1.4.1_linux_amd64 -o /usr/local/bin/cfssljson
sudo chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson
The first step is to create a ca.json
file which will provide the specification for our CA.
Here is what I am using.
{
"CN": "Cluster CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"C": "UK",
"O": "Cluster",
"OU": "Cluster Root CA"
}
]
}
We can then generate our CA’s certificate, key and certificate signing request using cfssl:
cfssl gencert -initca ca.json | cfssljson -bare ca
Since this is for an internal development cluster we are not going to generate an intermediate CA.
Step 3, Create ClusterIssuer
The next step is to install the CA in to the k3s cluster as a secret.
The ca-certificate.yml
for this is as follows:
apiVersion: v1
kind: Secret
metadata:
name: ca-key-pair
namespace: cert-manager
data:
tls.crt: # base64 encoded contents of ca.pem
tls.key: # base64 encoded contents of ca-key.pem
This can then be installed in to the k3s cluster using kubectl apply -f ca-certificate.yml
.
This secret can then be used by cert-manager to issue certificates.
This can be done in two ways, using an Issuer
or a ClusterIssuer
.
An Issuer
is a namespaced resource and will only issue certificates to resources in the namespace it is deployed in.
A ClusterIssuer
is not namespaced, it can issue certificates to resources in any namespace.
Since we are the only users of this cluster we will deploy a ClusterIssuer
.
The cluster-issuer.yml
for this is as follows:
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: ca-issuer
namespace: cert-manager
spec:
ca:
secretName: ca-key-pair
This can then be installed in to the k3s cluster using kubectl apply -f cluster-issuer.yml
.
Step 4, Deploy an Application with TLS Ingress
The below yaml will create a simple hello world HTTP application.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world-http-server
namespace: example
labels:
app: hello-world-http-server
spec:
replicas: 1
selector:
matchLabels:
app: hello-world-http-server
template:
metadata:
labels:
app: hello-world-http-server
spec:
containers:
- name: hello-world-http-server
image: markdashcs/hello-world-http-server:arm32v7-1.0.0
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: hello-world-http-server
namespace: example
labels:
app: hello-world-http-server
spec:
ports:
- port: 8080
targetPort: 8080
name: tcp
selector:
app: hello-world-http-server
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: hello-world-http-server
namespace: example
annotations:
kubernetes.io/ingress.class: "traefik"
cert-manager.io/cluster-issuer: ca-issuer
spec:
rules:
- host: hello.home
http:
paths:
- path:
backend:
serviceName: hello-world-http-server
servicePort: 8080
tls:
- hosts:
- hello.home
secretName: hello-home-cert
The interesting part is in the Ingress definition.
The cert-manager.io/cluster-issuer
annotation tells cert-manager which ClusterIssuer
should be used to issue a certificate.
The below fragment are directions for cert-manager on how to issue a certificate.
cert-manager will issue a certificate for hello.home
and store the certificate and key in a secret called hello-home-cert
.
...
tls:
- hosts:
- hello.home
secretName: hello-home-cert
Once this has been deployed, and you have edited your hosts file to direct hello.home
to your k3s cluster you should be able to navigate to https://hello.home
.
If all is well you should be greeted with “Hello, world!”.
I hope you found this useful.