Istio MTLS configuration: client validation

Istio MTLS

Istio supports MTLS to authenticate clients. This is configured using a Gateway resource. There’s great documentation on the configuration steps here. However, what’s not quite clear is how to validate client identity using the subjectAltName (SAN) presented in the client certificate. In the example from Istio documentation, any client who presents a certificate using a given CA is accepted which is not desired in certain cases e.g. server exposed to multiple clients in the same network but accepts traffic only from specific clients.

Starting from the example here, MTLS client validation is done by adding tls.subjectAltNames

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
... <snip> ...
tls:
     mode: MUTUAL
     credentialName: httpbin-credential
     subjectAltNames: 
      - client.example.com

The caveat is, Istio validates only the SAN and not the Common Name (CN).

To create a CSR with SANs, do

openssl req -nodes -newkey rsa:2048 -subj "/CN=client.example.com/O=client organization" -out client.example.com.csr -pubkey -new -keyout client.example.com.key -sha256 -config ssl.cnf

where ssl.cnf is

[ CA_default ]
copy_extensions = copy

[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[req_distinguished_name]

[ v3_req ]
# Extensions to add to a certificate request
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = client.example.com

To sign the client certificate, do

openssl x509 -req -in client.example.com.csr -CA example.com.crt -CAkey example.com.key -CAcreateserial -out client.example.com.crt -days 3650 -sha256 -extfile ssl-ext.cnf

where ssl-ext.cnf is

basicConstraints = CA:FALSE
subjectAltName = client.example.com

and CA certificate/key are generated from

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=example Inc./CN=example.com' -keyout example.com.key -out example.com.crt

Server certificates installed installed in Gateway.tls.credentialName are generated from

openssl req -out httpbin.example.com.csr -newkey rsa:2048 -nodes -keyout httpbin.example.com.key -subj "/CN=httpbin.example.com/O=httpbin organization"
openssl x509 -req -days 365 -CA example.com.crt -CAkey example.com.key -set_serial 0 -in httpbin.example.com.csr -out httpbin.example.com.crt

The credential contains

$ kubectl describe secrets/httpbin-credential -n istio-system

... <snip> ...

Data
====
ca.crt:   1046 bytes
tls.crt:  1054 bytes
tls.key:  1708 bytes

To test (against the httpbin server in the example), run

curl -vvv -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert example.com.crt --cert client.example.com.crt --key client.example.com.key \
"https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"