Post

Public Trusted certificates on internal sites within k3s

Requirements

  • K3s cluster or stand alone node
  • Kubectl installed on your machine and configured to connect to your node / cluster
  • A public domain within cloudflare
  • Helm installed on your machine

Setting up

Ensure you have cert manager helm repo installed

1
helm repo add jetstack https://charts.jetstack.io
1
helm repo update

Create namespace for cert manager

1
kubectl create namespace cert-manager

You will want to apply the CRD (Custom resource definitions) for cert-manager otherwise this will not work

1
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.9.1/cert-manager.crds.yaml

Now install with helm

1
helm install cert-manager jetstack/cert-manager --namespace cert-manager --values=values.yaml --version v1.9.1

For the values.yaml see the below YAML block

1
2
3
4
5
6
7
8
9
10
installCRDs: false
replicaCount: 3
extraArgs:
  - --dns01-recursive-nameservers=1.1.1.1:53,9.9.9.9:53
  - --dns01-recursive-nameservers-only
podDnsPolicy: None
podDnsConfig:
  nameservers:
    - "1.1.1.1"
    - "9.9.9.9"

We want to set the DNS servers to 1.1.1.1 and 9.9.9.9 so that cert manager can look up domains on the internet, this will prevent it matching your internal dns records. This is so the DNS-01 challenge does not fail

You will need to create an API token within your cloudflare account that can read and write to your domain.

Create and apply the secret for your API key

1
2
3
4
5
6
7
8
9
---
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-token-secret
  namespace: cert-manager
type: Opaque
stringData:
  cloudflare-token: #Token here

We will want to create a staging cert issuer first. this is so that we test the function in the first instance, if this repeatedly fails on the prod issuer, you can lock your account out.

1
kubectl apply -f letsencrypt-staging.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    email: Email@dmain
    privateKeySecretRef:
      name: letsencrypt-staging
    solvers:
      - dns01:
          cloudflare:
            email: email@domain
            apiTokenSecretRef:
              name: cloudflare-token-secret
              key: cloudflare-token
        selector:
          dnsZones:
            - "your.domain.tld"

deploy a test staging cert into the default NS

1
kubectl apply -f staging-example.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: staging-cert-name #Change to whatever you wish to call the staging cert
  namespace: default
spec:
  secretName: staging-cert-name  #Change to whatever you wish to call the staging cert
  issuerRef:
    name: letsencrypt-staging
    kind: ClusterIssuer
  commonName: "*.internal.domain.tld"
  dnsNames:
  - "*.internal.domain.tld"

Check if cert has been issued

1
kubectl get certificate

Deploy a Demo Nginx service with the cert applied

Deployment of Nginx YAML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: nginx
  namespace: default #This is fine for demo purposes, use own namespace for prod deployments
  labels:
    app: nginx
spec:
  replicas: 3 #change to how many replicas you wish to have
  progressDeadlineSeconds: 600
  revisionHistoryLimit: 2
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest

Create the Service yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: default
spec:
  selector:
    app: nginx
  ports:
  - name: http
    targetPort: 80
    port: 80

Create the ingress.yaml You will need to change the ingress class to match your setup. But I am using an ingress route as it provides more control.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: nginx
  namespace: default
  annotations: 
    kubernetes.io/ingress.class: traefik-external #Note,you may not have ingress class if you not created one whith traefik.
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`www.nginx.internal.domain.tld`)
      kind: Rule
      services:
        - name: nginx
          port: 80
    - match: Host(`nginx.internal.domain.tld`)
      kind: Rule
      services:
        - name: nginx
          port: 80
      middlewares:
        - name: default-headers
  tls:
    secretName: #Name of staging cert
This post is licensed under CC BY 4.0 by the author.