Cloud Service Mesh by example: mTLS Stay organized with collections Save and categorize content based on your preferences.
In Cloud Service Mesh 1.5 and later, auto mutual TLS (auto mTLS) is enabled bydefault. With auto mTLS, a client sidecar proxy automatically detects if theserver has a sidecar. The client sidecar sends mTLS to workloads with sidecarsand sends plaintext to workloads without sidecars. Note, however, servicesaccept both plaintext and mTLS traffic. As youinject sidecar proxies to your Pods, werecommend that you also configure your services to only accept mTLS traffic.
With Cloud Service Mesh, you can enforce mTLS, outside of your application code, byapplying a single YAML file. Cloud Service Mesh gives you the flexibility to apply anauthentication policy to the entire service mesh, to a namespace, or to anindividual workload.
Costs
In this document, you use the following billable components of Google Cloud:
To generate a cost estimate based on your projected usage, use thepricing calculator.
When you finish this tutorial, you can avoid ongoing costs by deleting theresources you created. For more information, seeClean up.
Before you begin
Provision Cloud Service Mesh on a GKE cluster. There arevarious supported setup methods:
Clone the repository:
git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samplescd anthos-service-mesh-samples
Deploy an ingress gateway
Set the current context for
Note: Usekubectlto the cluster:--regioninstead of--zone, if the cluster is a regionalcluster.gcloud container clusters get-credentialsCLUSTER_NAME \--project=PROJECT_ID \--zone=CLUSTER_LOCATIONCreate a namespace for your ingress gateway:
kubectl create namespace asm-ingressEnable the namespace for injection. The steps depend on yourcontrol plane implementation.
Managed (TD)
Apply the default injection label to the namespace:
kubectllabelnamespaceasm-ingress\istio.io/rev-istio-injection=enabled--overwriteManaged (Istiod)
Recommended: Run the following command to apply the default injection label to the namespace:
kubectllabelnamespaceasm-ingress\istio.io/rev-istio-injection=enabled--overwriteIf you are an existing user with the Managed Istiod control plane:We recommend that you use default injection, but revision-based injection issupported. Use the following instructions:
Run the following command to locate the available release channels:
kubectl-nistio-systemgetcontrolplanerevisionThe output is similar to the following:
Note: If two control plane revisions appear in the earlier list, remove one. Having multiple control plane channels in the cluster is not supported.NAME AGEasm-managed-rapid 6d7hIn the output, the value under the
NAMEcolumn is the revision label that corresponds to the availablerelease channel for the Cloud Service Mesh version.Apply the revision label to the namespace:
kubectllabelnamespaceasm-ingress\istio-injection-istio.io/rev=REVISION_LABEL--overwrite
Deploy the example gateway in the
anthos-service-mesh-samplesrepository:kubectl apply -n asm-ingress \-f docs/shared/asm-ingress-gatewayExpected output:
serviceaccount/asm-ingressgateway configuredservice/asm-ingressgateway configureddeployment.apps/asm-ingressgateway configuredgateway.networking.istio.io/asm-ingressgateway configured
Deploy the Online Boutique sample application
If you haven't, set the current context for
kubectlto the cluster:gcloud container clusters get-credentialsCLUSTER_NAME \ --project=PROJECT_ID \ --zone=CLUSTER_LOCATIONCreate the namespace for the sample application:
kubectl create namespace onlineboutiqueLabel the
onlineboutiquenamespace to automatically inject Envoy proxies. Follow the stepsto enable automatic sidecar injection.Deploy the sample app, the
VirtualServicefor the frontend, and service accounts for the workloads. For this tutorial, you will deployOnline Boutique, a microservice demo app.kubectl apply \ -n onlineboutique \ -f docs/shared/online-boutique/virtual-service.yaml kubectl apply \ -n onlineboutique \ -f docs/shared/online-boutique/service-accounts
View your services
View the pods in the
onlineboutiquenamespace:kubectl get pods -n onlineboutiqueExpected output:
NAME READY STATUS RESTARTS AGEadservice-85598d856b-m84m6 2/2 Running 0 2m7scartservice-c77f6b866-m67vd 2/2 Running 0 2m8scheckoutservice-654c47f4b6-hqtqr 2/2 Running 0 2m10scurrencyservice-59bc889674-jhk8z 2/2 Running 0 2m8semailservice-5b9fff7cb8-8nqwz 2/2 Running 0 2m10sfrontend-77b88cc7cb-mr4rp 2/2 Running 0 2m9sloadgenerator-6958f5bc8b-55q7w 2/2 Running 0 2m8spaymentservice-68dd9755bb-2jmb7 2/2 Running 0 2m9sproductcatalogservice-84f95c95ff-c5kl6 2/2 Running 0 114srecommendationservice-64dc9dfbc8-xfs2t 2/2 Running 0 2m9sredis-cart-5b569cd47-cc2qd 2/2 Running 0 2m7sshippingservice-5488d5b6cb-lfhtt 2/2 Running 0 2m7sAll of the pods for your application should be up and running, with a
2/2in theREADYcolumn. This indicates that the pods have an Envoy sidecar proxy injected successfully. If it does not show2/2after a couple of minutes, visit theTroubleshooting guide.Get the external IP, and set it to a variable:
kubectl get services -n asm-ingressexport FRONTEND_IP=$(kubectl --namespace asm-ingress \get service --output jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}' \)You see output similar to the following:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEasm-ingressgateway LoadBalancer 10.19.247.233 35.239.7.64 80:31380/TCP,443:31390/TCP,31400:31400/TCP 27mVisit the
EXTERNAL-IPaddress in your web browser. You should expect to seethe Online Boutique shop in your browser.
Create a TestCurl pod
Create a TestCurl pod to send plaintext traffic for testing.
apiVersion:v1kind:Podmetadata:name:testcurlnamespace:defaultannotations:sidecar.istio.io/inject:"false"spec:containers:-name:curlimage:curlimages/curlcommand:["sleep","600"]Access Online Boutique
Set the current context for
kubectlto the cluster where you deployedOnline Boutique:gcloud container clusters get-credentialsCLUSTER_NAME \ --project=PROJECT_ID \ --zone=CLUSTER_LOCATIONList the services in the
frontendnamespace:kubectl get services -n frontendNotice that
frontend-externalis aLoadBalancer, and it has anexternal IP address. The sample application includes a service that is aload balancer so that it can be deployed on GKE withoutCloud Service Mesh.Visit the application in your browser using the external IP address of the
frontend-externalservice:http://FRONTEND_EXTERNAL_IP/Cloud Service Mesh provides you the ability to deploy an ingress gateway. You canalso access the Online Boutique using the external IP address of the ingressgateway. Get the external IP of the gateway. Replace the placeholders withthe following information:
- GATEWAY_SERVICE_NAME : The name of the ingress gatewayservice. If you deployed the sample gateway without modification, or ifyou deployed thedefault ingress gateway,the name is
istio-ingressgateway. - GATEWAY_NAMESPACE: The namespace in which you deployedthe ingress gateway. If you deployed the default ingress gateway, thenamespace is
istio-system.
kubectl get serviceGATEWAY_NAME -nGATEWAY_NAMESPACE- GATEWAY_SERVICE_NAME : The name of the ingress gatewayservice. If you deployed the sample gateway without modification, or ifyou deployed thedefault ingress gateway,the name is
Open another tab in your browser and visit the application using theexternal IP address of the ingress gateway:
http://INGRESS_GATEWAY_EXTERNAL_IP/Run the following command to
curlthefrontendservice with plain HTTPfrom another Pod. Because the services are in different namespaces, youneed to curl theDNS name of thefrontendservice.kubectl debug --image istio/base --target istio-proxy -it \ $(kubectl get pod -l app=productcatalogservice -n product-catalog -o jsonpath={.items..metadata.name}) \ -n product-catalog -- \ curl http://frontend.frontend.svc.cluster.local:80/ -o /dev/null -s -w '%{http_code}\n'Your request succeeds with status
200, because by default, both TLS andplaintext traffic are accepted.
Enable mutual TLS per namespace
You enforce mTLS by applying aPeerAuthentication policy withkubectl.
Save the following authentication policy as
mtls-namespace.yaml.cat <<EOF > mtls-namespace.yamlapiVersion: "security.istio.io/v1beta1"kind: "PeerAuthentication"metadata: name: "namespace-policy"spec: mtls: mode: STRICTEOFThe line
mode: STRICTin the YAML configures the services to onlyaccept mTLS. By default, themodeisPERMISSIVE, which configuresservices to accept both plaintext and mTLS.Apply the authentication policy to configure all Online Boutiqueservices to only accept mTLS:
fornsinadcartcheckoutcurrencyemailfrontendloadgenerator\paymentproduct-catalogrecommendationshipping;dokubectlapply-n$ns-fmtls-namespace.yamldoneExpected output:
peerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy createdpeerauthentication.security.istio.io/namespace-policy created
Go to the tab in your browser that accesses the Online Boutique using theexternal IP address of the
frontend-externalservice:http://FRONTEND_EXTERNAL_IP/Refresh the page. The browser displays the following error:

