Service Mesh — Istio & Linkerd›04 · mTLS & identity

Lesson 04 of 7 · Modules

mTLS & identity

How meshes identify workloads and encrypt traffic: SPIFFE IDs derived from service accounts, short-lived certificates and rotation, rolling out mTLS from permissive to strict without breaking callers, and writing identity-based authorization policies.

Advanced
Key wordsmTLSworkload identitySPIFFESPIFFE IDSVIDservice accountsPERMISSIVE vs STRICTAuthorizationPolicycertificate rotationexternal CAincremental rollout

Identity first

Encryption alone isn't the main win; knowing who is calling is. Meshes issue each workload a certificate encoding its identity, following the SPIFFE standard:

spiffe://<trust-domain>/ns/<namespace>/sa/<service-account>
spiffe://cluster.local/ns/shop/sa/cart

The certificate (an X.509 SVID) is short-lived (Istio's default is 24 hours) and rotated automatically by the proxy.

Every pupil gets a photo ID card from the school office, renewed every day so a lost card is useless tomorrow. When two pupils pass notes, they show each other their cards first (mutual TLS), and the teacher's rule says "only pupils from class 3 may hand notes to the class 5 monitor" (authorization policy), based on the card, not on where someone is sitting (IP).

Rolling out mTLS safely (Istio)

  1. Mesh the workloads (sidecars or ambient); default mode is PERMISSIVE: accepts mTLS and plaintext.
  2. Check which traffic is still plaintext (Kiali, or metrics' connection_security_policy label).
  3. Switch namespace by namespace to STRICT:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: shop
spec:
  mtls:
    mode: STRICT
  1. Finally, a mesh-wide STRICT default in the root namespace (istio-system), with explicit exceptions (e.g. a port used by a non-mesh health checker).

Linkerd mTLSes meshed-to-meshed traffic automatically; its policy resources can require mTLS/identities per server port.

Authorization by identity

Default-deny for a namespace, then allow specific callers:

apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: allow-nothing
  namespace: orders
spec: {}                      # empty spec = deny all requests to this namespace's workloads
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: orders-api
  namespace: orders
spec:
  selector:
    matchLabels: { app: orders-api }
  action: ALLOW
  rules:
    - from:
        - source:
            principals: [ "cluster.local/ns/shop/sa/checkout" ]
      to:
        - operation:
            methods: [ "POST" ]
            paths: [ "/v1/orders" ]
  • L7 conditions (methods, paths) need an L7 proxy (sidecar, or a waypoint in ambient mode).
  • Keep NetworkPolicies too: defence in depth at L3/L4, and protection if the mesh is bypassed.

Certificate authority choices

  • Built-in CA (self-signed root generated at install): fine for labs; for production, plug in your own root/intermediate.
  • External CA integration: an intermediate from your corporate PKI, Vault, or cert-manager (e.g. istio-csr) so mesh identities chain to a root you control and can rotate.
  • Multi-cluster: clusters must share a root of trust (common root, different intermediates) to authenticate each other.

Try it: mTLS and identity policies (Istio on kind)

  1. Deploy sleep (curl) pods in a meshed and an un-meshed namespace, and httpbin in a meshed namespace.
  2. With PERMISSIVE, confirm both callers reach httpbin; switch httpbin's namespace to STRICT and confirm the un-meshed caller fails.
  3. Inspect the certificate chain a proxy presents (e.g. istioctl proxy-config secret deploy/httpbin -o json) and find the SPIFFE ID.
  4. Add the allow-nothing policy and a rule allowing only the meshed sleep service account to GET /headers; test allowed and denied calls.
  5. Give two deployments the same service account and explain why the policy can no longer tell them apart.

Going deeper: identity at scale

  • Enforce one service account per workload with a policy engine; default service accounts should have no mesh permissions.
  • Plan root/intermediate rotation before go-live; test it in staging.
  • Use SPIRE (the SPIFFE reference implementation) when identities must span meshes, VMs and non-Kubernetes workloads.

Recap

  • Mesh identity = SPIFFE ID from namespace + service account, in short-lived, auto-rotated certificates.
  • Roll out mTLS PERMISSIVE → STRICT, namespace by namespace, then mesh-wide.
  • AuthorizationPolicies allow callers by verified identity (and L7 attributes); keep NetworkPolicies as defence in depth.
  • Use your own CA/intermediate in production; share a root of trust across clusters.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.