Try   HackMD

cert-manager + ListenerSet: TLS self-service for Gateway API

See thread on #sig-network-gateway-api (Slack).

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.
  • Step 2: Add XListenerSet (see 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.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

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:

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 tool to convert the above Ingress. Command used:

ingress2gateway print --providers ingress-nginx --input-file ingress.yaml | yq 'del(.. | select(length==0))'
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:

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,