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...
  3. Decide whether to set up an HTTP listener inline on the Gateway resource or as a separate ListenerSet resource. Note that ListenerSets are an experimental feature in the upstream Kubernetes Gateway API project, and subject to change. For more information, see the Listener overview.

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.

  2. Create an HTTPRoute for the httpbin app and add it to the HTTPS gateway that you created.

  3. Verify that the HTTPRoute is applied successfully.

      kubectl get httproute/httpbin-https -n httpbin -o yaml
      

    Example output: Notice in the status section that the parentRef is either the Gateway or the ListenerSet, depending on how you attached the HTTPRoute.

      ...
    status:
      parents:
      - conditions:
        - lastTransitionTime: "2025-04-29T20:48:51Z"
          message: ""
          observedGeneration: 3
          reason: Accepted
          status: "True"
          type: Accepted
        - lastTransitionTime: "2025-04-29T20:48:51Z"
          message: ""
          observedGeneration: 3
          reason: ResolvedRefs
          status: "True"
          type: ResolvedRefs
        controllerName: solo.io/gloo-gateway
      parentRef:
        group: gateway.networking.x-k8s.io
        kind: XListenerSet
        name: https-listenerset
        namespace: gloo-system
      
  4. Verify that the listener now has a route attached.

  5. 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.

  6. 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

  1. Remove the routing resources for the HTTPS route, including the Kubernetes secret that holds the TLS certificate and key.

  2. Remove the example_certs directory that stores your TLS credentials.

      rm -rf example_certs