In the previous post I have exposed a web application through Traefik Proxy.
Unfortunately Traefik does not support, by default, websockets, which my application uses.
To enable them we have to add a Header to all requests to our backend, adding a customRequestHeaders Middleware.
We do so by specifying a Middleware CRD, or “Custom Resource Definition”, which we then recall from a Traefik IngressRoute
This is not supported with traditional Kubernetes Ingress specifications, so we can scrap all of that hard work!
First, we need to tell Kuberneted what an IngressRoute is
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v2.9/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
And now we can create our Middleware!
# CRD to allow wss protocol through apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: metamonitor-pass-wss spec: headers: customRequestHeaders: X-Forwarded-Proto: "https,ws"
Which we can recall from our IngressRoute
apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: metamonitor-web-ingressroute spec: entryPoints: - web routes: - match: Host(`metamonitor.k8s`) && PathPrefix(`/`) kind: Rule services: - name: metamonitor-web port: 80
HTTPS
This is fine and everything, but so far everything is in plaintext.
I don’t think traffic inside the cluster will be much of a security issue (for now), so we’ll concentrate on securing traffic from clients to Traefik.
Cert-manager
Certificates have to be issued, and Kubernetes’ native way to do so is centralising everything to Cert-manager, having it create a Secret with key and signed certificate inside, and then importing those in your application either using the Kubernetes native API or passing them to your Deployments as files from secrets.
On microk8s it can be enabled with microk8s enable cert-manager
Loading the certificate
There are many ways to configure cert-manager, this time I’ll configure it as an internal CA, for which we will need to generate an appropriate certificate.
Since I am not skilled in the black arts of openssl
, I’ll be laming it with XCA
Generate a new self-signed certificate using the “CA” model, create a private key in the next tab and give it a name.
Export the certificate and the key to PEM format, then convert them to base64
cat kuca.crt | base64 -w0
cat kuca.key | base64 -w0
Put the relative outputs in this Secret definition.
Make sure to use the same namespace
from now on, as everything will be namespace-specific, thus, I’ll be putting everything inside the traefik namespace so traefik can access it
apiVersion: v1 kind: Secret metadata: name: kuca-key-pair namespace: traefik data: tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tC.... tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktL....
Now that we’ve loaded our certificate and key, we need to tell cert-manager it can use them to issue a certificate
apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: kuca-issuer namespace: traefik spec: ca: secretName: kuca-key-pair
Request a certificate
When requesting a certificate, cert-manager will create a Secret containing our key, our certificate, and the CA’s certificate.
The Certificate object, thus, needs to contain the domain names we need this certificate for, the name of the secret where to save the generated data, and whom we want the certificate issued by
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: metamonitor.k8s namespace: traefik spec: dnsNames: - metamonitor.k8s secretName: metamonitor.k8s issuerRef: name: kuca-issuer kind: Issuer
Wait a couple moments for the magic to happen and…
$ microk8s kubectl -n traefik describe secrets metamonitor.k8s
Name: metamonitor.k8s
Namespace: traefik
Labels: <none>
Annotations: cert-manager.io/alt-names: metamonitor.k8s
cert-manager.io/certificate-name: metamonitor.k8s
cert-manager.io/common-name:
cert-manager.io/ip-sans:
cert-manager.io/issuer-group:
cert-manager.io/issuer-kind: Issuer
cert-manager.io/issuer-name: kuca-issuer
cert-manager.io/uri-sans:
Type: kubernetes.io/tls
Data
====
tls.crt: 1541 bytes
tls.key: 1675 bytes
ca.crt: 2021 bytes
Signed!
Cert-manager will make sure to keep this certificate valid and will refresh it automatically when the renewal time comes
$ microk8s kubectl -n traefik describe certificate metamonitor.k8s
Name: metamonitor.k8s
Namespace: traefik
Labels: <none>
Annotations: <none>
API Version: cert-manager.io/v1
Kind: Certificate
Metadata:
Creation Timestamp: 2023-01-07T10:50:01Z
Generation: 1
Resource Version: 485968
UID: 3c3e3395-58c9-49af-8c4c-5a7afbb7200f
Spec:
Dns Names:
metamonitor.k8s
Issuer Ref:
Kind: Issuer
Name: kuca-issuer
Secret Name: metamonitor.k8s
Status:
Conditions:
Last Transition Time: 2023-01-07T10:50:01Z
Message: Certificate is up to date and has not expired
Observed Generation: 1
Reason: Ready
Status: True
Type: Ready
Not After: 2023-04-07T10:50:01Z
Not Before: 2023-01-07T10:50:01Z
Renewal Time: 2023-03-08T10:50:01Z
Revision: 1
Events: <none>
Make traefik use it
We can go back to our IngressRoute and change it to use https (entrypoint websecure) and adding the tls.secretName
property
apiVersion: traefik.containo.us/v1alpha1 kind: IngressRoute metadata: name: metamonitor-web-ingressroute spec: entryPoints: - websecure routes: - match: Host(`metamonitor.k8s`) && PathPrefix(`/`) kind: Rule services: - name: metamonitor-web port: 443 tls: secretName: metamonitor.k8s
annnnnndddd