Mutual TLS (mTLS) on the ASM ingress gateway requires clients to present a certificate during the TLS handshake, proving their identity. After authentication, authorization policies control what each client identity can access. This two-layer approach separates authentication (mTLS verifies who the client is) from authorization (policies control what the client can do).
This guide walks through three tasks:
-
Generate the certificates for mTLS (CA, server, and client).
-
Configure the ASM ingress gateway to require client certificates on port 443.
-
Create an authorization policy that denies a specific client identity access to a specific path.
How client identity maps to authorization rules
The client certificate includes a SPIFFE URI in its Subject Alternative Name (SAN), such as spiffe://test.client. ASM authorization policies use the principals field to match this identity. The policy matches the value after the spiffe:// prefix. For example, a principals value of test.client matches a certificate with URI.1 = spiffe://test.client.
This mapping is the core mechanism that connects mTLS authentication to path-based access control.
Prerequisites
Before you begin, make sure that you have:
-
Automatic sidecar injection enabled. For more information, see Configure sidecar proxy injection policies
-
The ingress gateway and httpbin application deployed, with port 443 enabled on the ingress gateway. For more information, see Deploy the httpbin application
-
OpenSSL installed on your local machine
Step 1: Generate mTLS certificates
mTLS requires three certificate sets: a root CA, a server certificate, and a client certificate. The root CA signs both the server and client certificates. The gateway presents the server certificate to identify itself, and clients present the client certificate to prove their identity.
When prompted for certificate fields during generation, accept the default values. The configuration files below pre-set all required fields.
-
Create a file named
ca.cnfwith the following content. -
Generate the root CA certificate and private key.
openssl req -x509 -config ca.cnf -newkey rsa:4096 -sha256 -nodes -out cacert.pem -outform PEMThis produces two files:
cacert.pem(the CA certificate) andcakey.pem(the CA private key). -
Create a file named
server.cnfwith the following content.HOME = . RANDFILE = $ENV::HOME/.rnd #################################################################### [ req ] default_bits = 2048 default_keyfile = serverkey.pem distinguished_name = server_distinguished_name req_extensions = server_req_extensions string_mask = utf8only #################################################################### [ server_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = bj localityName = Locality Name (eg, city) localityName_default = bj organizationName = Organization Name (eg, company) organizationName_default = test commonName = Common Name (e.g. server FQDN or YOUR name) commonName_default = test.com emailAddress = Email Address emailAddress_default = test@example.com #################################################################### [ server_req_extensions ] subjectKeyIdentifier = hash basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment subjectAltName = @alternate_names nsComment = "OpenSSL Generated Certificate" #################################################################### [ alternate_names ] DNS.1 = test.com -
Generate the server certificate, initialize the CA signing database, and sign the certificate.
# Generate the CSR and private key openssl req -config server.cnf -newkey rsa:2048 -sha256 -nodes -out server.csr -outform PEM # Initialize the CA signing database touch index.txt echo '01' > serial.txt # Sign the server certificate with the root CA openssl ca -config ca.cnf -policy signing_policy -extensions signing_req -out servercert.pem -infiles server.csrThis produces two files:
servercert.pem(the server certificate) andserverkey.pem(the server private key). -
Create a file named
client.cnfwith the following content.HOME = . RANDFILE = $ENV::HOME/.rnd #################################################################### [ req ] default_bits = 2048 default_keyfile = client.key.pem distinguished_name = server_distinguished_name req_extensions = server_req_extensions string_mask = utf8only #################################################################### [ server_distinguished_name ] countryName = Country Name (2 letter code) countryName_default = CN stateOrProvinceName = State or Province Name (full name) stateOrProvinceName_default = bj localityName = Locality Name (eg, city) localityName_default = bj organizationName = Organization Name (eg, company) organizationName_default = test.client commonName = Common Name (e.g. server FQDN or YOUR name) commonName_default = test.client emailAddress = Email Address emailAddress_default = test.client@example.com #################################################################### [ server_req_extensions ] subjectKeyIdentifier = hash basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment subjectAltName = @alternate_names nsComment = "OpenSSL Generated Certificate" #################################################################### [ alternate_names ] URI.1 = spiffe://test.clientTwo fields in this configuration determine the client identity for authorization:
Field Value Role commonName_defaulttest.clientIdentifies the client URI.1(SAN)spiffe://test.clientMatched by the principalsfield in authorization policies (after thespiffe://prefix) -
Generate the client certificate and sign it with the root CA.
# Generate the CSR and private key openssl req -config client.cnf -newkey rsa:2048 -sha256 -nodes -out clientcert.csr -outform PEM # Sign the client certificate with the root CA openssl ca -config ca.cnf -policy signing_policy -extensions signing_req -out clientcert.pem -infiles clientcert.csrThis produces two files:
clientcert.pem(the client certificate) andclient.key.pem(the client private key). -
Import the mTLS certificate using the ASM certificate management feature. Set the certificate name to
test.com. For more information, see Use the certificate management feature of ASM.Alternatively, create a Kubernetes Secret directly. Run the following command using the kubeconfig of your data plane cluster:
kubectl create -n istio-system secret generic test.com \ --from-file=tls.key=serverkey.pem \ --from-file=tls.crt=servercert.pem \ --from-file=ca.crt=cacert.pemThis Secret bundles three files:
Key File Purpose tls.keyserverkey.pemServer private key for TLS termination tls.crtservercert.pemServer certificate presented to clients ca.crtcacert.pemRoot CA certificate used to verify client certificates
Step 2: Configure the mTLS listener on the gateway
Add an mTLS listener on port 443 of the ASM ingress gateway. After this configuration, external clients must present a valid client certificate to access the httpbin service.
-
Update the Gateway resource with the following content.
apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: httpbin namespace: default spec: selector: istio: ingressgateway servers: - hosts: - '*' port: name: test number: 80 protocol: HTTP - hosts: - test.com port: number: 443 name: https protocol: HTTPS tls: mode: MUTUAL credentialName: test.comThe
tls.mode: MUTUALsetting requires clients to present a certificate signed by the CA in thetest.comSecret. Without a valid client certificate, the TLS handshake fails and the gateway rejects the connection.NoteOther TLS modes include
SIMPLE(server-side TLS only, no client certificate required) andPASSTHROUGH(TLS is not terminated at the gateway). UseMUTUALwhen you need to authenticate client identity at the gateway level. -
Verify that mTLS rejects requests without a client certificate.
curl -v --resolve "test.com:443:<ASM-gateway-IP>" \ --cacert cacert.pem \ https://test.com/status/200Replace
<ASM-gateway-IP>with the IP address of your ASM ingress gateway.The request fails with a TLS handshake error because no client certificate is provided. This confirms that the gateway enforces mTLS.
-
Verify that mTLS accepts requests with a valid client certificate.
curl --header "host:test.com" \ --resolve "test.com:443:<ASM-gateway-IP>" \ --cacert cacert.pem \ --cert clientcert.pem \ --key client.key.pem \ https://test.com/status/200 -IExpected output:
HTTP/2 200 server: istio-envoy date: Sun, 28 Jul 2024 7:30:30 GMT content-type: text/html; charset=utf-8 access-control-allow-origin: * access-control-allow-credentials: true content-length: 0 x-envoy-upstream-service-time: 6An
HTTP/2 200response confirms that the gateway accepted the client certificate and forwarded the request to httpbin.
Step 3: Restrict client access with an authorization policy
After mTLS authenticates each client, authorization policies control what each client can access. In this step, create a DENY policy that blocks the test.client identity from accessing a specific path. Other clients remain unaffected.
When multiple authorization policies exist on the same workload, DENY policies are evaluated before ALLOW policies. A request that matches any DENY rule is rejected regardless of ALLOW rules.
-
Deploy the following AuthorizationPolicy to deny
test.clientaccess to the/status/418path on the httpbin application. For more information, see Configure authorization policies for HTTP requests.apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: test namespace: istio-system spec: action: DENY rules: - from: - source: principals: - test.client to: - operation: paths: - /status/418 selector: matchLabels: istio: ingressgatewayThis policy targets the ingress gateway (
istio: ingressgateway) and denies any request from thetest.clientprincipal to the/status/418path. Theprincipalsvaluetest.clientmatches the SPIFFE URIspiffe://test.clientin the client certificate's SAN.
Verify the authorization policy
-
Confirm that
test.clientcan still access other paths. Send a request to/status/200:curl --header "host:test.com" \ --resolve "test.com:443:<ASM-gateway-IP>" \ --cacert cacert.pem \ --cert clientcert.pem \ --key client.key.pem \ https://test.com/status/200 -IExpected output:
HTTP/2 200 server: istio-envoy date: Sun, 28 Jul 2024 7:33:30 GMT content-type: text/html; charset=utf-8 access-control-allow-origin: * access-control-allow-credentials: true content-length: 0 x-envoy-upstream-service-time: 6A
200response means the DENY policy applies only to/status/418, not other paths. -
Confirm that
test.clientis denied access to/status/418:curl --header "host:test.com" \ --resolve "test.com:443:<ASM-gateway-IP>" \ --cacert cacert.pem \ --cert clientcert.pem \ --key client.key.pem \ https://test.com/status/418Expected output:
RBAC: access deniedRBAC: access deniedconfirms the authorization policy blockedtest.clientfrom this path. -
Confirm that a different client identity can still access
/status/418. Send a request using the server certificate instead:curl --header "host:test.com" \ --resolve "test.com:443:<ASM-gateway-IP>" \ --cacert cacert.pem \ --cert servercert.pem \ --key serverkey.pem \ https://test.com/status/418Expected output:
-=[ teapot ]=- _...._ .' _ _ `. | ."` ^ `". _, \_;`"---"`|// | ;/ \_ _/ `"""`The teapot response confirms the DENY rule targets only the
test.clientidentity. Other identities with valid certificates can still access/status/418.