Zero Trust Architecture for Cloud-Native Applications
Implement Zero Trust security principles in Kubernetes environments with service mesh, mTLS, SPIFFE/SPIRE identity, policy-as-code, and defense in depth strategies.
Table of Contents
Introduction
In the era of cloud-native applications, the traditional perimeter-based security model has become obsolete. With microservices distributed across multiple clusters, cloud providers, and edge locations, the concept of a secure "inside" network no longer applies. Zero Trust Architecture emerges as the paradigm shift needed to secure modern distributed systems.
Zero Trust Principle: Never trust, always verify. Every request must be authenticated, authorized, and encrypted regardless of its origin.
This article provides a comprehensive guide to implementing Zero Trust security in Kubernetes environments, covering identity management with SPIFFE/SPIRE, service mesh with Istio, policy-as-code with OPA, and secrets management with HashiCorp Vault.
Zero Trust Principles
Zero Trust is not a product but a security philosophy built on several core tenets:
| Principle | Description | Implementation |
|---|---|---|
| Never Trust, Always Verify | All access requests must be authenticated | mTLS, SPIFFE identities |
| Least Privilege Access | Grant minimal permissions required | RBAC, OPA policies |
| Assume Breach | Design as if attackers are already inside | Micro-segmentation, monitoring |
| Verify Explicitly | Authenticate based on all available data | Context-aware access control |
| Secure All Communications | Encrypt all traffic, internal and external | mTLS everywhere |
The Evolution from Perimeter Security
Traditional security operates like a castle with a moat—once you're inside, you're trusted. Zero Trust operates like a modern airport—every person is verified at every checkpoint, regardless of where they came from.
Zero Trust Network Architecture
A comprehensive Zero Trust architecture in Kubernetes consists of multiple layers working together:
Zero Trust Architecture
Architecture Layers
| Layer | Components | Purpose |
|---|---|---|
| External | Users, External Services | Traffic sources |
| Edge Security | WAF, API Gateway, DDoS Protection | Perimeter defense |
| Identity | Identity Provider, SPIRE, Vault | Authentication and secrets |
| Policy | OPA/Gatekeeper, Authorization Service | Access control |
| Service Mesh | Istio, Envoy Proxies | mTLS and traffic management |
| Workloads | Services A, B, C | Application layer |
| Data | Database, Cache, Message Queue | Data layer |
Service Mesh with mTLS
Istio service mesh provides the foundational layer for Zero Trust by enforcing mutual TLS (mTLS) between all services.
Implementing Strict mTLS
Enable strict mTLS across your entire mesh:
# PeerAuthentication - Enforce mTLS mesh-wide
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: STRICT
---
# DestinationRule - Enforce mTLS for all services
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: default
namespace: istio-system
spec:
host: "*.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
Authorization Policy - Deny by Default
# Authorization Policy - Deny by default
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: production
spec:
{} # Empty spec = deny all
---
# Authorization Policy - Allow specific services
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-order-service
namespace: production
spec:
selector:
matchLabels:
app: order-service
action: ALLOW
rules:
- from:
- source:
principals:
- cluster.local/ns/production/sa/api-gateway
- cluster.local/ns/production/sa/inventory-service
to:
- operation:
methods: ["GET", "POST"]
paths: ["/api/orders/*"]
Identity-Based Access with SPIFFE/SPIRE
SPIFFE (Secure Production Identity Framework for Everyone) provides a standard for service identity, while SPIRE is its production-ready implementation.
SPIFFE/SPIRE Architecture
SPIFFE Identity Model
| Component | Function |
|---|---|
| SPIRE Server | Issues and manages SPIFFE identities |
| SPIRE Agent | Runs on each node, attests workloads |
| SVID | SPIFFE Verifiable Identity Document |
| Trust Bundle | Root certificates for verification |
SPIRE Server Deployment
# SPIRE Server StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: spire-server
namespace: spire
spec:
replicas: 3
selector:
matchLabels:
app: spire-server
serviceName: spire-server
template:
metadata:
labels:
app: spire-server
spec:
serviceAccountName: spire-server
containers:
- name: spire-server
image: ghcr.io/spiffe/spire-server:1.8.0
args:
- -config
- /run/spire/config/server.conf
ports:
- containerPort: 8081
name: grpc
volumeMounts:
- name: spire-config
mountPath: /run/spire/config
readOnly: true
- name: spire-data
mountPath: /run/spire/data
livenessProbe:
exec:
command:
- /opt/spire/bin/spire-server
- healthcheck
initialDelaySeconds: 15
periodSeconds: 60
Workload Registration
# ClusterSPIFFEID for automatic workload registration
apiVersion: spire.spiffe.io/v1alpha1
kind: ClusterSPIFFEID
metadata:
name: production-workloads
spec:
spiffeIDTemplate: "spiffe://example.org/ns/{{ .PodMeta.Namespace }}/sa/{{ .PodSpec.ServiceAccountName }}"
podSelector:
matchLabels:
spiffe.io/spiffe-id: "true"
namespaceSelector:
matchLabels:
environment: production
dnsNameTemplates:
- "{{ .PodMeta.Name }}.{{ .PodMeta.Namespace }}.svc.cluster.local"
ttl: "1h"
jwtTTL: "5m"
Policy-as-Code with OPA/Gatekeeper
Open Policy Agent (OPA) with Gatekeeper provides Kubernetes-native policy enforcement.
Constraint Templates for Zero Trust
# Constraint Template: Require mTLS
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiremtls
spec:
crd:
spec:
names:
kind: K8sRequireMTLS
validation:
openAPIV3Schema:
type: object
properties:
exemptNamespaces:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiremtls
violation[{"msg": msg}] {
input.review.kind.kind == "PeerAuthentication"
input.review.object.spec.mtls.mode != "STRICT"
not exempt_namespace(input.review.object.metadata.namespace)
msg := sprintf("PeerAuthentication %v must have STRICT mTLS mode", [input.review.object.metadata.name])
}
exempt_namespace(ns) {
input.parameters.exemptNamespaces[_] == ns
}
---
# Constraint: Enforce mTLS
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireMTLS
metadata:
name: require-strict-mtls
spec:
match:
kinds:
- apiGroups: ["security.istio.io"]
kinds: ["PeerAuthentication"]
parameters:
exemptNamespaces:
- kube-system
- istio-system
Security Policy Constraints
# Constraint Template: No Privileged Containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8spsprivilegedcontainer
spec:
crd:
spec:
names:
kind: K8sPSPPrivilegedContainer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spsprivilegedcontainer
violation[{"msg": msg, "details": {}}] {
c := input_containers[_]
c.securityContext.privileged
msg := sprintf("Privileged container is not allowed: %v", [c.name])
}
input_containers[c] {
c := input.review.object.spec.containers[_]
}
input_containers[c] {
c := input.review.object.spec.initContainers[_]
}
---
# Constraint: Deny Privileged Containers
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
name: deny-privileged-containers
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
Secrets Management with Vault
HashiCorp Vault provides centralized secrets management with dynamic credentials, encryption as a service, and detailed audit logging.
Vault Secrets Management
Vault Integration Patterns
| Pattern | Use Case | Pros |
|---|---|---|
| Sidecar Injector | Inject secrets into pods | Automatic rotation, transparent |
| CSI Driver | Mount secrets as volumes | Native K8s integration |
| External Secrets | Sync to K8s secrets | GitOps friendly |
Kubernetes Authentication Method
#!/bin/bash
# Configure Vault Kubernetes Auth
# Enable Kubernetes auth method
vault auth enable kubernetes
# Configure Kubernetes auth
vault write auth/kubernetes/config \
kubernetes_host="https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT" \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_ca_cert="$(cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt)" \
issuer="https://kubernetes.default.svc.cluster.local"
# Create policy for applications
vault policy write app-policy - <<EOF
path "secret/data/{{identity.entity.aliases.auth_kubernetes_xxx.metadata.service_account_namespace}}/*" {
capabilities = ["read"]
}
path "database/creds/{{identity.entity.aliases.auth_kubernetes_xxx.metadata.service_account_namespace}}-*" {
capabilities = ["read"]
}
path "pki/issue/{{identity.entity.aliases.auth_kubernetes_xxx.metadata.service_account_namespace}}" {
capabilities = ["create", "update"]
}
EOF
# Create role for production namespace
vault write auth/kubernetes/role/production \
bound_service_account_names="*" \
bound_service_account_namespaces="production" \
policies="app-policy" \
ttl="1h"
Vault Sidecar Injection
# Deployment with Vault Sidecar Injection
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "production"
vault.hashicorp.com/agent-inject-secret-config: "secret/data/production/app-config"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "secret/data/production/app-config" -}}
export DATABASE_URL="{{ .Data.data.database_url }}"
export API_KEY="{{ .Data.data.api_key }}"
export ENCRYPTION_KEY="{{ .Data.data.encryption_key }}"
{{- end }}
vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/production-postgres"
vault.hashicorp.com/agent-inject-template-db-creds: |
{{- with secret "database/creds/production-postgres" -}}
export DB_USERNAME="{{ .Data.username }}"
export DB_PASSWORD="{{ .Data.password }}"
{{- end }}
spec:
serviceAccountName: secure-app
containers:
- name: app
image: myregistry/secure-app:v1.0
command: ["/bin/sh", "-c"]
args:
- source /vault/secrets/config && source /vault/secrets/db-creds && /app/start.sh
ports:
- containerPort: 8080
Network Segmentation and Micro-Segmentation
Zero Trust requires fine-grained network controls that go beyond traditional firewalls.
Network Policies
# Default Deny All - Apply to every namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Allow DNS Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# Frontend to API Gateway
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: frontend-to-api
namespace: api
spec:
podSelector:
matchLabels:
app: api-gateway
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
tier: frontend
podSelector:
matchLabels:
role: web
ports:
- protocol: TCP
port: 8080
Cilium Network Policies (Advanced)
# Cilium Network Policy with L7 filtering
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-gateway-l7
namespace: api
spec:
endpointSelector:
matchLabels:
app: api-gateway
ingress:
- fromEndpoints:
- matchLabels:
tier: frontend
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/.*"
- method: "POST"
path: "/api/v1/orders"
headers:
- "Content-Type: application/json"
- method: "GET"
path: "/health"
---
# Cilium DNS-aware policy
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: external-api-access
namespace: services
spec:
endpointSelector:
matchLabels:
app: payment-service
egress:
- toFQDNs:
- matchName: "api.stripe.com"
- matchName: "api.paypal.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
Continuous Verification and Monitoring
Zero Trust requires continuous monitoring and verification of all activities.
Prometheus Alerts for Zero Trust
# PrometheusRule for Zero Trust monitoring
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: zero-trust-alerts
namespace: monitoring
spec:
groups:
- name: zero-trust.rules
rules:
# mTLS violations
- alert: MTLSDisabled
expr: |
sum(istio_tcp_connections_opened_total{
connection_security_policy!="mutual_tls"
}) by (source_workload, destination_workload) > 0
for: 5m
labels:
severity: critical
annotations:
summary: Non-mTLS connection detected
description: "{{ $labels.source_workload }} to {{ $labels.destination_workload }} is not using mTLS"
# Authorization denials spike
- alert: AuthorizationDenialSpike
expr: |
sum(rate(istio_requests_total{
response_code="403"
}[5m])) by (destination_workload) > 10
for: 2m
labels:
severity: warning
annotations:
summary: High rate of authorization denials
# Certificate expiry
- alert: CertificateExpiringSoon
expr: |
(cert_exporter_cert_expires_in_seconds < 86400 * 7) and
(cert_exporter_cert_expires_in_seconds > 0)
for: 1h
labels:
severity: warning
annotations:
summary: Certificate expiring soon
# OPA policy violations
- alert: OPAPolicyViolations
expr: |
sum(rate(gatekeeper_violations[5m])) by (constraint_name) > 0
for: 1m
labels:
severity: warning
annotations:
summary: OPA policy violations detected
Falco Rules for Runtime Security
# Falco custom rules for Zero Trust
- rule: Unauthorized Service Account Token Access
desc: Detect attempts to access service account tokens
condition: >
open_read and
fd.name startswith /var/run/secrets/kubernetes.io and
not proc.name in (allowed_token_readers)
output: >
Unauthorized service account token access
(user=%user.name command=%proc.cmdline file=%fd.name container=%container.name)
priority: WARNING
tags: [zero-trust, kubernetes]
- rule: Unexpected Outbound Connection
desc: Detect unexpected outbound network connections
condition: >
outbound and
not fd.sip in (allowed_outbound_ips) and
not fd.sport in (allowed_outbound_ports) and
container
output: >
Unexpected outbound connection
(command=%proc.cmdline connection=%fd.name container=%container.name image=%container.image.repository)
priority: WARNING
tags: [zero-trust, network]
- rule: Privileged Container Started
desc: Detect privileged container execution
condition: >
container_started and container.privileged=true
output: >
Privileged container started
(container=%container.name image=%container.image.repository)
priority: CRITICAL
tags: [zero-trust, container]
- list: allowed_token_readers
items: [vault-agent, spire-agent, istio-proxy]
Best Practices Summary
| Area | Best Practice | Implementation |
|---|---|---|
| Identity | Use short-lived credentials | SPIFFE SVIDs with 1h TTL |
| Authentication | Require mutual TLS | Istio STRICT mTLS mode |
| Authorization | Deny by default | Empty AuthorizationPolicy |
| Secrets | Dynamic secrets | Vault database engine |
| Network | Micro-segmentation | Namespace-level NetworkPolicy |
| Monitoring | Continuous verification | Prometheus + Falco alerts |
| Compliance | Policy-as-code | OPA Gatekeeper constraints |
Conclusion
Implementing Zero Trust Architecture in Kubernetes is not a single product deployment but a comprehensive security transformation. By combining:
- Service Mesh (Istio): For mTLS and traffic management
- Identity (SPIFFE/SPIRE): For workload identity
- Secrets Management (Vault): For credential lifecycle
- Policy-as-Code (OPA/Gatekeeper): For admission control
- Network Policies: For micro-segmentation
- Runtime Security (Falco): For threat detection
You create a defense-in-depth architecture where every component continuously verifies trust, never assumes it.
The ZeroTrustK8s repository provides working examples of all components discussed in this article, ready for deployment in your Kubernetes clusters.
Remember: Zero Trust is a journey, not a destination. Start with identity and mTLS, then progressively add policy enforcement, monitoring, and automation.