Mutual TLS (mTLS)

Set up a mutual TLS (mTLS) listener on the gateway that serves a single host. Before a TLS connection is established between a client and the ingress gateway, the gateway and the client exchange certificates to verify each other's identity. After successful validation, the TLS connection to the gateway is established. After the gateway receives the request, the TLS connection is terminated, and the unencrypted HTTP request is forwarded to the destination in the cluster.

mTLS listener setup

The steps in this guide show how to set up an mTLS listener for the httpbin.example.com domain.

Before you begin

  1. Set up Gloo Gateway in a single cluster as described in the getting started guide.
  2. Deploy the httpbin sample app.
  3. The default openssl version that is included in macOS is LibreSSL, which does not work with these instructions.

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

    1. Check the openssl version that is installed. 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 the TLS certificates to use for mTLS

To enable mTLS between a client and the gateway, you must create server and client TLS certificates.

  1. Create a root certificate for the example.com domain. You use this certificate to sign the server and client certificates 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 server certificate and private 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
    
  3. Create a secret that stores the server certificate and key for the httpbin.example.com domain.

    kubectl create -n gloo-mesh-gateways secret generic httpbin-credential \
      --from-file=tls.key=example_certs/httpbin.example.com.key \
      --from-file=tls.crt=example_certs/httpbin.example.com.crt \
     --from-file=ca.crt=example_certs/example.com.crt
    
  4. Create a client certificate and key.

    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

To enable an mTLS listener, you must create a virtual gateway that uses the server credentials that you created earlier. Then, you set up routing rules for the httpbin app.

  1. Create the virtual gateway to configure your mTLS listener. Make sure to set the spec.tls.mode to MUTUAL and to reference the httpbin-credential Kubernetes secret that you created in the spec.tls.secretName field.

    kubectl apply -f- <<EOF
    apiVersion: networking.gloo.solo.io/v2
    kind: VirtualGateway
    metadata:
      annotations:
        cluster.solo.io/cluster: ""
      name: istio-ingressgateway
      namespace: default
    spec:
      listeners:
      - allowedRouteTables:
        - host: httpbin.example.com
        http: {}
        port:
          number: 443
        tls: 
          mode: MUTUAL
          secretName: httpbin-credential
      workloads:
      - selector:
          labels:
            istio: ingressgateway
    EOF
    
  2. Create a route table to route incoming requests on the httpbin.example.com domain to the httpbin app that you deployed earlier.

    kubectl apply -f- <<EOF
    apiVersion: networking.gloo.solo.io/v2
    kind: RouteTable
    metadata:
      annotations:
        cluster.solo.io/cluster: ""
      name: mtls
      namespace: default
    spec:
      hosts:
      - httpbin.example.com
      http:
      - forwardTo:
          destinations:
          - port:
              number: 8000
            ref:
              cluster: $CLUSTER_NAME
              name: httpbin
              namespace: default
        matchers: 
          - uri:
              prefix: /status
          - uri:
              prefix: /delay
      virtualGateways:
      - name: istio-ingressgateway
        namespace: default
    EOF
    
  3. Get the external address of the ingress gateway. If you deployed your ingress gateway in a different namespace or with a different version, update the command.

    export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    echo $INGRESS_GW_IP
    
    export INGRESS_GW_IP=$(kubectl get svc -n gloo-mesh-gateways istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
    echo $INGRESS_GW_IP
    

  4. Send a request to the httpbin.example.com domain. The /delay/2 endpoint delays forwarding your request by 2 seconds. In your CLI output, verify that you get back a 200 HTTP response code and that you can see the successful TLS handshake between the server and the client.

    curl -vik --resolve httpbin.example.com:443:$INGRESS_GW_IP --cacert example_certs/example.com.crt --cert example_certs/client.example.com.crt --key example_certs/client.example.com.key https://httpbin.example.com:443/delay/2 
    

    Example output:

    * Added httpbin.example.com:443:34.XXX.XX.XXX to DNS cache
    * Hostname httpbin.example.com was found in DNS cache
    *   Trying 34.XXX.XX.XXX:443...
    * Connected to httpbin.example.com (34.XXX.XX.XXX) port 443 (#0)
    * ALPN, offering h2
    * ALPN, offering http/1.1
    * successfully set certificate verify locations:
    *  CAfile: example_certs1/example.com.crt
    *  CApath: none
    * TLSv1.2 (OUT), TLS handshake, Client hello (1):
    * TLSv1.2 (IN), TLS handshake, Server hello (2):
    * TLSv1.2 (IN), TLS handshake, Certificate (11):
    * TLSv1.2 (IN), TLS handshake, Server key exchange (12):
    * TLSv1.2 (IN), TLS handshake, Request CERT (13):
    * TLSv1.2 (IN), TLS handshake, Server finished (14):
    * TLSv1.2 (OUT), TLS handshake, Certificate (11):
    * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
    * TLSv1.2 (OUT), TLS handshake, CERT verify (15):
    * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
    * TLSv1.2 (OUT), TLS handshake, Finished (20):
    * TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
    * TLSv1.2 (IN), TLS handshake, Finished (20):
    * SSL connection using TLSv1.2 / ECDHE-RSA-CHACHA20-POLY1305
    * ALPN, server accepted to use h2
    * Server certificate:
    *  subject: CN=httpbin.example.com; O=httpbin organization
    *  start date: Apr 19 17:54:57 2023 GMT
    *  expire date: Apr 18 17:54:57 2024 GMT
    *  issuer: O=example Inc.; CN=example.com
    *  SSL certificate verify ok.
    * Using HTTP2, server supports multi-use
    * Connection state changed (HTTP/2 confirmed)
    * Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
    * Using Stream ID: 1 (easy handle 0x13f812400)
    > GET /delay/2 HTTP/2
    > Host: httpbin.example.com
    > user-agent: curl/7.77.0
    > accept: */*
    > 
    * Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!
    < HTTP/2 200 
    HTTP/2 200 
       
    {
      "args": {}, 
      "data": "", 
      "files": {}, 
      "form": {}, 
      "headers": {
        "Accept": "*/*", 
        "Host": "httpbin.example.com", 
        "User-Agent": "curl/7.77.0", 
        "X-B3-Sampled": "0", 
        "X-B3-Spanid": "2b6d648c90a92d17", 
        "X-B3-Traceid": "17065b8c374e4cdd2b6d648c90a92d17", 
        "X-Envoy-Attempt-Count": "1", 
        "X-Envoy-Decorator-Operation": "httpbin.default.svc.cluster.local:8000/delay*", 
        "X-Envoy-Internal": "true", 
        "X-Envoy-Peer-Metadata": "Ch...eiofj", 
        "X-Envoy-Peer-Metadata-Id": "router~10.232.0.52~istio-ingressgateway-1-20-6cfb94798b-dt6fv.gloo-mesh-gateways~gloo-mesh-gateways.svc.cluster.local", 
        "X-Forwarded-Client-Cert": "Hash=cccbb...3D%0A-----END%20CERTIFICATE-----%0A\";Subject=\"O=client organization,CN=client.example.com\";URI="
      }, 
      "origin": "10.5.0.18", 
      "url": "https://httpbin.example.com/delay/2"
    }
    * Connection #0 to host httpbin.example.com left intact
    

Cleanup

You can optionally remove the resources that you set up as part of this guide.
rm -r example_certs
kubectl delete virtualgateway istio-ingressgateway
kubectl delete routetable mtls
kubectl delete secret httpbin-credential -n gloo-mesh-gateways