SNI
Serve multiple hosts on the same HTTPS listener.
About SNI
Each host comes with its own TLS certificates that the gateway uses to authenticate and authorize client requests.
Serving multiple hosts on a single listener is also referred to as Server Name Indication (SNI) routing. SNI is an extension to the TLS protocol and allows clients to indicate which hostname they want to connect to at the start of the TLS handshake. After the HTTPS/TLS traffic is accepted at the gateway, the TLS connection is terminated, and the unencrypted HTTP/TCP request is forwarded to the destination.
About this guide
In this guide, you learn how to set up an HTTPS Gateway that serves two different domains, httpbin.example.com
and petstore.example.com
on the same port 443. When sending a request to the Gateway, you indicate the hostname you want to connect to. Based on the selected hostname, the Gateway presents the hostname-specific certificate.
Before you begin
-
Set up kgateway by following the Quick start or Installation guides.
-
Make sure that you have the OpenSSL version of
openssl
, not LibreSSL. Theopenssl
version must be at least 1.1.-
Check the
openssl
version that is installed. If you see LibreSSL in the output, continue to the next step.openssl version
-
Install the OpenSSL version (not LibreSSL). For example, you might use Homebrew.
brew install openssl
-
Review the output of the OpenSSL installation for the path of the binary file. You can choose to export the binary to your path, or call the entire path whenever the following steps use an openssl command.
- For example, openssl might be installed along the following path:
/usr/local/opt/openssl@3/bin/
- To run commands, you can append the path so that your terminal uses this installed version of OpenSSL, and not the default LibreSSL.
/usr/local/opt/openssl@3/bin/openssl req -new -newkey rsa:4096 -x509 -sha256 -days 3650...
- For example, openssl might be installed along the following path:
-
-
Decide whether to set up a listener inline on the Gateway resource or as a separate ListenerSet resource. For more information, see the Listener overview.
ListenerSets: This feature is available in kgateway version 2.1.x or later. Also, you must install the experimental channel of the Kubernetes Gateway API at version 1.3 or later.
Deploy sample apps
Deploy the Petstore sample app. This app is used alongside the httpbin app from the Get started guide to demonstrate the SNI routing capabilities.
-
Deploy the Petstore app.
kubectl apply -f https://raw.githubusercontent.com/solo-io/gloo/v1.16.x/example/petstore/petstore.yaml
Example output:
deployment.apps/petstore created service/petstore created
-
Verify that the Petstore app is up and running.
kubectl get pods
Example output:
NAME READY STATUS RESTARTS AGE petstore-66cddd5bb4-x7vdd 1/1 Running 0 26s
Set up TLS certificates for multiple domains
Create TLS certificates for the httpbin.example.com
and petstore.example.com
domains that are signed by a self-signed root CA.
-
Create a root certificate and private key to sign the certificates for your services.
mkdir example_certs openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example_certs/example.com.key -out example_certs/example.com.crt
-
Generate a TLS certificate and key for the
httpbin.example.com
domain.openssl req -out example_certs/httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs/httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization" openssl x509 -req -sha256 -days 365 -CA example_certs/example.com.crt -CAkey example_certs/example.com.key -set_serial 0 -in example_certs/httpbin.example.com.csr -out example_certs/httpbin.example.com.crt
-
Generate a TLS certificate and key for the
petstore.example.com
domain.openssl req -out example_certs/petstore.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs/petstore.example.com.key -subj "/CN=petstore.example.com/O=petstore organization" openssl x509 -req -sha256 -days 365 -CA example_certs/example.com.crt -CAkey example_certs/example.com.key -set_serial 1 -in example_certs/petstore.example.com.csr -out example_certs/petstore.example.com.crt
-
Verify that you have the required certificates and keys.
ls example_cert*
Example output:
petstore.example.com.crt petstore.example.com.key example.com.crt httpbin.example.com.crt httpbin.example.com.key petstore.example.com.csr example.com.key httpbin.example.com.csr
-
Store the credentials for the
httpbin.example.com
domain in a Kubernetes secret.kubectl create -n kgateway-system secret tls httpbin-credential \ --key=example_certs/httpbin.example.com.key \ --cert=example_certs/httpbin.example.com.crt
-
Store the credentials for the
petstore.example.com
domain in a Kubernetes secret.kubectl create -n kgateway-system secret tls petstore-credential \ --key=example_certs/petstore.example.com.key \ --cert=example_certs/petstore.example.com.crt
Set up SNI routing
Set up an SNI Gateway that serves multiple hosts on the same port.
-
Create an SNI Gateway. The Gateway defines two hosts on the same HTTPS listener. Each host is configured with the host-specific TLS certificate that you set up earlier.
kubectl apply -f- <<EOF kind: Gateway apiVersion: gateway.networking.k8s.io/v1 metadata: name: sni namespace: kgateway-system labels: gateway: sni spec: gatewayClassName: kgateway listeners: - protocol: HTTPS port: 443 name: httpbin hostname: "httpbin.example.com" tls: mode: Terminate certificateRefs: - name: httpbin-credential kind: Secret allowedRoutes: namespaces: from: All - protocol: HTTPS port: 443 name: petstore hostname: "petstore.example.com" tls: mode: Terminate certificateRefs: - name: petstore-credential kind: Secret allowedRoutes: namespaces: from: All EOF
Review the following table to understand this configuration.
Setting Description spec.gatewayClassName
The name of the Kubernetes GatewayClass that you want to use to configure the gateway. When you set up kgateway, a default GatewayClass is set up for you. To view the GatewayClass configuration, see Default Gateway setup. spec.listeners
Configure the listeners for this gateway. In this example, you configure two HTTPS listeners. One listener is for the httpbin app and the other is for the petstore app. Each listener refers to a secret that holds the TLS certificate and key for the hostname that the listener is configured for. spec.listeners.tls.mode
The TLS mode that you want to use for incoming requests. In this example, HTTPS requests are terminated at the gateway and the unencrypted request is forwarded to the service in the cluster. spec.listeners.tls.certificateRefs
The Kubernetes secret that holds the TLS certificate and key for the gateway. The gateway uses these credentials to establish the TLS connection with a client, and to decrypt incoming HTTPS requests. -
Create a Gateway that enables the attachment of ListenerSets.
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: sni namespace: kgateway-system labels: gateway: sni spec: gatewayClassName: kgateway allowedListeners: namespaces: from: All listeners: - protocol: HTTP port: 80 name: http allowedRoutes: namespaces: from: All EOF
Review the following table to understand this configuration.
Setting Description spec.gatewayClassName
The name of the Kubernetes GatewayClass that you want to use to configure the gateway. When you set up kgateway, a default GatewayClass is set up for you. To view the GatewayClass configuration, see Default Gateway setup. spec.allowedListeners
Enable the attachment of ListenerSets to this Gateway. The example allows listeners from any namespace, which is helpful in multitenant environments. You can also limit the allowed listeners. To limit to listeners in the same namespace as the Gateway, set this value to Same
. To limit to listeners with a particular label, set this value toSelector
.spec.listeners
Optionally, you can configure a listener that is specific to the Gateway. Note that due to a Gateway API limitation, you must configure at least one listener on the Gateway resource, even if the listener is not used and is a generic, “dummy” listener. This generic listener cannot conflict with the listener that you configure in the ListenerSet, such as using the same port or name. In this example, the generic listener is configured on HTTP port 80, which differs from the HTTPS port 443 in the ListenerSet that you create later. -
Create a ListenerSet that configures an HTTPS listener for each app that the Gateway serves.
kubectl apply -f- <<EOF apiVersion: gateway.networking.x-k8s.io/v1alpha1 kind: XListenerSet metadata: name: sni-listenerset namespace: kgateway-system labels: gateway: sni spec: parentRef: name: sni namespace: kgateway-system kind: Gateway group: gateway.networking.k8s.io listeners: - protocol: HTTPS port: 443 name: httpbin hostname: "httpbin.example.com" tls: mode: Terminate certificateRefs: - name: httpbin-credential kind: Secret allowedRoutes: namespaces: from: All - protocol: HTTPS port: 443 name: petstore hostname: "petstore.example.com" tls: mode: Terminate certificateRefs: - name: petstore-credential kind: Secret allowedRoutes: namespaces: from: All EOF
Review the following table to understand this configuration.
Setting Description spec.parentRef
The name of the Gateway to attach the ListenerSet to. spec.listeners
Configure the listeners for this gateway. In this example, you configure two HTTPS listeners. One listener is for the httpbin app and the other is for the petstore app. Each listener refers to a secret that holds the TLS certificate and key for the hostname that the listener is configured for. spec.listeners.tls.mode
The TLS mode that you want to use for incoming requests. In this example, HTTPS requests are terminated at the gateway and the unencrypted request is forwarded to the service in the cluster. spec.listeners.tls.certificateRefs
The Kubernetes secret that holds the TLS certificate and key for the gateway. The gateway uses these credentials to establish the TLS connection with a client, and to decrypt incoming HTTPS requests.
-
-
Create an HTTPRoute that routes incoming requests on the
httpbin.example.com
domain to the httpbin app.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httpbin-https namespace: httpbin labels: example: httpbin-route gateway: sni spec: parentRefs: - name: sni namespace: kgateway-system hostnames: - "httpbin.example.com" rules: - backendRefs: - name: httpbin port: 8000 EOF
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httpbin-https namespace: httpbin labels: example: httpbin-route gateway: sni spec: parentRefs: - name: sni-listenerset namespace: kgateway-system kind: XListenerSet group: gateway.networking.x-k8s.io hostnames: - "httpbin.example.com" rules: - backendRefs: - name: httpbin port: 8000 EOF
-
Create an HTTPRoute that routes incoming requests on the
petstore.example.com
domain to the petstore app.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: petstore-https namespace: default labels: example: petstore-route gateway: sni spec: parentRefs: - name: sni namespace: kgateway-system hostnames: - "petstore.example.com" rules: - backendRefs: - name: petstore port: 8080 EOF
kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: petstore-https namespace: default labels: example: petstore-route gateway: sni spec: parentRefs: - name: sni-listenerset namespace: kgateway-system kind: XListenerSet group: gateway.networking.x-k8s.io hostnames: - "petstore.example.com" rules: - backendRefs: - name: petstore port: 8080 EOF
-
Get the external address of the Gateway and save it in an environment variable. Note that it might take a few seconds for the Gateway address to become available.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system sni -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESS
kubectl port-forward svc/sni -n kgateway-system 8443:443
-
Send a request to the
httpbin.example.com
domain with the client certificate that you created earlier. Verify that the gateway presents the TLS certificate for thehttpbin.example.com
domain during the TLS handshake.curl -vik --resolve "httpbin.example.com:443:${INGRESS_GW_ADDRESS}" https://httpbin.example.com:443/anything
curl -vik --resolve "httpbin.example.com:443:$(dig +short $INGRESS_GW_ADDRESS | head -n1)" https://httpbin.example.com:443/anything
curl -vik --connect-to httpbin.example.com:443:localhost:8443 https://httpbin.example.com:443/anything
Example output:
* Added httpbin.example.com:443:3.XXX.XXX.XX to DNS cache * Hostname httpbin.example.com was found in DNS cache * Trying 3.128.214.17:443... * Connected to httpbin.example.com (3.XXX.XXX.XX) port 443 * ALPN: curl offers h2,http/1.1 * (304) (OUT), TLS handshake, Client hello (1): * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: CN=httpbin.example.com; O=httpbin organization * issuer: O=example Inc.; CN=example.com * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://httpbin.example.com:443/headers * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: httpbin.example.com] * [HTTP/2] [1] [:path: /headers] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: */*] > GET /headers HTTP/2 > Host: httpbin.example.com > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/2 200 HTTP/2 200 ... "Accept": [ "*/*" ], "Host": [ "httpbin.example.com" ], "User-Agent": [ "curl/8.7.1" ], "X-Envoy-Expected-Rq-Timeout-Ms": [ "15000" ], "X-Forwarded-Proto": [ "https" ], "X-Request-Id": [ "33654cba-7198-4b7c-a850-8629fd230145" ] } }
-
Send a request to the
petstore.example.com
domain with the client certificate that you created earlier. Verify that the gateway presents the TLS certificate for thepetstore.example.com
domain during the TLS handshake.curl -vik --resolve "petstore.example.com:443:${INGRESS_GW_ADDRESS}" https://petstore.example.com:443/api/pets
curl -vik --resolve "petstore.example.com:443:$(dig +short $INGRESS_GW_ADDRESS | head -n1)" https://petstore.example.com:443/api/pets
curl -vik --connect-to petstore.example.com:443:localhost:8443 https://petstore.example.com:443/api/pets
Example output:
* Added petstore.example.com:443:3.XXX.XXX.XX to DNS cache * Hostname petstore.example.com was found in DNS cache * Trying 3.128.214.17:443... * Connected to petstore.example.com (3.XXX.XXX.XX) port 443 * ALPN: curl offers h2,http/1.1 * (304) (OUT), TLS handshake, Client hello (1): * (304) (IN), TLS handshake, Server hello (2): * (304) (IN), TLS handshake, Unknown (8): * (304) (IN), TLS handshake, Certificate (11): * (304) (IN), TLS handshake, CERT verify (15): * (304) (IN), TLS handshake, Finished (20): * (304) (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256 / [blank] / UNDEF * ALPN: server accepted h2 * Server certificate: * subject: CN=petstore.example.com; O=petstore organization * issuer: O=example Inc.; CN=example.com * SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway. * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://petstore.example.com:443/api/pets * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: petstore.example.com] * [HTTP/2] [1] [:path: /api/pets] * [HTTP/2] [1] [user-agent: curl/8.7.1] * [HTTP/2] [1] [accept: */*] > GET /api/pets HTTP/2 > Host: petstore.example.com > User-Agent: curl/8.7.1 > Accept: */* > * Request completely sent off < HTTP/2 200 HTTP/2 200 ... [{"id":1,"name":"Dog","status":"available"},{"id":2,"name":"Cat","status":"pending"}]
Cleanup
You can remove the resources that you created in this guide.-
Remove the routing resources for the HTTPS route, including the Kubernetes secret that holds the TLS certificate and key.
kubectl delete secret httpbin-credential -n kgateway-system kubectl delete secret petstore-credential -n kgateway-system kubectl delete gateway sni -n kgateway-system kubectl delete httproute httpbin-https -n httpbin kubectl delete httproute petstore-https -n default kubectl delete deployment petstore kubectl delete service petstore
kubectl delete secret httpbin-credential -n kgateway-system kubectl delete secret petstore-credential -n kgateway-system kubectl delete gateway sni -n kgateway-system kubectl delete xlistenerset sni-listenerset -n kgateway-system kubectl delete httproute httpbin-https -n httpbin kubectl delete httproute petstore-https -n default kubectl delete deployment petstore kubectl delete service petstore
-
Remove the
example_certs
directory that stores your TLS credentials.rm -rf example_certs