Skip to content




cert-manager for cilium gateway

https://cert-manager.io/

Cert-manager is a powerful tool for automating the management of TLS certificates, making it ideal for setting up HTTPS gateways in kubernetes clusters.

I have setup Cilium Gateway to handle plain http access in my previous post. I will continue on to setup https access on the cilium gateway using cert-manager in this post.

previous post on setting up cilium gateway

And just as a reference, I have setup the same with Calico, MetalLB, and NGINX Gateway Fabric instead of Cilium a while ago, and here is the link.

post on the previous setup with calico & ngf

Prerequisite

  • GitOps is setup using fluxcd and GitLab
  • Five flux kustomizations setup
    • ./clusters/lab-hlv3 as flux-system created during bootstrap process
    • ./infrastructure/lab-hlv3/controllers as infra-controllers
    • ./infrastructure/lab-hlv3/configs as infra-configs
    • ./apps/lab-hlv3 as apps
    • ./sops/lab-hlv3 as sops
  • Cilium Gateway setup from the previous post
    • Gateway API v1.2.0 installed
    • Cilium Gateway created, IP address assigned (dummy 192.0.2.83 in this post) and exposed to the LAN using L2Announcement
    • traefik/whoami pod deployed along with service and HTTPRoute to connect to the http listener on the gateway

Steps

  • On infra-controllers
    • Install cert-manager as helm release
  • On infra-configs
    • Add Issuer
    • Update Gateway config to work along with the cert-manager Issuer
    • Update Gateway config to add https listener
    • Add HTTPRoute to connect the https listener and the target service, traefik/whoami

cert-manager helm chart

Add the jetstack helm repository which stores cert-manager helm chart.

# add helm repository
helm repo add jetstack https://charts.jetstack.io

Here is the list of available charts in jetstack.

# run helm repo update to update repository information

$ helm search repo jetstack
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION
jetstack/cert-manager                   v1.17.1         v1.17.1         A Helm chart for cert-manager
jetstack/cert-manager-approver-policy   v0.19.0         v0.19.0         approver-policy is a CertificateRequest approve...
jetstack/cert-manager-csi-driver        v0.10.2         v0.10.2         cert-manager csi-driver enables issuing secretl...
jetstack/cert-manager-csi-driver-spiffe v0.8.2          v0.8.2          csi-driver-spiffe is a Kubernetes CSI plugin wh...
jetstack/cert-manager-google-cas-issuer v0.9.0          v0.9.0          A Helm chart for jetstack/google-cas-issuer
jetstack/cert-manager-istio-csr         v0.14.0         v0.14.0         istio-csr enables the use of cert-manager for i...
jetstack/cert-manager-trust             v0.2.1          v0.2.0          DEPRECATED: The old name for trust-manager. Use...
jetstack/finops-dashboards              v0.0.5          0.0.5           A Helm chart for Kubernetes
jetstack/finops-policies                v0.0.6          v0.0.6          A Helm chart for Kubernetes
jetstack/finops-stack                   v0.0.5          0.0.3           A FinOps Stack for Kubernetes
jetstack/trust-manager                  v0.16.0         v0.16.0         trust-manager is the easiest way to manage TLS ...
jetstack/version-checker                v0.8.6          v0.8.6          A Helm chart for version-checker

values file

Use helm show command to download values file for the specific cert-manager helm chart version. I will be installing cert-manager under infra-controllers flux kustomization, so that's also where I store the values file.

Although I can download it anytime, I like to keep a copy of the values file before editing in my GitOps repository.

# on gitops repo
cd infrastructure/lab-hlv3/controllers/default-values
helm show values jetstack/cert-manager --version v1.17.1 > cert-manager-v1.17.1-values.yaml
cp cert-manager-v1.17.1-values.yaml ../values/cert-manager-values.yaml

# and edit values file at ./infrastructure/lab-hlv3/controllers/values/cert-manager-values.yaml

