# cert-manager + ListenerSet: TLS self-service for Gateway API See [thread on #sig-network-gateway-api](https://kubernetes.slack.com/archives/CR0H13KGA/p1747384203304419?thread_ts=1744015745.611159&cid=CR0H13KGA) (Slack). [cert-manager#7473]: https://github.com/cert-manager/cert-manager/issues/7473 This document details why supporting ListenerSet in cert-manager will fix a problem raised by application developers: they are used to managing TLS using the Ingress API and are not being able to manage TLS with Gateway API. This problem has been raised by the community in [cert-manager#7473][]. ## Plan - Step 1: Support the XGateway resource in cert-manager (not required, but good to have since users may want to use XGateway to keep using experimental features) -> [#7647](https://github.com/cert-manager/cert-manager/issues/7647). - Step 2: Add XListenerSet (see [gep-1713](https://gateway-api.sigs.k8s.io/geps/gep-1713/)) support to cert-manager, giving users a way to do what's described in [cert-manager#7473][]. - Step 3: contribute to `ingress2gateway` to support the cert-manager annotations. ## Today: developers coming from Ingress can't configure cert-manager because they can't edit Gateway resources Before Gateway API existed, application developers were able to manage both the HTTP routing and the TLS configuration. With Gateway API, developers no longer have control over the TLS configuration as they used to. The TLS configuration is now configured on the Gateway object, which is owned by the cluster operator. That's because Gateway objects represent actual infra that cost money (IPs, Google Cloud Load Balancers...). The idea is to be able to share this infrastructure across the Kubernetes cluster, and let the cluster operator manage it. ![gateway-with-manifests.excalidraw-fs8](https://hackmd.io/_uploads/r1rgH2tblx.png) With Gateway API, the application developer needs to synchronize with the cluster operator to add the annotation `cert-manager.io/cluster-issuer` on the Gateway resource and to add a new listener with the correct hostname. On top of that, there is can only be a single cert-manager issuer per Gateway. ## Tomorrow: developers can configure cert-manager thanks to the ListenerSet resource With ListernerSets, which can be edited by developers, it becomes possible for developers to manage the TLS configuration of their routes. The platform admin keeps control of the Gateway, and the developer attaches HTTPRoutes and ListernerSets. cert-manager's annotations can either be set on the Gateway, or on the ListenerSet: ![gateway-listenerset-manifests.excalidraw-fs8](https://hackmd.io/_uploads/B1PRYpKZgl.png) In the above example, the application developer was able to go with Let's Encrypt for issuing their certificate; another team may need to use the DigiCert ACME service instead. Unlike with the central Gateway approach, each hostname can have its own issuer. ## Example using `ingress2gateway` Let's look at an example of Ingress resource, developers are able to manage both the HTTP and TLS side of things, including configuring cert-manager: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: cert-manager.io/cluster-issuer: letsencrypt-prod name: foo namespace: default spec: ingressClassName: nginx rules: - host: foo.com http: paths: - pathType: Prefix path: / backend: service: name: ping port: number: 80 tls: - hosts: - foo.com secretName: foo-tls --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: cert-manager.io/cluster-issuer: letsencrypt-prod name: bar namespace: default spec: ingressClassName: nginx rules: - host: bar.com http: paths: - pathType: Prefix path: / backend: service: name: ping port: number: 80 tls: - hosts: - bar.com secretName: bar-tls ``` When moving to Gateway and HTTPRoute objects, developers can no longer manage the TLS and cert-manager configuration as they can only create and edit HTTPRoutes. > I've used the [ingress2gateway](https://github.com/kubernetes-sigs/ingress2gateway) tool to convert the above Ingress. Command used: > > ```bash > ingress2gateway print --providers ingress-nginx --input-file ingress.yaml | yq 'del(.. | select(length==0))' > ``` ```yaml apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: annotations: # Note that ingress2gateway doesn't actually keep # the cert-manager annotations. I've re-added it. cert-manager.io/cluster-issuer: letsencrypt-prod name: nginx namespace: default spec: gatewayClassName: nginx listeners: - hostname: foo.com name: foo-com-http port: 80 protocol: HTTP - hostname: foo.com name: foo-com-https port: 443 protocol: HTTPS tls: certificateRefs: - name: foo-tls # ✨ Managed by cert-manager - hostname: bar.com name: bar-com-http port: 80 protocol: HTTP - hostname: bar.com name: bar-com-https port: 443 protocol: HTTPS tls: certificateRefs: - name: bar-tls # ✨ Managed by cert-manager --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: foo-foo-com namespace: default spec: hostnames: - foo.com parentRefs: - name: nginx rules: - backendRefs: - name: ping port: 80 matches: - path: type: PathPrefix value: / --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: bar-bar-com namespace: default spec: hostnames: - bar.com parentRefs: - name: nginx rules: - backendRefs: - name: ping port: 80 matches: - path: type: PathPrefix value: / ``` With the ListenerSet, it looks like this: ```yaml apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: parent namespace: default spec: gatewayClassName: nginx listeners: [] allowedListeners: - from: Same # = same namespace --- apiVersion: gateway.networking.x-k8s.io/v1alpha1 kind: ListenerSet metadata: name: foo namespace: default spec: parentRef: name: parent listeners: - name: foo hostname: foo.com protocol: HTTPS port: 443 tls: mode: Terminate certificateRefs: - kind: Secret name: foo-tls # ✨ Managed by cert-manager --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: foo-foo-com spec: hostnames: - foo.com parentRefs: - name: nginx rules: - backendRefs: - name: ping port: 80 matches: - path: type: PathPrefix value: / --- apiVersion: gateway.networking.x-k8s.io/v1alpha1 kind: ListenerSet metadata: name: bar annotations: cert-manager.io/cluster-issuer: letsencrypt-prod spec: parentRef: name: parent listeners: - name: bar hostname: bar.com protocol: HTTPS port: 443 tls: mode: Terminate certificateRefs: - kind: Secret name: bar-tls # ✨ Managed by cert-manager --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: bar-bar-com annotations: cert-manager.io/cluster-issuer: ca-issuer spec: hostnames: - bar.com parentRefs: - name: nginx rules: - backendRefs: - name: ping port: 80 matches: - path: type: PathPrefix value: / ``` ## Issuer Annotation Selection Since both the Gateway and the ListenerSet can have the cert-manager annotations, ...