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

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.