Here is the list of changes made to the values file:

  • to install cert-manager crds
    • crds.enabled: true
  • to enable gateway api support
    • config.apiVersion: controller.config.cert-manager.io/v1alpha1
    • config.kind: ControllerConfiguration
    • config.enableGatewayAPI: true
  • to specify external DNS server to process DNS01 challenges
    • dns01RecursiveNameservers: "1.1.1.1:53,1.0.0.1:53"
    • dns01RecursiveNameserversOnly: true
  • and optionally, I have changed the registry to pull images from

script to prepare flux helm repo and helm release

https://fluxcd.io/flux/guides/helmreleases/

To install something with helm and fluxcd GitOps, the brief steps are to (1) add HelmRepository and (2) add HelmRelease using the helm repository added to the flux system on the kubernetes cluster, and the flux system pulls the helm chart and deploys it.

These are all CRDs for fluxcd, and there are flux commands to create these manifests (flux create source helm and flux create helmrelease).

$ kubectl api-resources | grep fluxcd
helmreleases                        hr                                  helm.toolkit.fluxcd.io/v2                true         HelmRelease
kustomizations                      ks                                  kustomize.toolkit.fluxcd.io/v1           true         Kustomization
alerts                                                                  notification.toolkit.fluxcd.io/v1beta3   true         Alert
providers                                                               notification.toolkit.fluxcd.io/v1beta3   true         Provider
receivers                                                               notification.toolkit.fluxcd.io/v1        true         Receiver
buckets                                                                 source.toolkit.fluxcd.io/v1              true         Bucket
gitrepositories                     gitrepo                             source.toolkit.fluxcd.io/v1              true         GitRepository
helmcharts                          hc                                  source.toolkit.fluxcd.io/v1              true         HelmChart
helmrepositories                    helmrepo                            source.toolkit.fluxcd.io/v1              true         HelmRepository
ocirepositories                     ocirepo                             source.toolkit.fluxcd.io/v1beta2         true         OCIRepository

$ flux create source helm --help
The create source helm command generates a HelmRepository resource and waits for it to fetch the index.
For private Helm repositories, the basic authentication credentials are stored in a Kubernetes secret.

Usage:
  flux create source helm [name] [flags]

Examples:
  # Create a source for an HTTPS public Helm repository
  flux create source helm podinfo \
    --url=https://stefanprodan.github.io/podinfo \
    --interval=10m

$ flux create helmrelease --help
The helmrelease create command generates a HelmRelease resource for a given HelmRepository source.

Usage:
  flux create helmrelease [name] [flags]

Aliases:
  helmrelease, hr

Examples:
  # Create a HelmRelease with a chart from a HelmRepository source
  flux create hr podinfo \
    --interval=10m \
    --source=HelmRepository/podinfo \
    --chart=podinfo \
    --chart-version=">4.0.0"

As you can see from the examples in the --help, they can be lengthy.

Whenever chart version or values file content change, you need to run flux create to generate required flux manifests. By having the script ready, you can just execute the script to generate the updated flux helm manifests.

And so here is the script I use for all the helm releases I want to install using fluxcd. This will create ./infrastructure/lab-hlv3/controllers/cert-manager.yaml including both flux HelmRepository and HelmRelease manifests, and I can just include this one file in my infra-controllers flux kustomization. Whenever I update the parameters in the values file, I run the shell script and push/commit the change to the cert-manager.yaml file, and the helm release will be upgraded. As for the helm chart version changes, I will have to update the --chart-version value in the shell script.

# on gitops repo
mkdir infrastructure/lab-hlv3/controllers/scripts
cd infrastructure/lab-hlv3/controllers/scripts

# script to generate a file containing flux helmrepo and hr
cat <<'EOF' >cert-manager.sh
#!/bin/bash

# add flux helmrepo to the manifest
flux create source helm cert-manager \
  --url=https://charts.jetstack.io \
  --interval=1h0m0s \
  --export >../cert-manager.yaml

# add flux helm release to the manifest including the customized values.yaml file
flux create helmrelease cert-manager \
  --interval=10m \
  --target-namespace=cert-manager \
  --source=HelmRepository/cert-manager \
  --chart=cert-manager \
  --chart-version=v1.17.1 \
  --values=../values/cert-manager-values.yaml \
  --export >>../cert-manager.yaml
