Cilium Service Mesh Security
Supported Versions: Cilium 1.16+, Kubernetes 1.28+ Last Updated: August 21, 2026
Overview
Cilium security has three distinct layers:
- Identity-based authorization: Cilium Identity and eBPF policy decide which workloads may communicate.
- Mutual authentication: Cilium mutual authentication with SPIFFE/SPIRE verifies peer identity through an out-of-band handshake separate from the application data connection.
- Data encryption: with the established implementation, WireGuard/IPsec must be enabled separately to encrypt payloads. Where supported, the native ztunnel mTLS preview encrypts workload traffic with TLS.
These capabilities can be combined, but they are not automatically equivalent to Istio PeerAuthentication STRICT workload mTLS. Evaluate identity authorization, peer authentication, and encryption in transit as separate requirements.
Security Architecture
Mutual Authentication and Data Encryption
Established Cilium mutual authentication
Cilium mutual authentication verifies both endpoint identities before a connection is allowed, but the established authentication handshake is separate from the application data path. Do not assume that authentication.mode: required alone TLS-encrypts the payload of the existing data connection. Configure WireGuard or IPsec when data confidentiality is required.
Native mTLS via ztunnel (2026 Update)
The Cilium native mTLS design announced in March 2026 uses a ztunnel model to combine mutual authentication with actual payload encryption on a workload-mTLS path. It is a different data plane from established out-of-band mutual authentication plus WireGuard/IPsec. The stack has three cooperating components:
- SPIRE — issues workload identity and X.509 certificates (same role as in the SPIRE-based configuration below)
- Cilium — installs iptables rules that transparently redirect outbound pod traffic to ztunnel on port 15001
- ztunnel — a per-node proxy (not a per-pod sidecar) that performs the actual mTLS handshake and encrypts pod-to-pod traffic
This retains the "no per-pod sidecar, no application changes" property, while the TLS handshake runs in a dedicated per-node process. Check the current preview status and platform support before adoption; do not treat it as an automatic replacement for the operationally mature Istio STRICT mTLS path.
See the Cilium blog post on native mTLS for the full architecture writeup.
When to choose Cilium vs. Istio for mTLS
- Choose Cilium when the requirement is efficient L3/L4 identity policy and network encryption on a data plane that already runs Cilium — no additional sidecar or per-service proxy to operate, and CiliumNetworkPolicy/CiliumClusterwideNetworkPolicy already express the access rules you need.
- Choose Istio when the requirement is mature workload-certificate mTLS with
PeerAuthenticationSTRICTsemantics, or Istio-native L7 policy/routing (the kindAuthorizationPolicy, retry, and traffic-shifting rules covered in the sidecar vs. ambient comparison) — Cilium's established mutual authentication is out-of-band and does not carry that policy surface. - Do not decide based on the encryption layer alone: Cilium's WireGuard/IPsec and its native ztunnel mTLS preview both encrypt payloads, but neither one alone reproduces Istio
PeerAuthenticationSTRICT's combination of workload identity issuance, policy enforcement, and payload encryption in one switch.
SPIRE-based Mutual Authentication Configuration
# values.yaml - SPIRE integration configuration
authentication:
mutual:
spire:
enabled: true
install:
enabled: true
namespace: cilium-spire
server:
# SPIRE Server configuration
replicas: 1
dataStorage:
enabled: true
size: 1Gi
storageClass: gp3
# Trust Domain configuration
trustDomain: cluster.local
# CA configuration
ca:
# Use internal CA
keyType: ec-p256
ttl: 24h
# Node Attestor configuration
nodeAttestor:
k8sPsat:
enabled: true
agent:
# SPIRE Agent configuration
socketPath: /run/spire/sockets/agent.sock
# Workload Attestor configuration
workloadAttestor:
k8s:
enabled: true
disableContainerSelectors: falseMutual Authentication Policy Enforcement
# Require mutual authentication cluster-wide
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: enforce-mtls
spec:
endpointSelector: {}
authentication:
- mode: requiredPer-Namespace Mutual Authentication
# Apply mutual authentication to a specific namespace
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: namespace-mtls
namespace: production
spec:
endpointSelector: {}
ingress:
- fromEndpoints:
- {}
authentication:
- mode: required
egress:
- toEndpoints:
- {}
authentication:
- mode: requiredPer-Service Mutual Authentication
# Enforce mutual authentication between specific services
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: service-mtls
namespace: default
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
authentication:
- mode: required
toPorts:
- ports:
- port: "8080"
protocol: TCPCiliumNetworkPolicy L7 Rules
HTTP L7 Security Policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: http-security-policy
namespace: default
spec:
endpointSelector:
matchLabels:
app: api-server
ingress:
# Read-only access
- fromEndpoints:
- matchLabels:
role: reader
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET
path: "/api/.*"
# Admin access
- fromEndpoints:
- matchLabels:
role: admin
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: ".*"
path: "/api/.*"
headers:
- "Authorization: Bearer .*"
# Health checks
- fromEndpoints:
- matchLabels:
app: monitoring
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: GET
path: "/health"
- method: GET
path: "/metrics"Kafka L7 Security Policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: kafka-security
namespace: kafka
spec:
endpointSelector:
matchLabels:
app: kafka
ingress:
# Producer - allow writing to specific topics only
- fromEndpoints:
- matchLabels:
role: producer
toPorts:
- ports:
- port: "9092"
protocol: TCP
rules:
kafka:
- apiKey: produce
topic: "orders"
- apiKey: produce
topic: "events"
- apiKey: metadata
# Consumer - allow reading from specific topics only
- fromEndpoints:
- matchLabels:
role: consumer
toPorts:
- ports:
- port: "9092"
protocol: TCP
rules:
kafka:
- apiKey: fetch
topic: "orders"
- apiKey: fetch
topic: "events"
- apiKey: listoffsets
topic: "orders"
- apiKey: listoffsets
topic: "events"
- apiKey: metadata
- apiKey: findcoordinator
- apiKey: joingroup
- apiKey: heartbeat
- apiKey: leavegroup
- apiKey: syncgroup
- apiKey: offsetcommit
topic: "orders"
- apiKey: offsetfetch
topic: "orders"DNS L7 Security Policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: dns-security
namespace: default
spec:
endpointSelector:
matchLabels:
app: web-application
egress:
# Restrict DNS queries
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
rules:
dns:
# Allow internal services only
- matchPattern: "*.svc.cluster.local"
# Allow specific external domains only
- matchName: "api.stripe.com"
- matchName: "api.aws.amazon.com"
- matchPattern: "*.s3.amazonaws.com"
# Egress to allowed external services
- toFQDNs:
- matchName: "api.stripe.com"
- matchName: "api.aws.amazon.com"
- matchPattern: "*.s3.amazonaws.com"
toPorts:
- ports:
- port: "443"
protocol: TCPMutual Authentication
This section configures the
authentication.modepolicy examples. For what mutual authentication does and does not cover (out-of-band handshake, separate from payload encryption), see Mutual Authentication and Data Encryption above.
Authentication Modes
# Cilium authentication mode options
# 1. disabled - no authentication (default)
authentication:
- mode: disabled
# 2. optional - use authentication if possible, otherwise allow
authentication:
- mode: optional
# 3. required - authentication required
authentication:
- mode: required
# 4. test-always-fail - for testing (always fails)
authentication:
- mode: test-always-failMutual Authentication Policy Examples
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: mutual-auth-policy
namespace: production
spec:
endpointSelector:
matchLabels:
app: secure-service
ingress:
# Allow authenticated clients only
- fromEndpoints:
- matchLabels:
app: trusted-client
authentication:
- mode: required
toPorts:
- ports:
- port: "443"
protocol: TCP
# Optional authentication for monitoring
- fromEndpoints:
- matchLabels:
app: prometheus
authentication:
- mode: optional
toPorts:
- ports:
- port: "9090"
protocol: TCPSPIFFE ID-based Authentication
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: spiffe-auth
namespace: default
spec:
endpointSelector:
matchLabels:
app: database
ingress:
# Allow specific SPIFFE ID only
- fromEndpoints:
- matchLabels:
app: backend
authentication:
- mode: required
# SPIFFE ID verification is performed automatically
# spiffe://cluster.local/ns/default/sa/backendEncryption
This section configures the payload-encryption mechanisms (WireGuard/IPsec) introduced conceptually in Mutual Authentication and Data Encryption above — encryption is a separate choice from mutual authentication, not a byproduct of it.
WireGuard Transparent Encryption
WireGuard encrypts all Pod-to-Pod traffic at the Linux kernel level:
# values.yaml - Enable WireGuard
encryption:
enabled: true
type: wireguard
wireguard:
# Userspace fallback (when kernel support unavailable)
userspaceFallback: true
# Node-to-node encryption
nodeEncryption: true# Check WireGuard status
cilium status | grep Encryption
# Expected output
Encryption: Wireguard [NodeEncryption: Enabled, cilium_wg0 (Pubkey: xxx, Port: 51871, Peers: 2)]
# Check WireGuard peers
cilium encrypt status
# Expected output
Encryption: Wireguard
Keys in use: 1
Max Seq. Number: 0x0
Errors: 0WireGuard Architecture
IPsec Encryption
# values.yaml - Enable IPsec
encryption:
enabled: true
type: ipsec
ipsec:
# IPsec interface
interface: ""
# Key rotation interval
keyRotationDuration: "5m"
# Encryption interface
mountPath: /etc/ipsec
# Generate IPsec key
# kubectl create secret generic -n kube-system cilium-ipsec-keys \
# --from-literal=keys="3 rfc4106(gcm(aes)) $(openssl rand -hex 20) 128"Encryption Comparison
| Feature | WireGuard | IPsec |
|---|---|---|
| Performance | Very High | High |
| Configuration Complexity | Low | Medium |
| Kernel Support | 5.6+ (built-in) | All versions |
| Encryption Algorithm | ChaCha20Poly1305 | AES-GCM, etc. |
| Key Management | Automatic | Manual/Automatic |
| Standard | Non-standard | IETF Standard |
Identity-based Security
Cilium Identity
Cilium applies security policies based on identity instead of IP:
Identity Components
# Identity label composition
# - k8s:io.kubernetes.pod.namespace
# - k8s:io.cilium.k8s.policy.serviceaccount
# - k8s:app
# - k8s:version
# - Other user-defined labels
# List identities
cilium identity list
# Example output
IDENTITY LABELS
1 reserved:host
2 reserved:world
3 reserved:unmanaged
4 reserved:health
5 reserved:init
6 reserved:remote-node
12345 k8s:app=frontend,k8s:io.kubernetes.pod.namespace=default
12346 k8s:app=backend,k8s:io.kubernetes.pod.namespace=defaultIdentity-based Policy
# Identity-based network policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: identity-based-policy
namespace: default
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
# Allow only Pods with specific labels (Identity)
- fromEndpoints:
- matchLabels:
app: frontend
environment: production
toPorts:
- ports:
- port: "8080"
# Allow specific service from another namespace
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: monitoring
app: prometheus
toPorts:
- ports:
- port: "9090"IP vs Identity Comparison
External PKI Integration
cert-manager Integration
# Certificate management with cert-manager
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cilium-ca-issuer
spec:
ca:
secretName: cilium-ca-secret
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: cilium-spire-ca
namespace: cilium-spire
spec:
secretName: spire-ca-secret
duration: 8760h # 1 year
renewBefore: 720h # Renew 30 days before expiry
isCA: true
privateKey:
algorithm: ECDSA
size: 256
subject:
organizations:
- Cilium
commonName: SPIRE CA
issuerRef:
name: cilium-ca-issuer
kind: ClusterIssuerVault Integration
# Use Vault as CA in SPIRE
apiVersion: v1
kind: ConfigMap
metadata:
name: spire-server-config
namespace: cilium-spire
data:
server.conf: |
server {
trust_domain = "cluster.local"
ca_subject = {
country = ["US"]
organization = ["MyOrg"]
common_name = ""
}
# Vault UpstreamAuthority
UpstreamAuthority "vault" {
plugin_data {
vault_addr = "https://vault.vault.svc:8200"
pki_mount_path = "pki"
ca_cert_path = "/vault/ca/ca.crt"
token_path = "/vault/token/token"
}
}
}Zero Trust Networking
Default Deny Policy
# Cluster-wide default deny
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: default-deny
spec:
endpointSelector: {}
ingress:
- fromEndpoints:
- matchLabels:
reserved:host: ""
egress:
- toEndpoints:
- matchLabels:
reserved:host: ""
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDPLeast Privilege Access
# Production namespace security policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: production-security
namespace: production
spec:
# Apply to all Pods
endpointSelector: {}
# Default deny
ingressDeny:
- fromEntities:
- world
# Allow rules
ingress:
# Allow communication within same namespace
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: production
authentication:
- mode: required
# Allow access from Ingress Controller
- fromEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: ingress-nginx
app: nginx-ingress
toPorts:
- ports:
- port: "8080"
egress:
# DNS
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
# Communication within same namespace
- toEndpoints:
- matchLabels:
k8s:io.kubernetes.pod.namespace: production
authentication:
- mode: requiredMicrosegmentation
# 3-tier architecture security
---
# Frontend policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: frontend-policy
namespace: app
spec:
endpointSelector:
matchLabels:
tier: frontend
ingress:
- fromEntities:
- world
toPorts:
- ports:
- port: "443"
egress:
- toEndpoints:
- matchLabels:
tier: backend
toPorts:
- ports:
- port: "8080"
---
# Backend policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: backend-policy
namespace: app
spec:
endpointSelector:
matchLabels:
tier: backend
ingress:
- fromEndpoints:
- matchLabels:
tier: frontend
toPorts:
- ports:
- port: "8080"
authentication:
- mode: required
egress:
- toEndpoints:
- matchLabels:
tier: database
toPorts:
- ports:
- port: "5432"
authentication:
- mode: required
---
# Database policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: database-policy
namespace: app
spec:
endpointSelector:
matchLabels:
tier: database
ingress:
- fromEndpoints:
- matchLabels:
tier: backend
toPorts:
- ports:
- port: "5432"
authentication:
- mode: required
# No external egress (data exfiltration prevention)
egressDeny:
- toEntities:
- worldSecurity Auditing and Monitoring
Policy Audit Mode
# Test policy in audit mode
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: audit-policy
namespace: default
annotations:
# Audit mode - logging only, no blocking
cilium.io/audit-mode: "true"
spec:
endpointSelector:
matchLabels:
app: backend
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"Policy Violation Monitoring
# Observe policy violations with Hubble
hubble observe --verdict DROPPED
# Dropped traffic in specific namespace
hubble observe --namespace production --verdict DROPPED
# Policy violation statistics
hubble observe --verdict DROPPED -o json | jq -r '.flow | "\(.source.namespace)/\(.source.pod_name) -> \(.destination.namespace)/\(.destination.pod_name)"' | sort | uniq -c | sort -rnPrometheus Metrics
# Collect security-related metrics
hubble:
metrics:
enabled:
- dns
- drop
- flow
- http
- icmp
- port-distribution
- tcp
# Useful metrics
# - cilium_drop_count_total: Packets dropped by policy
# - cilium_policy_verdict: Policy decisions (allow/deny)
# - cilium_forward_count_total: Forwarded packetsNext Steps
- Observability: Security monitoring with Hubble
- Ingress & Gateway: External traffic security
- Best Practices: Production security configuration