Cloud Service Mesh by example: mTLS

Note: This guide only supports Cloud Service Mesh with Istio APIs and doesnot support Google Cloud APIs. For more information see,Cloud Service Mesh overview.

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.

mutual mTLS

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.

New Google Cloud users might be eligible for afree trial.

When you finish this tutorial, you can avoid ongoing costs by deleting theresources you created. For more information, seeClean up.

Before you begin

  • Make sure that billing is enabled for your Cloud project.Learn how toconfirm that billing is enabled for your project.

  • Install Cloud Service Mesh on a GKE cluster and deploy an ingressgateway. If you need to set up a cluster for this tutorial, see theCloud Service Mesh quickstart,which walks you through:

    • Creating a GKE cluster.
    • Provisions managed Cloud Service Mesh.
    • Deploying an ingress gateway.
    • Deploying the Online Boutique sample application from theanthos-service-mesh-packagesrepo, which is modified from the original set of manifests in themicroservices-demorepo. Following best practices, each service is deployed in a separate namespacewith a unique service account.
  • Create a TestCurl pod to send plaintext traffic for testing.

      apiVersion: v1  kind: Pod  metadata:    name: testcurl    namespace: default    annotations:      sidecar.istio.io/inject: "false"  spec:    containers:    - name: curl      image: curlimages/curl      command: ["sleep", "600"]

Access Online Boutique

  1. Set the current context forkubectl to the cluster where you deployedOnline Boutique:

    gcloud container clusters get-credentialsCLUSTER_NAME  \    --project=PROJECT_ID \    --zone=CLUSTER_LOCATION
  2. List the services in thefrontend namespace:

    kubectl get services -n frontend

    Notice thatfrontend-external is 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.

  3. Visit the application in your browser using the external IP address of thefrontend-external service:

    http://FRONTEND_EXTERNAL_IP/
  4. 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 isistio-ingressgateway.
    • GATEWAY_NAMESPACE: The namespace in which you deployedthe ingress gateway. If you deployed the default ingress gateway, thenamespace isistio-system.
    kubectl get serviceGATEWAY_NAME -nGATEWAY_NAMESPACE
  5. Open another tab in your browser and visit the application using theexternal IP address of the ingress gateway:

    http://INGRESS_GATEWAY_EXTERNAL_IP/
  6. Run the following command tocurl thefrontend service with plain HTTPfrom another Pod. Because the services are in different namespaces, youneed to curl theDNS name of thefrontend service.

    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 status200, because by default, both TLS andplaintext traffic are accepted.

Enable mutual TLS per namespace

You enforce mTLS by applying aPeerAuthentication policy withkubectl.

  1. Save the following authentication policy asmtls-namespace.yaml.

    cat <<EOF > mtls-namespace.yamlapiVersion: "security.istio.io/v1beta1"kind: "PeerAuthentication"metadata:  name: "namespace-policy"spec:  mtls:    mode: STRICTEOF

    The linemode: STRICT in the YAML configures the services to onlyaccept mTLS. By default, themode isPERMISSIVE, which configuresservices to accept both plaintext and mTLS.

  2. Apply the authentication policy to configure all Online Boutiqueservices to only accept mTLS:

    fornsinadcartcheckoutcurrencyemailfrontendloadgenerator\paymentproduct-catalogrecommendationshipping;dokubectlapply-n$ns-fmtls-namespace.yamldone

    Expected 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

  3. Go to the tab in your browser that accesses the Online Boutique using theexternal IP address of thefrontend-external service:

    http://FRONTEND_EXTERNAL_IP/
  4. Refresh the page. The browser displays the following error:

    site can't be reached

    Refreshing the page causes plaintext to be sent to thefrontend service.Because of theSTRICTauthentication policy, the sidecar proxy blocks therequest to the service.

  5. Go to the tab in your browser that accesses the Online Boutique using theexternal IP address of theistio-ingressgateway, and refresh the page,which displays successfully. When you access Online Boutique using theingress gateway, the request takes the following path:

    mutual mTLS

    mTLS authentication flow:

    1. The browser sends a plaintext HTTP request to the server.
    2. The ingress gateway proxy container intercepts the request.
    3. 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.
    4. The ingress gateway proxy performs a secure naming check on theserver's certificate, verifying that an authorized identity is runningthe server.
    5. The ingress gateway and server proxies establish a mutual TLSconnection, and the server proxy forwards the request to the serverapplication container (the frontend service).
  6. Run the following command tocurl thefrontend service 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 STRICTpeerAuthentication policy is applied.

Find and delete authentication policies

  1. For a list of all thePeerAuthentication policies in the service mesh:

    kubectl get peerauthentication --all-namespaces

    The 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   17m
  2. Delete 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" deleted
  3. Access the Online Boutique using the external IP address of thefrontend-external service, and refresh the page. The page displays asexpected.

  4. Run the following command tocurl thefrontend service 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 status200, 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.

  1. Apply an authentication policy to a specific workload. Notice how thefollowing policy uses labels and selectors to target the specificfrontenddeployment.

    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: STRICTEOF

    Expected output:

    peerauthentication.security.istio.io/frontend created
  2. 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_MUTUALEOF

    Expected output:

    destinationrule.networking.istio.io/frontend created
  3. Access the Online Boutique using the external IP address of thefrontend-external service, and refresh the page. The page doesn'tdisplay because because thefrontend service is set toSTRICT mTLS, and the sidecar proxy blocks the request.

  4. Run the following command tocurl thefrontend service 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 STRICTpeerAuthentication policy is applied.

  5. Delete the authentication policy:

    kubectl delete peerauthentication -n frontend frontend

    Expected output:

    peerauthentication.security.istio.io "frontend" deleted
  6. Delete the destination rule:

    kubectl delete destinationrule -n frontend frontend

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

  1. Enforce mesh-wide mTLS:

    kubectl apply -f - <<EOFapiVersion: "security.istio.io/v1beta1"kind: "PeerAuthentication"metadata:  name: "mesh-wide"  namespace: "istio-system"spec:  mtls:    mode: STRICTEOF

    Expected output:

    peerauthentication.security.istio.io/mesh-wide created

  2. Access the Online Boutique using the external IP address of thefrontend-external service, and refresh the page. The page doesn'tdisplay.

  3. Run the following command tocurl thefrontend service 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 STRICTpeerAuthentication policy is applied.

  4. Delete themesh-wide policy:

    kubectl delete peerauthentication -n istio-system mesh-wide

    Expected output:

    peerauthentication.security.istio.io "mesh-wide" deleted
Note: For more information on authorization policies, seeCloud Service Mesh by example: Authorization.

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_LOCATION
  • If you want to keep your cluster and remove the Online Boutique sample:

    1. Delete the application namespaces:
    kubectl delete -f online-boutique/kubernetes-manifests/namespaces

    Expected 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
    1. Delete the service entries:
    kubectl delete -f online-boutique/istio-manifests/allow-egress-googleapis.yaml

    Expected output:

    serviceentry.networking.istio.io "allow-egress-googleapis" deletedserviceentry.networking.istio.io "allow-egress-google-metadata" deleted

What's next

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.