EOF


# run the script to generate the file
chmod u+x cert-manager.sh
./cert-manager.sh

cert-manager namespace

In the flux helmrelease manifest above, the target namespace to install cert-manager is set to "cert-manager", and this namespace must be created separately as --create-target-namespace is not set.

I prefer this way so that whenever I want to make changes to the namespace itself, I'm certain I'm not bothering with the helm chart installation.

# ./clusters/lab-hlv3/namespaces/cert-manager.yaml
---
kind: Namespace
apiVersion: v1
metadata:
  name: cert-manager
  labels:
    service: cert-manager
    type: infrastructure

infra-controller kustomization

Finally, update the infra-controller kustomization to include the generated flux helmrepo and helmrelease manifests.

# ./infrastructure/lab-hlv3/controllers/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - cm-placeholder.yaml
  ### crds
  # gateway api
  # version 1.2.0
  - crds/gateway-api/standard/standard-install-v1.2.0.yaml
  - crds/gateway-api/experimental/gateway.networking.k8s.io_tlsroutes-v1.2.0.yaml
  ### infra-controllers
  - cert-manager.yaml

cert-manager installed

These are the changes you can confirm.

$ flux get all
NAME                            REVISION                SUSPENDED       READY   MESSAGE
gitrepository/flux-system       main@sha1:5883ff2a      False           True    stored artifact for revision 'main@sha1:5883ff2a'

NAME                            REVISION        SUSPENDED       READY   MESSAGE
helmrepository/cert-manager     sha256:f3211071 False           True    stored artifact: revision 'sha256:f3211071'

NAME                                    REVISION        SUSPENDED       READY   MESSAGE
helmchart/flux-system-cert-manager      v1.17.1         False           True    pulled 'cert-manager' chart with version 'v1.17.1'

NAME                            REVISION        SUSPENDED       READY   MESSAGE
helmrelease/cert-manager        v1.17.1         False           True    Helm install succeeded for release cert-manager/cert-manager-cert-manager.v1 with chart [email protected]

NAME                            REVISION                SUSPENDED       READY   MESSAGE
kustomization/apps              main@sha1:5883ff2a      False           True    Applied revision: main@sha1:5883ff2a
kustomization/flux-system       main@sha1:5883ff2a      False           True    Applied revision: main@sha1:5883ff2a
kustomization/infra-configs     main@sha1:5883ff2a      False           True    Applied revision: main@sha1:5883ff2a
kustomization/infra-controllers main@sha1:5883ff2a      False           True    Applied revision: main@sha1:5883ff2a


$ kubectl get all -n cert-manager
NAME                                                        READY   STATUS    RESTARTS   AGE
pod/cert-manager-cert-manager-cainjector-86cd99655f-44tn7   1/1     Running   0          2m19s
pod/cert-manager-cert-manager-d56b496b8-ssmmr               1/1     Running   0          2m19s
pod/cert-manager-cert-manager-webhook-54696f8b94-x2jxk      1/1     Running   0          2m19s

NAME                                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)            AGE
service/cert-manager-cert-manager              ClusterIP   10.96.243.72    <none>        9402/TCP           2m19s
service/cert-manager-cert-manager-cainjector   ClusterIP   10.96.213.160   <none>        9402/TCP           2m20s
service/cert-manager-cert-manager-webhook      ClusterIP   10.96.164.115   <none>        443/TCP,9402/TCP   2m19s

NAME                                                   READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/cert-manager-cert-manager              1/1     1            1           2m19s
deployment.apps/cert-manager-cert-manager-cainjector   1/1     1            1           2m19s
deployment.apps/cert-manager-cert-manager-webhook      1/1     1            1           2m19s

NAME                                                              DESIRED   CURRENT   READY   AGE
replicaset.apps/cert-manager-cert-manager-cainjector-86cd99655f   1         1         1       2m19s
replicaset.apps/cert-manager-cert-manager-d56b496b8               1         1         1       2m19s
replicaset.apps/cert-manager-cert-manager-webhook-54696f8b94      1         1         1       2m19s


$ kubectl api-resources | grep cert
challenges                                                              acme.cert-manager.io/v1                  true         Challenge
orders                                                                  acme.cert-manager.io/v1                  true         Order
certificaterequests                 cr,crs                              cert-manager.io/v1                       true         CertificateRequest
certificates                        cert,certs                          cert-manager.io/v1                       true         Certificate
clusterissuers                                                          cert-manager.io/v1                       false        ClusterIssuer
issuers                                                                 cert-manager.io/v1                       true         Issuer
certificatesigningrequests          csr                                 certificates.k8s.io/v1                   false        CertificateSigningRequest

cert-manager verification test

https://cert-manager.io/docs/installation/kubectl/#verify

Run the verification test described in the link above and see if the installation is successful.

Configure Issuer

https://cert-manager.io/docs/configuration/acme/

Next I am going to setup the issuer.

The ACME Issuer type represents a single account registered with the Automated Certificate Management Environment (ACME) Certificate Authority server.

Here is basically what needs to be done:

  • tell ACME system how I am going to prove that I am the rightful domain owner for the certificates I am going to request signs for

I have transferred my domain from Google Domains to Cloudflare a while ago, and since I'm still there I will be configuring my issuer to use Cloudflare DNS01 challenge method.

Cloudflare DNS01 Challenge

First obtain the API token with appropriate scope, and then create issuer.

https://cert-manager.io/docs/configuration/acme/dns01/cloudflare/#api-tokens

https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.Issuer

The required permissions and zone resources to set are as follows, as described in the link above:

  • permissions
    • zone - dns - edit
    • zone - zone -read
  • zone resources
    • include - all zones

You do not want to place the API token without encryption on GitOps repository, so place the secret manifest in the SOPS directory, encrypt it, and commit and push.

(SOPS encryption setup for GitOps covered in this separate post)

# ./sops/lab-hlv3/gateway/cloudflare-api-token-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token-secret
  namespace: gateway
type: Opaque
stringData:
  api-token: YOUR_API_TOKEN_HERE

Here is the issuer with the cloudflare dns01 details set. Update "EMAIL_ADDR" and .spec.acme.solvers[].selector.dnsZones[] accordingly when you setup your own.

# ./infrastructure/lab-hlv3/configs/cert-manager/issuer.yaml
---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: issuer
  namespace: gateway
spec:
  acme:
    email: EMAIL_ADDR
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: issuer-account-key
    solvers:
      - dns01:
          cloudflare:
            email: EMAIL_ADDR
            apiTokenSecretRef:
              name: cloudflare-api-token-secret
              key: api-token
        selector:
          dnsZones:
            - "blink-1x52.net"

Make sure that this is included in the infra-configs kustomization.

# ./infrastructure/lab-hlv3/configs/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - cm-placeholder.yaml
  # cilium gateway
  - cilium/gateway.yaml
  - cilium/ippools-cilium-gateway.yaml
  - cilium/l2announcement-cilium-gateway.yaml
  # cert-manager
  # - cert-manager/cert-manager-test.yaml
  # cloudflare dns01 issuer
  - cert-manager/issuer.yaml

Once successfully created, you can see the issuer and its status like this.

# kubectl describe issuer issuer -n gateway
Status:
  Acme:
    ......
  Conditions:
    Last Transition Time:  2025-03-31T01:21:13Z
    Message:               The ACME account was registered with the ACME server
    Observed Generation:   1
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>

Configure cilium gateway to use the certificate issuer

Let's update the cilium gateway configuration.

To have the gateway to listen on http, I just had to add one listener for the http at .spec.listners[]. Adding another one for https is almost the same.

See the whoami-kube-https https listener added below.

  • hostname is added and this is what's in the certificate and SNI, server name indicator, of the incoming traffic
  • port has changed to 443
  • protocol is HTTPS
  • .spec.listeners[].tls is newly added
    • mode is terminate which is to do TLS offloading at the gateway and pass on plain http traffic to whatever connected via HTTPRoutes
    • .spec.listeners[].tls.certificateRefs is the secret cert-manager process eventually creates once all the creation, request, and challenge processes are complete, so name it whatever you like

