# 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.

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:

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, ...