Configure Istio service mesh security with mTLS authentication, JWT validation, and RBAC authorization policies. This tutorial covers implementing comprehensive security controls for microservices communication in production Kubernetes environments.
Prerequisites
- Kubernetes cluster with admin access
- Istio service mesh installed
- kubectl configured
- Basic understanding of Kubernetes networking
What this solves
Istio security policies provide essential authentication and authorization controls for microservices running in Kubernetes clusters. This tutorial demonstrates how to implement mutual TLS (mTLS) for secure service-to-service communication, configure JWT-based authentication for external access, and establish role-based access control (RBAC) policies to restrict service interactions based on identity and context.
Prerequisites
Before implementing Istio security policies, ensure you have a working Kubernetes cluster with Istio installed. You'll also need kubectl access and basic understanding of Kubernetes concepts like namespaces, services, and pods.
Step-by-step configuration
Verify Istio installation and create test namespace
First, verify your Istio installation is working and create a dedicated namespace for testing security policies.
kubectl get pods -n istio-system
kubectl create namespace production
kubectl label namespace production istio-injection=enabled
Deploy sample applications for testing
Deploy a simple application stack to demonstrate Istio security policies in action.
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: frontend
version: v1
template:
metadata:
labels:
app: frontend
version: v1
spec:
containers:
- name: frontend
image: nginx:1.24
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: production
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: production
spec:
replicas: 1
selector:
matchLabels:
app: backend
version: v1
template:
metadata:
labels:
app: backend
version: v1
spec:
containers:
- name: backend
image: httpd:2.4
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: production
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 80
kubectl apply -f frontend-app.yaml
Configure Istio mTLS authentication policies
Enable automatic mutual TLS for all services in the production namespace. This ensures all service-to-service communication is encrypted and authenticated.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: production
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: DestinationRule
metadata:
name: default
namespace: production
spec:
host: "*.production.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
kubectl apply -f mtls-policy.yaml
Create JWT authentication policy for external access
Configure JWT-based authentication for external traffic entering the mesh through the Istio gateway.
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: production
spec:
selector:
matchLabels:
app: frontend
jwtRules:
- issuer: "https://accounts.google.com"
jwksUri: "https://www.googleapis.com/oauth2/v3/certs"
audiences:
- "example.com"
- issuer: "https://auth.example.com"
jwks: |
{
"keys": [
{
"kty": "RSA",
"use": "sig",
"kid": "example-key-id",
"n": "example-modulus",
"e": "AQAB"
}
]
}
audiences:
- "api.example.com"
kubectl apply -f jwt-policy.yaml
Implement RBAC authorization policies
Create fine-grained authorization policies that control which services can communicate based on service identity, HTTP methods, and request paths.
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: frontend-authz
namespace: production
spec:
selector:
matchLabels:
app: frontend
rules:
- from:
- source:
principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
- to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/v1/*"]
when:
- key: request.auth.claims[sub]
values: ["user@example.com"]
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: backend-authz
namespace: production
spec:
selector:
matchLabels:
app: backend
rules:
- from:
- source:
principals: ["cluster.local/ns/production/sa/default"]
namespaces: ["production"]
- to:
- operation:
methods: ["GET", "POST"]
when:
- key: source.labels[app]
values: ["frontend"]
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all-default
namespace: production
spec: {}
kubectl apply -f rbac-policy.yaml
Configure service-specific authentication requirements
Create more granular authentication policies for specific services that require different security levels.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: backend-peer-auth
namespace: production
spec:
selector:
matchLabels:
app: backend
mtls:
mode: STRICT
portLevelMtls:
80:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: backend-request-auth
namespace: production
spec:
selector:
matchLabels:
app: backend
jwtRules:
- issuer: "https://internal-auth.example.com"
jwksUri: "https://internal-auth.example.com/.well-known/jwks.json"
audiences:
- "backend-api.example.com"
forwardOriginalToken: true
kubectl apply -f service-auth-policy.yaml
Create namespace-level security policies
Implement broader security controls at the namespace level to establish baseline security for all services.
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: namespace-policy
namespace: production
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: namespace-authz
namespace: production
spec:
rules:
- from:
- source:
namespaces: ["production", "istio-system"]
- to:
- operation:
methods: ["GET", "POST", "PUT", "DELETE"]
when:
- key: source.certificate_fingerprint
notValues: [""]
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: ingress-policy
namespace: production
spec:
selector:
matchLabels:
app: frontend
rules:
- from:
- source:
principals: ["cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"]
to:
- operation:
methods: ["GET", "POST"]
when:
- key: request.headers[authorization]
notValues: [""]
- key: source.ip
values: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
kubectl apply -f namespace-security.yaml
Configure Istio Gateway with security policies
Set up an Istio Gateway with integrated security policies for handling external traffic securely.
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: secure-gateway
namespace: production
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: example-com-tls
hosts:
- "api.example.com"
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "api.example.com"
tls:
httpsRedirect: true
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: secure-vs
namespace: production
spec:
hosts:
- "api.example.com"
gateways:
- secure-gateway
http:
- match:
- uri:
prefix: "/api/v1/"
headers:
request:
set:
x-forwarded-proto: "https"
route:
- destination:
host: frontend.production.svc.cluster.local
port:
number: 80
timeout: 30s
retries:
attempts: 3
perTryTimeout: 10s
kubectl apply -f secure-gateway.yaml
Monitor and troubleshoot Istio security policies
Enable security audit logging
Configure Istio to log security policy decisions for monitoring and troubleshooting purposes.
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
name: security-logging
namespace: istio-system
spec:
meshConfig:
accessLogFile: /dev/stdout
accessLogEncoding: JSON
extensionProviders:
- name: security-audit
configSource:
address: "audit-service.logging:8080"
values:
pilot:
env:
EXTERNAL_ISTIOD: false
PILOT_ENABLE_WORKLOAD_ENTRY_AUTOREGISTRATION: true
PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY: true
global:
meshConfig:
defaultConfig:
proxyStatsMatcher:
inclusionRegexps:
- "._cx_."
- "._rq_."
- "._rbac_."
kubectl apply -f security-logging.yaml
Set up monitoring dashboards
Configure Prometheus and Grafana to monitor Istio security metrics and policy enforcement.
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/prometheus.yaml
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.20/samples/addons/grafana.yaml
Verify your setup
Test your Istio security configuration to ensure policies are working correctly.
kubectl get peerauthentication -n production
kubectl get authorizationpolicy -n production
kubectl get requestauthentication -n production
Test mTLS connectivity
kubectl exec -n production deployment/frontend -c istio-proxy -- openssl s_client -connect backend.production:80 -cert /etc/ssl/certs/cert-chain.pem -key /etc/ssl/private/key.pem -CAfile /etc/ssl/certs/root-cert.pem
Check security policy status
istioctl authn tls-check frontend.production.svc.cluster.local
istioctl proxy-config cluster frontend-deployment-pod-name.production --fqdn backend.production.svc.cluster.local
Verify JWT authentication
curl -H "Authorization: Bearer valid-jwt-token" https://api.example.com/api/v1/health
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| RBAC access denied errors | Authorization policy too restrictive | Check policy rules with istioctl analyze and verify service identities |
| mTLS connection failures | Certificate or PeerAuthentication misconfiguration | Verify with istioctl authn tls-check and check Envoy config |
| JWT validation failures | Invalid JWT issuer or JWKS endpoint | Test JWT manually and verify RequestAuthentication config |
| Gateway SSL handshake errors | Missing or invalid TLS certificate | Check certificate with kubectl get secret example-com-tls -o yaml |
| Policy not taking effect | Selector labels don't match workloads | Verify pod labels match policy selectors with kubectl get pods --show-labels |
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
NAMESPACE="${1:-production}"
JWKS_ISSUER="${2:-https://auth.example.com}"
JWKS_AUDIENCE="${3:-api.example.com}"
# Usage message
usage() {
echo "Usage: $0 [namespace] [jwks-issuer] [jwks-audience]"
echo "Example: $0 production https://auth.example.com api.example.com"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function
cleanup() {
if [[ $? -ne 0 ]]; then
log_error "Installation failed. Cleaning up..."
kubectl delete namespace "$NAMESPACE" --ignore-not-found=true 2>/dev/null || true
rm -f /tmp/istio-security-*.yaml
fi
}
trap cleanup ERR
# Check prerequisites
check_prerequisites() {
log_info "[1/8] Checking prerequisites..."
if [[ $EUID -ne 0 ]] && ! groups | grep -q sudo; then
log_error "This script requires root privileges or sudo access"
exit 1
fi
# Check for kubectl
if ! command -v kubectl &> /dev/null; then
log_error "kubectl is required but not installed"
exit 1
fi
# Test kubectl connectivity
if ! kubectl cluster-info &> /dev/null; then
log_error "Cannot connect to Kubernetes cluster"
exit 1
fi
# Verify Istio installation
if ! kubectl get pods -n istio-system &> /dev/null; then
log_error "Istio system namespace not found. Install Istio first."
exit 1
fi
local istio_pods
istio_pods=$(kubectl get pods -n istio-system --no-headers | grep -E "(istiod|pilot)" | grep Running | wc -l)
if [[ $istio_pods -eq 0 ]]; then
log_error "Istio control plane not running"
exit 1
fi
}
# Auto-detect package manager
detect_package_manager() {
log_info "[2/8] Detecting operating system..."
if [[ ! -f /etc/os-release ]]; then
log_error "Cannot detect OS. /etc/os-release not found."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt-get install -y"
PKG_UPDATE="apt-get update"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf check-update || true"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum check-update || true"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_info "Detected OS: $PRETTY_NAME"
log_info "Package manager: $PKG_MGR"
}
# Install required packages
install_packages() {
log_info "[3/8] Installing required packages..."
if [[ $EUID -eq 0 ]]; then
$PKG_UPDATE
$PKG_INSTALL curl wget jq
else
sudo $PKG_UPDATE
sudo $PKG_INSTALL curl wget jq
fi
}
# Create and configure namespace
setup_namespace() {
log_info "[4/8] Setting up namespace: $NAMESPACE"
# Delete namespace if exists to start fresh
kubectl delete namespace "$NAMESPACE" --ignore-not-found=true
kubectl create namespace "$NAMESPACE"
kubectl label namespace "$NAMESPACE" istio-injection=enabled
# Wait for namespace to be ready
kubectl wait --for=condition=Ready --timeout=60s namespace/"$NAMESPACE"
}
# Deploy sample applications
deploy_applications() {
log_info "[5/8] Deploying sample applications..."
cat > /tmp/istio-security-apps.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: $NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: frontend
version: v1
template:
metadata:
labels:
app: frontend
version: v1
spec:
containers:
- name: frontend
image: nginx:1.24-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: $NAMESPACE
spec:
selector:
app: frontend
ports:
- port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
namespace: $NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: backend
version: v1
template:
metadata:
labels:
app: backend
version: v1
spec:
containers:
- name: backend
image: httpd:2.4-alpine
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
---
apiVersion: v1
kind: Service
metadata:
name: backend
namespace: $NAMESPACE
spec:
selector:
app: backend
ports:
- port: 80
targetPort: 80
EOF
kubectl apply -f /tmp/istio-security-apps.yaml
kubectl wait --for=condition=Available --timeout=300s deployment/frontend -n "$NAMESPACE"
kubectl wait --for=condition=Available --timeout=300s deployment/backend -n "$NAMESPACE"
}
# Configure mTLS policies
configure_mtls() {
log_info "[6/8] Configuring mutual TLS policies..."
cat > /tmp/istio-security-mtls.yaml << EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: $NAMESPACE
spec:
mtls:
mode: STRICT
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: default
namespace: $NAMESPACE
spec:
host: "*.$NAMESPACE.svc.cluster.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
kubectl apply -f /tmp/istio-security-mtls.yaml
}
# Configure JWT authentication
configure_jwt() {
log_info "[7/8] Configuring JWT authentication..."
cat > /tmp/istio-security-jwt.yaml << EOF
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
name: jwt-auth
namespace: $NAMESPACE
spec:
selector:
matchLabels:
app: frontend
jwtRules:
- issuer: "$JWKS_ISSUER"
audiences:
- "$JWKS_AUDIENCE"
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: frontend-authz
namespace: $NAMESPACE
spec:
selector:
matchLabels:
app: frontend
rules:
- from:
- source:
principals: ["cluster.local/ns/$NAMESPACE/sa/default"]
- when:
- key: request.auth.claims[iss]
values: ["$JWKS_ISSUER"]
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: backend-authz
namespace: $NAMESPACE
spec:
selector:
matchLabels:
app: backend
rules:
- from:
- source:
principals: ["cluster.local/ns/$NAMESPACE/sa/default"]
to:
- operation:
methods: ["GET", "POST"]
EOF
kubectl apply -f /tmp/istio-security-jwt.yaml
}
# Verify installation
verify_installation() {
log_info "[8/8] Verifying Istio security policies..."
# Check if policies are applied
local peer_auth_count
peer_auth_count=$(kubectl get peerauthentication -n "$NAMESPACE" --no-headers | wc -l)
if [[ $peer_auth_count -eq 0 ]]; then
log_error "PeerAuthentication policies not found"
return 1
fi
local dest_rules_count
dest_rules_count=$(kubectl get destinationrule -n "$NAMESPACE" --no-headers | wc -l)
if [[ $dest_rules_count -eq 0 ]]; then
log_error "DestinationRule policies not found"
return 1
fi
local authz_count
authz_count=$(kubectl get authorizationpolicy -n "$NAMESPACE" --no-headers | wc -l)
if [[ $authz_count -lt 2 ]]; then
log_error "Authorization policies not properly configured"
return 1
fi
# Check pod status
local ready_pods
ready_pods=$(kubectl get pods -n "$NAMESPACE" --no-headers | grep Running | wc -l)
if [[ $ready_pods -lt 2 ]]; then
log_error "Not all pods are running"
return 1
fi
log_info "Verification completed successfully!"
log_info "Namespace: $NAMESPACE"
log_info "Applications deployed: frontend, backend"
log_info "Security policies: mTLS (STRICT), JWT authentication, RBAC authorization"
# Show status
echo ""
log_info "Current status:"
kubectl get pods,svc,peerauthentication,destinationrule,authorizationpolicy -n "$NAMESPACE"
}
# Main execution
main() {
if [[ $# -gt 3 ]]; then
usage
fi
log_info "Starting Istio security policies installation..."
log_info "Target namespace: $NAMESPACE"
log_info "JWKS issuer: $JWKS_ISSUER"
log_info "JWKS audience: $JWKS_AUDIENCE"
check_prerequisites
detect_package_manager
install_packages
setup_namespace
deploy_applications
configure_mtls
configure_jwt
verify_installation
# Cleanup temporary files
rm -f /tmp/istio-security-*.yaml
log_info "Istio security policies installation completed successfully!"
}
main "$@"
Review the script before running. Execute with: bash install.sh