Refreshing the page causes plaintext to be sent to the
frontendservice.Because of theSTRICTauthentication policy, the sidecar proxy blocks therequest to the service.Go to the tab in your browser that accesses the Online Boutique using theexternal IP address of the
istio-ingressgateway, and refresh the page,which displays successfully. When you access Online Boutique using theingress gateway, the request takes the following path:mTLS authentication flow:
- The browser sends a plaintext HTTP request to the server.
- The ingress gateway proxy container intercepts the request.
- The ingress gateway proxy performs a TLS handshake with theserver-side proxy (the frontend service in this example). This handshakeincludes an exchange of certificates. These certs are pre-loaded intothe proxy containers by Cloud Service Mesh.
- The ingress gateway proxy performs a secure naming check on theserver's certificate, verifying that an authorized identity is runningthe server.
- The ingress gateway and server proxies establish a mutual TLSconnection, and the server proxy forwards the request to the serverapplication container (the frontend service).
Run the following command to
curlthefrontendservice with plain HTTPfrom another Pod.kubectl exec testcurl -n default -- curl \ http://frontend.frontend.svc.cluster.local:80/ -o /dev/null -s -w '%{http_code}\n'Your request fails as we are sending plaintext traffic from sidecar-less workload where STRICT
peerAuthenticationpolicy is applied.
Find and delete authentication policies
For a list of all the
PeerAuthenticationpolicies in the service mesh:kubectl get peerauthentication --all-namespacesThe output is similar to the following:
NAMESPACE NAME MODE AGEad namespace-policy STRICT 17mcart namespace-policy STRICT 17mcheckout namespace-policy STRICT 17mcurrency namespace-policy STRICT 17memail namespace-policy STRICT 17mfrontend namespace-policy STRICT 17mloadgenerator namespace-policy STRICT 17mpayment namespace-policy STRICT 17mproduct-catalog namespace-policy STRICT 17mrecommendation namespace-policy STRICT 17mshipping namespace-policy STRICT 17mDelete the authentication policy from all of the Online Boutiquenamespaces:
fornsinadcartcheckoutcurrencyemailfrontendloadgeneratorpayment\product-catalogrecommendationshipping;dokubectldeletepeerauthentication-n$nsnamespace-policydone;Expected output:
peerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedpeerauthentication.security.istio.io "namespace-policy" deletedAccess the Online Boutique using the external IP address of the
frontend-externalservice, and refresh the page. The page displays asexpected.Run the following command to
curlthefrontendservice with plain HTTPfrom another Pod.kubectl debug --image istio/base --target istio-proxy -it \ $(kubectl get pod -l app=productcatalogservice -n product-catalog -o jsonpath={.items..metadata.name}) \ -n product-catalog -- \ curl http://frontend.frontend.svc.cluster.local:80/ -o /dev/null -s -w '%{http_code}\n'Your request succeeds with status
200, because by default, both TLS andplaintext traffic are accepted.
If you refresh the page in the Google Cloud console that displays theWorkloads list, it now shows that the mTLS status isPermissive.
Enable mutual TLS per workload
To set aPeerAuthentication policy for a specific workload, you must configuretheselector section and specify the labels that match the desired workload.However, Cloud Service Mesh can't aggregate workload-level policies for outboundmTLS traffic to a service. You need to configure a destination rule to managethat behavior.
Apply an authentication policy to a specific workload. Notice how thefollowing policy uses labels and selectors to target the specific
frontenddeployment.cat <<EOF | kubectl apply -n frontend -f -apiVersion: "security.istio.io/v1beta1"kind: "PeerAuthentication"metadata: name: "frontend" namespace: "frontend"spec: selector: matchLabels: app: frontend mtls: mode: STRICTEOFExpected output:
peerauthentication.security.istio.io/frontend created
Configure a matching destination rule.
cat <<EOF | kubectl apply -n frontend -f -apiVersion: "networking.istio.io/v1alpha3"kind: "DestinationRule"metadata: name: "frontend"spec: host: "frontend.demo.svc.cluster.local" trafficPolicy: tls: mode: ISTIO_MUTUALEOFExpected output:
destinationrule.networking.istio.io/frontend created
Access the Online Boutique using the external IP address of the
frontend-externalservice, and refresh the page. The page doesn'tdisplay because because thefrontend serviceis set toSTRICTmTLS, and the sidecar proxy blocks the request.Run the following command to
curlthefrontendservice with plain HTTPfrom another Pod.kubectl exec testcurl -n default -- curl \ http://frontend.frontend.svc.cluster.local:80/ -o /dev/null -s -w '%{http_code}\n'Your request fails as we are sending plaintext traffic from sidecar-less workload where STRICT
peerAuthenticationpolicy is applied.Delete the authentication policy:
kubectl delete peerauthentication -n frontend frontendExpected output:
peerauthentication.security.istio.io "frontend" deletedDelete the destination rule:
kubectl delete destinationrule -n frontend frontendExpected output:
destinationrule.networking.istio.io "frontend" deleted
Enforcing mesh-wide mTLS
To prevent all your services in the mesh from accepting plaintext traffic, seta mesh-widePeerAuthentication policy with the mTLS mode set toSTRICT.The mesh-widePeerAuthentication policy shouldn't have a selector and must beapplied in the root namespace,istio-system. When you deploy the policy, thecontrol plane automatically provisions TLS certificates so that workloads canauthenticate with each other.
Enforce mesh-wide mTLS:
kubectl apply -f - <<EOFapiVersion: "security.istio.io/v1beta1"kind: "PeerAuthentication"metadata: name: "mesh-wide" namespace: "istio-system"spec: mtls: mode: STRICTEOFExpected output:
peerauthentication.security.istio.io/mesh-wide created
Access the Online Boutique using the external IP address of the
frontend-externalservice, and refresh the page. The page doesn'tdisplay.Run the following command to
curlthefrontendservice with plain HTTPfrom another Pod.kubectl exec testcurl -n default -- curl \ http://frontend.frontend.svc.cluster.local:80/ -o /dev/null -s -w '%{http_code}\n'Your request fails as we are sending plaintext traffic from sidecar-less workload where STRICT
peerAuthenticationpolicy is applied.Delete the
mesh-widepolicy:kubectl delete peerauthentication -n istio-system mesh-wideExpected output:
peerauthentication.security.istio.io "mesh-wide" deleted
Clean up
To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, either delete the project that contains the resources, or keep the project and delete the individual resources.
If you want to prevent additional charges, delete the cluster:
gcloud container clusters deleteCLUSTER_NAME \ --project=PROJECT_ID \ --zone=CLUSTER_LOCATIONIf you want to keep your cluster and remove the Online Boutique sample:
- Delete the application namespaces:
kubectl delete -f online-boutique/kubernetes-manifests/namespacesExpected output:
namespace "ad" deletednamespace "cart" deletednamespace "checkout" deletednamespace "currency" deletednamespace "email" deletednamespace "frontend" deletednamespace "loadgenerator" deletednamespace "payment" deletednamespace "product-catalog" deletednamespace "recommendation" deletednamespace "shipping" deleted- Delete the service entries:
kubectl delete -f online-boutique/istio-manifests/allow-egress-googleapis.yamlExpected output:
serviceentry.networking.istio.io "allow-egress-googleapis" deletedserviceentry.networking.istio.io "allow-egress-google-metadata" deleted
What's next
- For a general guide on configuring
PeerAuthenticationpolicies, seeConfiguring transport security.
Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.
Last updated 2026-02-19 UTC.