-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Description
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: SecretObserved 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.comThe listener-to-certificate binding defined by the Gateway spec (listener.hostname → listener.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: SecretWait for the certificates to be issued:
kubectl wait --for=condition=Ready certificate/wildcard-tls certificate/other-app-tls \
-n tls-repro --timeout=60sPort-forward the Traefik service to localhost:
kubectl port-forward -n <traefik-namespace> svc/<traefik-service> 8443:443Check 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 subjectAltNameCleanup:
kubectl delete ns tls-reproWhat version of Traefik are you using?
image: docker.io/traefik:v3.6.7