And there is one more important thing to add to the gateway manifest to have cert-manager work together with the gateway. Adding the annotation cert-manager.io/issuer with the name of the issuer created in the gateway namespace does the job. I have just created an issuer named "issuer" in the previous step and that's what I need to set.

# ./infrastructure/lab-hlv3/configs/cilium/gateway.yaml
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: cilium-gateway
  namespace: gateway
  annotations:
    cert-manager.io/issuer: issuer
spec:
  gatewayClassName: cilium
  addresses:
    - type: IPAddress
      value: 192.0.2.83
  listeners:
    - name: whoami-kube-http
      hostname: whoami-kube.lab.blink-1x52.net
      port: 80
      protocol: HTTP
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway: cilium

    - name: whoami-kube-https
      hostname: whoami-kube.lab.blink-1x52.net
      port: 443
      protocol: HTTPS
      allowedRoutes:
        namespaces:
          from: Selector
          selector:
            matchLabels:
              gateway: cilium
      tls:
        mode: Terminate
        certificateRefs:
          - name: tls-whoami-kube
            kind: Secret
            namespace: gateway

What happens next?

Once the gateway is updated to work with the issuer, and a valid TLS listener added, cert-manager catches it and process everything needed to obtain the signed TLS certificate.

TLS certificate and key

Let's look at the end state first.

Check the secret by the name specified in the tls certificate ref for the https listener, and you will see the kubernetes TLS secret.

https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets

# kubectl get secret tls-whoami-kube -n gateway -o yaml
apiVersion: v1
kind: Secret
type: kubernetes.io/tls
metadata:
  annotations:
    cert-manager.io/alt-names: whoami-kube.lab.blink-1x52.net
    cert-manager.io/certificate-name: tls-whoami-kube
    cert-manager.io/common-name: whoami-kube.lab.blink-1x52.net
    cert-manager.io/ip-sans: ""
    cert-manager.io/issuer-group: cert-manager.io
    cert-manager.io/issuer-kind: Issuer
    cert-manager.io/issuer-name: issuer
    cert-manager.io/uri-sans: ""
  creationTimestamp: "2025-03-31T02:08:36Z"
  labels:
    controller.cert-manager.io/fao: "true"
  name: tls-whoami-kube
  namespace: gateway
  resourceVersion: "3969674"
  uid: ec65b140-027b-4a75-91f5-a80709f36cc7
data:
  tls.key: ...
  tls.crt: ...

What's happening in between?

The process flow diagram is available in the official documents:

You'd add one https listener on the gateway and the TLS certificate for the specified hostname gets automatically created. Here is what's happening in brief:

  • "Certificate" gets created
    • you can say it's an empty box as of this moment
  • "Certificate Request" gets created
    • "Order" gets created, and it works with the Issuer to spin up the "Challenge" to complete the DNS01 challenge to get the official signed certificate and key
  • The TLS cert & key secret gets created by the name specified in the gateway listener certificateRefs
  • "Certificate" status gets updated to ready state
describe challenge

Challenge disappears once the process is over. Below is what I captured during the process.

See the status. The challenge is "Presented: true", "Processing: true", but "State: pending" because the DNS01 challenge record was not confirmed yet at the moment I captured this.

$ kubectl describe challenges -n gateway
Name:         tls-whoami-kube-1-3711042244-1698055298
Namespace:    gateway
Labels:       <none>
Annotations:  <none>
API Version:  acme.cert-manager.io/v1
Kind:         Challenge
Metadata:
  Creation Timestamp:  2025-03-31T01:54:16Z
  Finalizers:
    acme.cert-manager.io/finalizer
  Generation:  1
  Owner References:
    API Version:           acme.cert-manager.io/v1
    Block Owner Deletion:  true
    Controller:            true
    Kind:                  Order
    Name:                  tls-whoami-kube-1-3711042244
    UID:                   b3aee648-c1ab-4827-a9c3-42ef041f5456
  Resource Version:        3962141
  UID:                     5121de47-99a5-4010-86a1-c991a40708f3
