Default TLS Certificate
A default TLS certificate acts as a fallback when no other certificates match the hostname specified in the SNI header, or when SNI is absent.
The Gateway API specification does not natively support a default TLS certificate. Instead, a listener with an empty spec.listeners[].hostname
field matches all hostnames, effectively serving as a fallback for that specific protocol and port.
In this example, we will deploy a simple HTTP service and expose it via the Cilium Gateway API.
We will use the bookinfo sample application from the Istio project.
Deploy the Demo App
$ kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.11/samples/bookinfo/platform/kube/bookinfo.yaml
This is just deploying the demo app, it’s not adding any Istio components. You can confirm that with Cilium Service Mesh there is no Envoy sidecar created alongside each of the demo app microservices.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-5498c86cf5-kjzkj 1/1 Running 0 2m39s
productpage-v1-65b75f6885-ff59g 1/1 Running 0 2m39s
ratings-v1-b477cf6cf-kv7bh 1/1 Running 0 2m39s
reviews-v1-79d546878f-r5bjz 1/1 Running 0 2m39s
reviews-v2-548c57f459-pld2f 1/1 Running 0 2m39s
reviews-v3-6dd79655b9-nhrnh 1/1 Running 0 2m39s
Note
With the sidecar implementation the output would show 2/2 READY. One for the microservice and one for the Envoy sidecar.
Create TLS Certificate and Private Key
For demonstration purposes we will use a TLS certificate signed by a made-up,
self-signed
certificate authority (CA). One easy way to do this is with mkcert.
We want a certificate that will validate bookinfo.cilium.rocks and
hipstershop.cilium.rocks, as these are the host names used in this example.
$ mkcert bookinfo.cilium.rocks hipstershop.cilium.rocks
Note: the local CA is not installed in the system trust store.
Run "mkcert -install" for certificates to be trusted automatically ⚠️
Created a new certificate valid for the following names 📜
- "bookinfo.cilium.rocks"
- "hipstershop.cilium.rocks"
The certificate is at "./bookinfo.cilium.rocks+1.pem" and the key at "./bookinfo.cilium.rocks+1-key.pem" ✅
It will expire on 29 November 2026 🗓
Create a Kubernetes secret with this demo key and certificate:
$ kubectl create secret tls demo-cert --key=bookinfo.cilium.rocks+1-key.pem --cert=bookinfo.cilium.rocks+1.pem
Let us install cert-manager:
$ helm repo add jetstack https://charts.jetstack.io
$ helm install cert-manager jetstack/cert-manager --version v1.16.2 \
--namespace cert-manager \
--set crds.enabled=true \
--create-namespace \
--set config.apiVersion="controller.config.cert-manager.io/v1alpha1" \
--set config.kind="ControllerConfiguration" \
--set config.enableGatewayAPI=true
Now, create a CA Issuer:
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/kubernetes/servicemesh/ca-issuer.yaml
Deploy the Gateway and HTTPRoutes
In this example, a single certificate serves all incoming TLS connections, which are then routed to the appropriate backend based on the hostnames defined in the HTTPRoutes.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: tls-gateway
spec:
gatewayClassName: cilium
listeners:
- name: default
protocol: HTTPS
port: 443
tls:
certificateRefs:
- kind: Secret
name: demo-cert
mode: Terminate
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: bookinfo
spec:
parentRefs:
- name: tls-gateway
sectionName: default
hostnames:
- "bookinfo.cilium.rocks"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: details
port: 9080
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: hipstershop
spec:
parentRefs:
- name: tls-gateway
sectionName: default
hostnames:
- "hipstershop.cilium.rocks"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: productpage
port: 9080
Apply the configuration:
$ kubectl apply -f https://raw.githubusercontent.com/cilium/cilium/HEAD/examples/kubernetes/gateway/https-default-tls-certificate.yaml
Once the Gateway and HTTPRoutes are deployed, you can find external IP address in Gateway, and ensure hostnames are valid in HTTPRoute.
$ kubectl get gateway tls-gateway
NAME CLASS ADDRESS PROGRAMMED AGE
tls-gateway cilium 172.18.255.200 True 27m
$ kubectl get httproute bookinfo hipstershop
NAME HOSTNAMES AGE
bookinfo ["bookinfo.cilium.rocks"] 27m
hipstershop ["hipstershop.cilium.rocks"] 27m
Make HTTPS Requests
By specifying the CA’s certificate in a curl request, you can indicate that you trust certificates signed by that CA.
$ curl --cacert minica.pem -v https://bookinfo.cilium.rocks/details/1 --resolve bookinfo.cilium.rocks:443:172.18.255.200
$ curl --cacert minica.pem -v https://hipstershop.cilium.rocks/ --resolve hipstershop.cilium.rocks:443:172.18.255.200
...
* subjectAltName: "bookinfo.cilium.rocks" matches cert's "bookinfo.cilium.rocks"
* SSL certificate verified via OpenSSL.
You should see no warning messages, as the TLS certificate was issued for the requested hostnames.
The following request demonstrates that when accessing the service via an IP address, the client does not provide an SNI header.
This typically occurs when Cilium acts as a backend for a load balancer or proxy that uses an IP address to establish connections.
Despite the lack of SNI, a TLS connection is still established using the default certificate.
Note that since the certificate’s hostname will not match the IP address, we use the -k flag in curl to bypass validation.
Subsequent routing to the correct backend is then determined by the Host HTTP header, which we provide explicitly in this example.
$ curl -H "Host: hipstershop.cilium.rocks" -k -v https://172.18.255.200/
...
* SSL certificate verification failed, continuing anyway!
* Established connection to 172.18.255.200 (172.18.255.200 port 443) from 10.244.0.52 port 42458
By specifying -v in the curl request, you can see that the TLS handshake was successful.
By specifying the CA’s certificate in a curl request, you can indicate that you trust certificates signed by that CA.
$ curl --cacert cm-cert.pem -v https://bookinfo.cilium.rocks/details/1 --resolve bookinfo.cilium.rocks:443:172.18.255.200
$ curl --cacert cm-cert.pem -v https://hipstershop.cilium.rocks/ --resolve hipstershop.cilium.rocks:443:172.18.255.200
...
* subjectAltName: "bookinfo.cilium.rocks" matches cert's "bookinfo.cilium.rocks"
* SSL certificate verified via OpenSSL.
You should see no warning messages, as the TLS certificate was issued for the requested hostnames.
The following request demonstrates that when accessing the service via an IP address, the client does not provide an SNI header.
This typically occurs when Cilium acts as a backend for a load balancer or proxy that uses an IP address to establish connections.
Despite the lack of SNI, a TLS connection is still established using the default certificate.
Note that since the certificate’s hostname will not match the IP address, we use the -k flag in curl to bypass validation.
Subsequent routing to the correct backend is then determined by the Host HTTP header, which we provide explicitly in this example.
$ curl -H "Host: hipstershop.cilium.rocks" -k -v https://172.18.255.200/
...
* SSL certificate verification failed, continuing anyway!
* Established connection to 172.18.255.200 (172.18.255.200 port 443) from 10.244.0.52 port 42458
By specifying -v in the curl request, you can see that the TLS handshake was successful.