---
title: "Istio Security: mTLS, Authorization Policies, and Egress Control"
description: "How to use Istio's security features for mutual TLS, fine-grained authorization, JWT validation, and egress traffic control in Kubernetes."
url: https://agent-zone.ai/knowledge/security/istio-security/
section: knowledge
date: 2026-02-22
categories: ["security"]
tags: ["istio","mtls","service-mesh","authorization","jwt","egress"]
skills: ["istio-security-configuration","mtls-enforcement","authorization-policy-design","egress-control"]
tools: ["kubectl","istioctl","istio"]
levels: ["intermediate"]
word_count: 789
formats:
  json: https://agent-zone.ai/knowledge/security/istio-security/index.json
  html: https://agent-zone.ai/knowledge/security/istio-security/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Istio+Security%3A+mTLS%2C+Authorization+Policies%2C+and+Egress+Control
---


# Istio Security

Istio provides three security capabilities that are difficult to implement without a service mesh: automatic mutual TLS between services, fine-grained authorization policies, and egress traffic control. These features work at the infrastructure layer, meaning applications do not need any code changes.

## Automatic mTLS with PeerAuthentication

Istio's sidecar proxies can automatically encrypt all pod-to-pod traffic with mutual TLS. The key resource is PeerAuthentication. There are three modes:

- **PERMISSIVE** -- Accepts both plaintext and mTLS traffic. This is the default and exists for migration. Do not leave it in production.
- **STRICT** -- Requires mTLS for all traffic. Plaintext connections are rejected.
- **DISABLE** -- Turns off mTLS entirely.

Enable strict mTLS across the entire mesh:

```yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: default
  namespace: istio-system
spec:
  mtls:
    mode: STRICT
```

Placing this in `istio-system` applies it mesh-wide. You can override per-namespace or per-workload:

```yaml
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
  name: db-strict
  namespace: database
spec:
  selector:
    matchLabels:
      app: postgres
  mtls:
    mode: STRICT
  portLevelMtls:
    5432:
      mode: STRICT
```

**Common mistake:** Leaving the mesh-wide PeerAuthentication in PERMISSIVE mode after the initial rollout. This means any service can communicate in plaintext, defeating the purpose of mTLS. Always switch to STRICT once all services have sidecars injected.

Verify mTLS is active:

```bash
istioctl x describe pod <pod-name> -n <namespace>
```

## Authorization Policies

AuthorizationPolicy controls which services can talk to which services, on which paths, using which methods. Without any policy, all traffic is allowed (in the absence of a mesh-wide deny policy).

### Deny-by-Default

Apply a deny-all policy per namespace, then add allow rules:

```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: production
spec:
  {}
```

An empty spec with no rules denies all traffic to workloads in the namespace.

### Allow Specific Traffic

Allow the `frontend` service to call the `api` service on GET and POST for specific paths:

```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: allow-frontend-to-api
  namespace: production
spec:
  selector:
    matchLabels:
      app: api
  action: ALLOW
  rules:
    - from:
        - source:
            principals: ["cluster.local/ns/production/sa/frontend"]
      to:
        - operation:
            methods: ["GET", "POST"]
            paths: ["/api/v1/*", "/healthz"]
```

The `principals` field uses the SPIFFE identity that Istio assigns based on the Kubernetes service account. This ties authorization to workload identity, not network location.

### Deny Rules

Block a specific source from accessing sensitive endpoints:

```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: deny-external-to-admin
  namespace: production
spec:
  selector:
    matchLabels:
      app: api
  action: DENY
  rules:
    - from:
        - source:
            notNamespaces: ["production"]
      to:
        - operation:
            paths: ["/admin/*"]
```

**Policy ordering matters:** DENY policies are evaluated before ALLOW policies. If a request matches any DENY rule, it is rejected regardless of ALLOW rules. Design your policies with this in mind.

## RequestAuthentication with JWT

Istio can validate JWT tokens at the proxy layer before they reach your application:

```yaml
apiVersion: security.istio.io/v1
kind: RequestAuthentication
metadata:
  name: jwt-auth
  namespace: production
spec:
  selector:
    matchLabels:
      app: api
  jwtRules:
    - issuer: "https://auth.example.com"
      jwksUri: "https://auth.example.com/.well-known/jwks.json"
      forwardOriginalToken: true
      outputPayloadToHeader: "x-jwt-payload"
```

RequestAuthentication only validates tokens that are present -- it does not require them. To require a valid JWT, pair it with an AuthorizationPolicy:

```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: require-jwt
  namespace: production
spec:
  selector:
    matchLabels:
      app: api
  action: ALLOW
  rules:
    - from:
        - source:
            requestPrincipals: ["https://auth.example.com/*"]
```

Requests without a valid JWT will have no `requestPrincipal` set and will be denied by this policy.

## Istio Ingress Gateway Security

The Istio ingress gateway replaces traditional ingress controllers. Secure it with TLS:

```yaml
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
  name: app-gateway
  namespace: production
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        credentialName: app-tls-cert
      hosts:
        - "app.example.com"
    - port:
        number: 80
        name: http
        protocol: HTTP
      tls:
        httpsRedirect: true
      hosts:
        - "app.example.com"
```

The HTTP server block redirects all plaintext traffic to HTTPS. The `credentialName` references a Kubernetes secret in the `istio-system` namespace containing the TLS certificate and key.

## Egress Control

By default, Istio allows all outbound traffic. Lock this down with `meshConfig.outboundTrafficPolicy.mode: REGISTRY_ONLY`, then explicitly allow external services:

```yaml
apiVersion: networking.istio.io/v1
kind: ServiceEntry
metadata:
  name: allow-external-api
  namespace: production
spec:
  hosts:
    - "api.stripe.com"
  ports:
    - number: 443
      name: https
      protocol: TLS
  resolution: DNS
  location: MESH_EXTERNAL
```

Combine with AuthorizationPolicy to restrict which workloads can reach external services:

```yaml
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: restrict-egress-stripe
  namespace: production
spec:
  selector:
    matchLabels:
      app: payment-service
  action: ALLOW
  rules:
    - to:
        - operation:
            hosts: ["api.stripe.com"]
            ports: ["443"]
```

## Audit Logging

Enable access logging in the mesh to capture all traffic decisions:

```bash
istioctl install --set meshConfig.accessLogFile=/dev/stdout
```

For production, send logs to a centralized system. Istio's Envoy access logs include source and destination identity, authorization decision, and response code -- everything needed to investigate security incidents.

## Verification Checklist

1. Run `istioctl analyze -A` to detect misconfigurations across the mesh.
2. Verify mTLS status with `istioctl x describe pod`.
3. Test authorization policies by sending requests from different service accounts and verifying the expected 403 responses.
4. Confirm egress restrictions by attempting outbound connections from pods to hosts not in ServiceEntry resources.
5. Check that PERMISSIVE mode is not present in any production PeerAuthentication resource.

