About this guide

In this guide, you learn how to set up an mTLS Gateway. Before the client application and the Gateway establish a connection, both parties must exchange certificates to verify their identities. After a TLS connection is established, the TLS connection is terminated at the Gateway and the unencrypted HTTP traffic is forwarded to the backend destination.

Before you begin

  1. Follow the Get started guide to install Gloo Gateway and deploy the httpbin sample app.

  2. Make sure that you have the OpenSSL version of openssl, not LibreSSL. The openssl version must be at least 1.1.

    1. Check your openssl version. If you see LibreSSL in the output, continue to the next step.
        openssl version
        
    2. Install the OpenSSL version (not LibreSSL). For example, you might use Homebrew.
        brew install openssl
        
    3. 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...

Create self-signed TLS certificates

Create self-signed TLS certificates that you use for the mutual TLS connection between your client application (curl) and the gateway proxy.

  1. Create a root certificate for the example.com domain. You use this certificate to sign the certificate for your client and gateway later.

      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
      
  2. Create a gateway certificate that is signed by the root CA certificate that you created in the previous step.

      openssl req -out example_certs/gateway.csr -newkey rsa:2048 -nodes -keyout example_certs/gateway.key -subj "/CN=*/O=any domain"
    openssl x509 -req -sha256 -days 365 -CA example_certs/example.com.crt -CAkey example_certs/example.com.key -set_serial 0 -in example_certs/gateway.csr -out example_certs/gateway.crt
      
  3. Create a Kubernetes secret to store your gateway TLS certificate. You create the secret in the same cluster and namespace that the gateway is deployed to. By including a rootca certificate, Gloo Gateway is automatically configured for mutual TLS with the downstream application.

      export TLS_CERT="$(< example_certs/gateway.crt)"  
    export TLS_KEY="$(< example_certs/gateway.key)"  
    export CA_CERT="$(< example_certs/example.com.crt)"
    
    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Secret
    metadata:
      name: https
      namespace: gloo-system
    type: kubernetes.io/tls
    stringData:
      tls.crt: |
    $(echo "$TLS_CERT" | sed 's/^/    /')
      tls.key: |
    $(echo "$TLS_KEY" | sed 's/^/    /')
      ca.crt: | 
    $(echo "$CA_CERT" | sed 's/^/    /')
    EOF
      
  4. Create a client certificate and private key. You use these credentials later when sending a request to the gateway proxy. The client certificate is signed with the same root CA certificate that you used for the gateway proxy.

      openssl req -out example_certs/client.example.com.csr -newkey rsa:2048 -nodes -keyout example_certs/client.example.com.key -subj "/CN=client.example.com/O=client 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/client.example.com.csr -out example_certs/client.example.com.crt
      

Set up an mTLS listener

  1. Create a Gateway that is configured with the TLS certificates that you set up earlier.

      kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: https
      namespace: gloo-system
      labels:
        gateway: https
    spec:
      gatewayClassName: gloo-gateway
      listeners:
        - name: https
          port: 443
          protocol: HTTPS
          hostname: https.example.com
          tls:
            mode: Terminate
            certificateRefs:
              - name: https
                kind: Secret
          allowedRoutes:
            namespaces:
              from: All
    EOF
      
  2. Create an HTTPRoute for the httpbin app and add it to the HTTPS gateway that you created.

      kubectl apply -f- <<EOF
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: httpbin-https
      namespace: httpbin
      labels:
        example: httpbin-route
        gateway: https
    spec:
      parentRefs:
        - name: https
          namespace: gloo-system
      rules:
        - backendRefs:
            - name: httpbin
              port: 8000
    EOF  
      
  3. Verify that the HTTPRoute is applied successfully.

      kubectl get httproute/httpbin-https -n httpbin -o yaml
      
  4. 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.

  5. Send a request to the httpbin app. Verify that you see the TLS handshake and that you get back a 200 HTTP response code.

    Example output:

      * 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, Request CERT (13):
    * (304) (IN), TLS handshake, Certificate (11):
    * (304) (IN), TLS handshake, CERT verify (15):
    * (304) (IN), TLS handshake, Finished (20):
    * (304) (OUT), TLS handshake, Certificate (11):
    * (304) (OUT), TLS handshake, CERT verify (15):
    * (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=*; O=any domain
    *  start date: Apr 30 19:20:03 2025 GMT
    *  expire date: Apr 30 19:20:03 2026 GMT
    *  issuer: O=any domain; CN=*
    *  SSL certificate verify result: unable to get local issuer certificate (20), continuing   anyway.
    * using HTTP/2
    * [HTTP/2] [1] OPENED stream for https://https.example.com:443/anything
    * [HTTP/2] [1] [:method: GET]
    * [HTTP/2] [1] [:scheme: https]
    * [HTTP/2] [1] [:authority: https.example.com]
    * [HTTP/2] [1] [:path: /anything]
    * [HTTP/2] [1] [user-agent: curl/8.7.1]
    * [HTTP/2] [1] [accept: */*]
    > GET /anything HTTP/2
    > Host: https.example.com
    > User-Agent: curl/8.7.1
    > Accept: */*
    > 
    * Request completely sent off
    < HTTP/2 200 
    HTTP/2 200 
    ...
    {
      "args": {},
      "headers": {
        "Accept": [
          "*/*"
        ],
        "Host": [
          "https.example.com"
        ],
        "User-Agent": [
          "curl/8.7.1"
        ],
        "X-Envoy-Expected-Rq-Timeout-Ms": [
          "15000"
        ],
        "X-Forwarded-Proto": [
          "https"
        ],
        "X-Request-Id": [
          "01ef350c-4587-4350-aa0e-0712fee757b9"
        ]
      },
      "url": "https://https.example.com/anything",
      ...
    }
      

Cleanup

  kubectl delete httproute httpbin-https -n httpbin
kubectl delete gateway https -n gloo-system 
kubectl delete secret https -n gloo-system 
rm -rf example_certs