Spec:
  Authorization URL:  https://acme-v02.api.letsencrypt.org/acme/authz/xxx/xxx
  Dns Name:           whoami-kube.lab.blink-1x52.net
  Issuer Ref:
    Group:  cert-manager.io
    Kind:   Issuer
    Name:   issuer
  Key: KEY_HERE
  Solver:
    dns01:
      Cloudflare:
        API Token Secret Ref:
          Key:   api-token
          Name:  cloudflare-api-token-secret
        Email:   EMAIL_ADDR
    Selector:
      Dns Zones:
        blink-1x52.net
  Token: TOKEN_HERE
  Type:      DNS-01
  URL: CHALLENGE_URL_HERE
  Wildcard:  false
Status:
  Presented:   true
  Processing:  true
  Reason:      Waiting for DNS-01 challenge propagation: DNS record for "whoami-kube.lab.blink-1x52.net" not yet propagated
  State:       pending
Events:
  Type    Reason     Age   From                     Message
  ----    ------     ----  ----                     -------
  Normal  Started    13m   cert-manager-challenges  Challenge scheduled for processing
  Normal  Presented  13m   cert-manager-challenges  Presented challenge using DNS-01 challenge mechanism

Add HTTPRoute

Let us next add HTTPRoute in order to connect the https listener and the existing whoami service.

It is almost identical with the plain http HTTPRoute. The only difference is that this one is pointing to the https listener in .spec.parentRefs[].sectionName.

---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
  name: whoami-https
  namespace: testbed
spec:
  parentRefs:
    - name: cilium-gateway
      sectionName: whoami-kube-https
      namespace: gateway
  hostnames:
    - "whoami-kube.lab.blink-1x52.net"
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /
      backendRefs:
        - name: whoami
          port: 80

Result

Below is the curl command result of the https access (with dummy ipaddr in 192.0.2.x).

#$ curl -v https://whoami-kube.lab.blink-1x52.net
* Host whoami-kube.lab.blink-1x52.net:443 was resolved.
* ...
* Connected to whoami-kube.lab.blink-1x52.net (192.0.2.83) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: CN=whoami-kube.lab.blink-1x52.net
*  start date: Mar 31 01:10:01 2025 GMT
*  expire date: Jun 29 01:10:00 2025 GMT
*  subjectAltName: host "whoami-kube.lab.blink-1x52.net" matched cert's "whoami-kube.lab.blink-1x52.net"
*  issuer: C=US; O=Let's Encrypt; CN=R11
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* using HTTP/1.x
> GET / HTTP/1.1
> Host: whoami-kube.lab.blink-1x52.net
> User-Agent: curl/8.5.0
> Accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
< HTTP/1.1 200 OK
< date: Mon, 31 Mar 2025 05:09:34 GMT
< content-length: 339
< content-type: text/plain; charset=utf-8
< x-envoy-upstream-service-time: 0
< server: envoy
<
Hostname: whoami
IP: 127.0.0.1
IP: ::1
IP: 10.0.3.170
IP: fe80::e834:eff:fe1f:44f3
RemoteAddr: 10.0.3.174:41057
GET / HTTP/1.1
Host: whoami-kube.lab.blink-1x52.net
User-Agent: curl/8.5.0
Accept: */*
X-Envoy-Internal: true
X-Forwarded-For: IPADDR_OF_THE_HOST_EXECUTING_CURL
X-Forwarded-Proto: https
X-Request-Id: d3e749cd-70ac-4e83-ab61-28b132dc9a61

* Connection #0 to host whoami-kube.lab.blink-1x52.net left intact

Outro

I've automated TLS certificate management with cert-manager to secure web access through Cilium Gateway. Combining this with GitOps makes deployments consistent and simplifies operations. The benefits of this automation really add up as your Kubernetes environment grows.

Now I cannot stop emphasizing this enough, but adding HTTPS access to your service running on the Kubernetes cluster is super easy – it only takes two steps:

  • Add an HTTPS listener to the gateway
  • Add an HTTP route to connect that listener to your target service

Cert-manager automatically renews the certificates, which is great, but I guess I'll be setting up a periodic check to report certificate expiration dates for extra peace of mind.