# Istio ambient mode: ztunnel, waypoint proxies and authorization without sidecars

> How Istio ambient mode secures traffic with no sidecar: a namespace label enrolls Pods, ztunnel carries mTLS between nodes, a waypoint Gateway adds HTTP rules, and PeerAuthentication and AuthorizationPolicy decide who may call what.

Interactive graph: https://k8wiz.com/graphs/istio-ambient-ztunnel-waypoint-authorization

Ambient mode splits what a sidecar did in two. A per-node proxy, ztunnel, gives every enrolled Pod an identity and mutual TLS at layer 4. HTTP features such as routing by path or authorizing by method run in a waypoint proxy, which you deploy only for the namespaces or services that need it.

Pods join by a label on their namespace, with no restart and no extra container. This graph shows one call from `web` to `api` through both layers.

## Resources

### Namespace shop

you write it, v1

`istio.io/dataplane-mode: ambient` enrolls every Pod in the namespace. `istio.io/use-waypoint` sends traffic for its Services through the named waypoint.

### DaemonSet ztunnel

comes with an add-on, apps/v1, namespace istio-system

One per node. It takes over the traffic of enrolled Pods on its node, wraps it in mutual TLS over port 15008 (HBONE), and presents each Pod's own SPIFFE identity, taken from its ServiceAccount.

### Pod web-5b7c9-…

created by a controller, v1, namespace shop

The caller. It has one container and shows 1/1. Its plain HTTP request to `api` is picked up by the ztunnel on its node.

### ServiceAccount web

you write it, v1, namespace shop

The caller's identity in the mesh: `cluster.local/ns/shop/sa/web`. Authorization rules name this principal, so each workload needs its own ServiceAccount.

### Gateway waypoint

you write it, gateway.networking.k8s.io/v1, namespace shop

A Gateway of class `istio-waypoint`. Istio turns it into an Envoy Deployment that handles layer 7 for the namespace's Services. `istioctl waypoint apply -n shop --enroll-namespace` creates it and labels the namespace.

### Deployment waypoint

created by a controller, apps/v1, namespace shop

The Envoy Pods istiod creates for the Gateway. They scale like any Deployment, apart from the applications they serve.

### PeerAuthentication default

you write it, security.istio.io/v1, namespace shop

`STRICT` makes ztunnel refuse plain text to the namespace's Pods. A caller outside the mesh gets a reset connection.

### AuthorizationPolicy api-read-only

you write it, security.istio.io/v1, namespace shop

An HTTP rule, so it attaches to the Service with `targetRefs` and is enforced by the waypoint: `web` may `GET /orders/*` on `api` and nothing else.

### AuthorizationPolicy api-from-waypoint-only

you write it, security.istio.io/v1, namespace shop

A layer 4 rule with a workload `selector`, enforced by ztunnel at the destination: only the waypoint's identity may connect to the `api` Pods. Without it a caller could skip the waypoint by using a Pod IP.

### Service api

you write it, v1, namespace shop

What `web` calls. Because the namespace uses a waypoint, ztunnel sends traffic for this Service to the waypoint first.

### Pod ×2 api-7f6d4-…

created by a controller, v1, namespace shop

The destination. The ztunnel on its node ends the mutual TLS, checks the layer 4 policy and hands plain HTTP to the container.

## How the resources connect

- Namespace shop → Pod web-5b7c9-…: enrolls (selects)
- Namespace shop → Gateway waypoint: use-waypoint (references)
- Pod web-5b7c9-… → ServiceAccount web: serviceAccountName (references)
- Pod web-5b7c9-… → DaemonSet ztunnel: HTTP, captured (traffic)
- DaemonSet ztunnel → Deployment waypoint: HBONE mTLS (traffic)
- Gateway waypoint → Deployment waypoint: istiod creates (creates)
- Deployment waypoint → Service api: routes (traffic)
- Service api → Pod ×2 api-7f6d4-…: selector (selects)
- AuthorizationPolicy api-read-only → Service api: targetRefs (references)
- AuthorizationPolicy api-from-waypoint-only → Pod ×2 api-7f6d4-…: selector (selects)
- PeerAuthentication default → Pod ×2 api-7f6d4-…: STRICT (selects)

## How it works

1. **A label enrolls the namespace.** With `istio.io/dataplane-mode: ambient` on the namespace, the CNI plugin redirects each Pod's traffic to the ztunnel on its node. Running Pods are enrolled as they are, with no restart.
2. **ztunnel gives the call an identity.** `web` sends plain HTTP to `api:8080`. ztunnel opens a mutual TLS tunnel on port 15008 and presents a certificate for `web`'s ServiceAccount, which istiod issued to it for that Pod.
3. **The waypoint handles HTTP.** The namespace names a waypoint, so the tunnel goes there first. The waypoint reads the request, applies the `api-read-only` policy and any HTTPRoute attached to the Service, then opens its own tunnel to a destination Pod.
4. **The destination ztunnel checks layer 4.** On the `api` Pod's node, ztunnel ends the tunnel. `STRICT` means it would refuse plain text, and `api-from-waypoint-only` means it accepts the waypoint's identity alone.
5. **What gets denied.** `POST /orders` from `web` gets a 403 from the waypoint. A Pod in another namespace that connects straight to an `api` Pod IP is reset by ztunnel. `istioctl ztunnel-config workloads` shows which Pods are enrolled and which waypoint they use.

## Common mistakes

- ztunnel only understands layer 4. An AuthorizationPolicy with a `selector` and HTTP fields such as `methods` or `paths` is not enforced as written: attach HTTP rules to the Service or the waypoint with `targetRefs`.
- Deploying a waypoint does nothing until something uses it. The namespace or the Service needs the `istio.io/use-waypoint` label.
- Identity comes from the ServiceAccount. Workloads that share `default` share one principal, and no policy can tell them apart.
- A waypoint policy alone can be bypassed by calling a Pod IP. Add the layer 4 policy that admits only the waypoint's principal.
- Pods that use `hostNetwork` are not captured, and a namespace with both sidecars and the ambient label should pick one mode per Pod.

## The manifests you write

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: shop
  labels:
    istio.io/dataplane-mode: ambient
    istio.io/use-waypoint: waypoint
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: web
  namespace: shop
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: waypoint
  namespace: shop
  labels:
    istio.io/waypoint-for: service
spec:
  gatewayClassName: istio-waypoint
  listeners:
    - name: mesh
      port: 15008
      protocol: HBONE
---
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: shop
spec:
  mtls:
    mode: STRICT
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: api-read-only
  namespace: shop
spec:
  targetRefs:
    - kind: Service
      group: ""
      name: api
  action: ALLOW
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/shop/sa/web"]
      to:
        - operation:
            methods: ["GET"]
            paths: ["/orders/*"]
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: api-from-waypoint-only
  namespace: shop
spec:
  selector:
    matchLabels:
      app: api
  action: ALLOW
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/shop/sa/waypoint"]
---
apiVersion: v1
kind: Service
metadata:
  name: api
  namespace: shop
spec:
  selector:
    app: api
  ports:
    - name: http
      port: 8080
```
