Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Gateway API: Listener hostname field has no effect on TLS certificate selection at serve time #12742

@crisbal

Description

@crisbal

What did you do?

When using the Kubernetes Gateway API provider, the hostname field on a TLS listener has no effect on which certificate is served to the client. Certificates are loaded unconditionally from all listeners' certificateRefs and served based solely on SAN matching - the listener hostname filtering is entirely absent.

Example

For a fully reproducible example, see the "Steps to reproduce" section below.

Two listeners on the same port, with different hostnames, each referencing a different certificate:

listeners:
  # Listener for *.example.com — should only serve wildcard-example-com-tls
  - name: websecure-wildcard
    port: 8443
    protocol: HTTPS
    hostname: "*.example.com"
    tls:
      mode: Terminate
      certificateRefs:
        - name: wildcard-example-com-tls  # SANs: *.example.com
          kind: Secret
  # Listener for foobar.com — hostname does NOT cover *.example.com
  # so other-app-tls should never be served for *.example.com SNIs
  - name: websecure-other-app
    port: 8443
    protocol: HTTPS
    hostname: "foobar.com"
    tls:
      mode: Terminate
      certificateRefs:
        - name: other-app-tls  # SANs: foobar.com, *.example.com
          kind: Secret

Observed behaviour

The listener hostname field is ignored during certificate selection:

# hello.example.com 
# Expected SANs: DNS:*.example.com
# Actual SANs:   DNS:foobar.com, DNS:*.example.com (from other-app-tls)

$ openssl s_client -connect 127.0.0.1:8443 -servername hello.example.com </dev/null 2>/dev/null | openssl x509 -noout -ext subjectAltName
X509v3 Subject Alternative Name: 
    DNS:foobar.com, DNS:*.example.com

The listener-to-certificate binding defined by the Gateway spec (listener.hostnamelistener.tls.certificateRefs) is not considered at serve time.

Expected behaviour

Each listener's hostname should define the SNI scope for which its certificateRefs certificate is served. A certificate referenced by a listener should only be a candidate for TLS handshakes whose SNI matches that listener's hostname.

I suspect this breaks GatewayAPI spec compliance.

Possible cause

The Gateway provider (pkg/provider/kubernetes/gateway/kubernetes.go) loads all certificates from all listeners into a flat CertificateStore keyed by SAN, with no record of which listener hostname a cert was associated with. The hostname field is used only for HTTPRoute attachment logic - it plays no part in the TLS certificate pipeline.

Related


Steps to reproduce

Requirements: a cluster with cert-manager and Traefik configured to use the Kubernetes Gateway API provider.

Apply the following manifest (adjust gatewayClassName and port to match your Traefik entrypoint):

Click to expand YAML code
---
apiVersion: v1
kind: Namespace
metadata:
  name: tls-repro

---
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned
  namespace: tls-repro
spec:
  selfSigned: {}

---
# Certificate 1: wildcard-tls
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: wildcard-tls
  namespace: tls-repro
spec:
  secretName: wildcard-tls
  issuerRef:
    name: selfsigned
    kind: Issuer
  dnsNames:
    - "*.example.com"

---
# Certificate 2: other-app-tls
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: other-app-tls
  namespace: tls-repro
spec:
  secretName: other-app-tls
  issuerRef:
    name: selfsigned
    kind: Issuer
  dnsNames:
    - "foobar.com"
    - "*.example.com"

---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: tls-repro
  namespace: tls-repro
spec:
  gatewayClassName: public-traefik  # adjust to your GatewayClass name
  listeners:
    - name: websecure-wildcard
      port: 8443
      protocol: HTTPS
      hostname: "*.example.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: wildcard-tls
            kind: Secret
    - name: websecure-other-app
      port: 8443
      protocol: HTTPS
      hostname: "foobar.com"
      tls:
        mode: Terminate
        certificateRefs:
          - name: other-app-tls
            kind: Secret

Wait for the certificates to be issued:

kubectl wait --for=condition=Ready certificate/wildcard-tls certificate/other-app-tls \
  -n tls-repro --timeout=60s

Port-forward the Traefik service to localhost:

kubectl port-forward -n <traefik-namespace> svc/<traefik-service> 8443:443

Check which certificate is served for each SNI:

# hello.example.com — matches listener websecure-wildcard (hostname: *.example.com)
# Expected SANs: DNS:*.example.com
# Actual SANs:   DNS:foobar.com, DNS:*.example.com
openssl s_client -connect 127.0.0.1:8443 -servername hello.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

# foobar.com — matches listener websecure-other-app (hostname: foobar.com)
# Expected SANs: DNS:foobar.com, DNS:*.example.com (correct)
# Actual SANs:   DNS:foobar.com, DNS:*.example.com — correct
openssl s_client -connect 127.0.0.1:8443 -servername foobar.com </dev/null 2>/dev/null \
  | openssl x509 -noout -ext subjectAltName

Cleanup:

kubectl delete ns tls-repro

What version of Traefik are you using?

image: docker.io/traefik:v3.6.7

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions