mTLS Pod-to-Pod Communication with Istio¶
Introduction¶
Mutual TLS (mTLS) is one of the most powerful security features offered by Istio service mesh. Unlike regular TLS where only the server authenticates itself to the client, in mutual TLS both parties verify each other’s identity.
Understanding Istio mTLS¶
How mTLS Works in Istio¶
Istio implements mTLS through its sidecar proxies (Envoy). When a service with an Istio sidecar communicates with another service in the mesh, the following happens:
- The client-side sidecar proxy initiates a connection to the server
- The client-side proxy performs a TLS handshake with the server-side proxy, exchanging certificates
- The client-side proxy verifies the server’s identity
- The client and server establish a mutual TLS connection
- The client proxy forwards the request to the server through the encrypted channel
- The server-side proxy forwards the request to the application container
This entire process happens transparently to your application.
mTLS Modes in Istio¶
Istio offers three modes for mTLS:
- PERMISSIVE: Accepts both plaintext and mTLS traffic (default)
- STRICT: Only accepts mTLS traffic
- DISABLED: Only accepts plaintext traffic
Implementing mTLS Between Pods¶
Enable Strict mTLS¶
Enable strict mTLS for the namespaces using PeerAuthentication:
# save this as peer-authentication.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: foo
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: bar
spec:
mtls:
mode: STRICT
Apply the configuration:
kubectl apply -f peer-authentication.yaml
Configure Destination Rules¶
Destination rules define how traffic is routed to a service after virtual service routing rules are evaluated. When you think about Istio’s traffic management, it’s important to understand the sequence:
- First, traffic is routed (often using VirtualService)
- Then, DestinationRule policies are applied to that routed traffic
Create destination rules to enforce mTLS for outbound traffic:
# save this as destination-rule.yaml
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: httpbin-foo
namespace: foo
spec:
host: "httpbin.foo.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: httpbin-bar
namespace: bar
spec:
host: "httpbin.bar.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
Apply the configuration:
kubectl apply -f destination-rule.yaml
Handling Non-Mesh Services¶
For services outside the mesh or services that don’t support mTLS, you can configure exceptions to the mTLS policy.
Workload-Specific mTLS Policies¶
Create workload-specific PeerAuthentication policies to override namespace or mesh-wide policies:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: workload-policy
namespace: foo
spec:
selector:
matchLabels:
app: httpbin
mtls:
mode: PERMISSIVE
Configure Destination Rules for External Services¶
For external services, set up a destination rule to disable mTLS:
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: external-service
spec:
host: external-service.example.com
trafficPolicy:
tls:
mode: DISABLE # or SIMPLE for regular TLS
Istio Sidecar Injection¶
Istio sidecar injection is the process of automatically adding an Envoy proxy container (the “sidecar”) to Kubernetes pods. This sidecar proxy is what enables Istio’s service mesh functionality, as it intercepts and manages all network traffic going in and out of your application pods.
When you deploy applications in a Kubernetes cluster with Istio, each pod gets its own sidecar proxy that handles all the networking concerns like traffic routing, load balancing, circuit breaking, and implementing security policies.
How Sidecar Injection Works¶
Istio uses a Kubernetes feature called “mutating webhook admission controller” to modify pod specifications at creation time. When the webhook is enabled and a namespace is properly labeled, any new pods created in that namespace will automatically have the Istio sidecar proxy container added to them.
The injection can happen in two ways:
- Automatic injection: Configured at the namespace level
- Manual injection: Applied directly to deployments using the
istioctl
command
How to Enable Sidecar Injection¶
Method 1: Automatic Injection (Namespace-Level)¶
This is the most common approach:
# Label the namespace to enable automatic injection
kubectl label namespace <your-namespace> istio-injection=enabled
Once you apply this label, all new workloads deployed in the namespace will automatically have the Istio sidecar injected. Note that existing workloads won’t be affected—you’ll need to redeploy them to get the sidecar.
You can verify the label is applied with:
kubectl get namespace <your-namespace> --show-labels
Method 2: Manual Injection (Deployment-Level)¶
If you prefer to inject sidecars on specific deployments:
# Manually inject the sidecar into a deployment
istioctl kube-inject -f your-deployment.yaml | kubectl apply -f -
This approach modifies the deployment YAML directly, adding the sidecar configuration before applying it to the cluster.
How to Verify Injection Worked¶
You can verify that a pod has the sidecar injected by checking the READY column:
kubectl get pods
A pod with the sidecar injected will show 2/2
in the READY column, indicating that there are two containers running (your application and the Istio proxy).
You can also describe the pod to see the istio-proxy
container:
kubectl describe pod <pod-name>
Or check for the presence of the sidecar container directly:
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].name}'
Disabling Sidecar Injection¶
If you need to exclude a specific workload from injection, you can add an annotation to that workload:
metadata:
annotations:
sidecar.istio.io/inject: "false"
Alternatively, the newer approach uses a label instead of an annotation:
metadata:
labels:
sidecar.istio.io/inject: "false"
This is particularly useful for jobs, CronJobs, or any workload that shouldn’t be part of the service mesh.