Fresh off the lab

The only difference between science and screwing around is writing it down

Traefik: Secure IngressRoutes with cert-manager

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

Difficulty level: clickthrough

Generate a new self-signed certificate using the “CA” model, create a private key in the next tab and give it a name.

Certificate
Key

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


Posted

in

by