| name | zero-trust-patterns |
| description | Zero-Trust security patterns — mTLS between microservices (Istio/SPIFFE), SPIRE workload identity, OPA/Envoy authorization, NetworkPolicy default-deny-all, short-lived credentials, service mesh security, and Kubernetes RBAC hardening. |
Zero-Trust Patterns
When to Activate
- Designing service-to-service communication in Kubernetes or cloud environments
- Implementing mTLS between microservices
- Setting up SPIFFE/SPIRE for workload identity
- Configuring Istio or Linkerd service mesh
- Writing Kubernetes NetworkPolicies
- Reviewing east-west traffic security
- Building BeyondCorp-style access controls
- Auditing existing cluster network policies for trust boundary gaps
Core Principles (NIST SP 800-207)
Zero-Trust Architecture operates on four pillars:
- Never trust, always verify — even traffic from within the private network is untrusted. Every connection requires authentication and authorization.
- Explicit verification — identity + device + context checked at every request (not just at the perimeter).
- Least Privilege Access — minimal rights, just enough to complete the task, scoped to the operation.
- Assume Breach — design to minimize lateral movement when an attacker is already inside the network.
Service Identity with SPIFFE/SPIRE
SPIFFE (Secure Production Identity Framework for Everyone) is the standard for cryptographic service identities.
SVID Format
spiffe://trust-domain/path/to/workload
# Example:
spiffe://prod.example.com/ns/payments/sa/checkout-service
SPIRE Server + Agent Setup
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: spire-server
namespace: spire
spec:
replicas: 1
selector:
matchLabels:
app: spire-server
template:
spec:
containers:
- name: spire-server
image: ghcr.io/spiffe/spire-server:1.9.0
args:
- -config
- /run/spire/config/server.conf
volumeMounts:
- name: spire-config
mountPath: /run/spire/config
- name: spire-data
mountPath: /run/spire/data
# server.conf
server {
bind_address = "0.0.0.0"
bind_port = "8081"
trust_domain = "prod.example.com"
data_dir = "/run/spire/data"
log_level = "INFO"
# JWT SVIDs for service-to-service auth
jwt_issuer = "https://spire-server.spire.svc.cluster.local"
}
plugins {
DataStore "sql" {
plugin_data {
database_type = "sqlite3"
connection_string = "/run/spire/data/datastore.sqlite3"
}
}
NodeAttestor "k8s_psat" {
plugin_data {
clusters = {
"my-cluster" = {
service_account_allow_list = ["spire:spire-agent"]
}
}
}
}
KeyManager "memory" {
plugin_data {}
}
}
Workload Registration
kubectl exec -n spire spire-server-0 -- \
/opt/spire/bin/spire-server entry create \
-spiffeID spiffe://prod.example.com/ns/payments/sa/checkout-service \
-parentID spiffe://prod.example.com/k8s-node/node1 \
-selector k8s:ns:payments \
-selector k8s:sa:checkout-service
Retrieving SVIDs from Workload API (Go)
import (
"github.com/spiffe/go-spiffe/v2/workloadapi"
"github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig"
)
func newTLSConfig(ctx context.Context) (*tls.Config, error) {
source, err := workloadapi.NewX509Source(ctx)
if err != nil {
return nil, fmt.Errorf("create x509 source: %w", err)
}
return tlsconfig.MTLSClientConfig(source, source, tlsconfig.AuthorizeAny()), nil
}
Istio Service Mesh
Installation
istioctl install --set profile=ambient
istioctl install --set profile=default
kubectl label namespace payments istio-injection=enabled
Enforce Strict mTLS Namespace-Wide
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: payments
spec:
mtls:
mode: STRICT
Authorization Policies (Default-Deny + Allowlist)
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: payments
spec: {}
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: allow-checkout-to-processor
namespace: payments
spec:
selector:
matchLabels:
app: payment-processor
rules:
- from:
- source:
principals:
- "cluster.local/ns/payments/sa/checkout-service"
to:
- operation:
methods: ["POST"]
paths: ["/v1/payments"]
Traffic Management
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: payment-processor
namespace: payments
spec:
hosts:
- payment-processor
http:
- retries:
attempts: 3
perTryTimeout: 2s
retryOn: gateway-error,connect-failure,retriable-4xx
timeout: 10s
route:
- destination:
host: payment-processor
Observability (Zero-Code)
istioctl proxy-config log payment-processor-pod --level debug
kubectl port-forward svc/kiali -n istio-system 20001:20001
Linkerd (Lightweight Alternative)
Choose Linkerd over Istio for: smaller teams, simpler operational model, better performance, no CRD explosion.
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
kubectl annotate namespace payments linkerd.io/inject=enabled
linkerd viz tap deploy/checkout-service -n payments
linkerd viz edges deployment -n payments
apiVersion: policy.linkerd.io/v1beta3
kind: Server
metadata:
name: payment-processor
namespace: payments
spec:
podSelector:
matchLabels:
app: payment-processor
port: 8080
proxyProtocol: HTTP/2
---
apiVersion: policy.linkerd.io/v1beta3
kind: MeshTLSAuthentication
metadata:
name: checkout-service-authn
namespace: payments
spec:
identities:
- "checkout-service.payments.serviceaccount.identity.linkerd.cluster.local"
---
apiVersion: policy.linkerd.io/v1beta3
kind: AuthorizationPolicy
metadata:
name: checkout-to-processor
namespace: payments
spec:
targetRef:
group: policy.linkerd.io
kind: Server
name: payment-processor
requiredAuthenticationRefs:
Kubernetes NetworkPolicies
NetworkPolicies are the Layer-3/4 firewall — always use them even with a service mesh.
Pattern: Default Deny All, then Allowlist
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-checkout-to-processor
namespace: payments
spec:
podSelector:
matchLabels:
app: payment-processor
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: checkout-service
ports:
- protocol: TCP
port: 8080
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: payments
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Cilium — Layer-7 NetworkPolicies (HTTP-aware)
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: l7-payment-processor
namespace: payments
spec:
endpointSelector:
matchLabels:
app: payment-processor
ingress:
- fromEndpoints:
- matchLabels:
app: checkout-service
toPorts:
- ports:
- port: "8080"
protocol: TCP
rules:
http:
- method: POST
path: /v1/payments
East-West Traffic Security
Secrets: Never in Environment Variables
env:
- name: DB_PASSWORD
value: "supersecret"
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-db: "secret/payments/db"
vault.hashicorp.com/role: "checkout-service"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "secret/payments/db" -}}
DB_PASSWORD={{ .Data.data.password }}
{{- end }}
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-secret
namespace: payments
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: ClusterSecretStore
target:
name: db-credentials
creationPolicy: Owner
data:
- secretKey: DB_PASSWORD
remoteRef:
key: prod/payments/db
property: password
Kubernetes Token Volume Projection (Audience-Bound, Short-Lived)
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
audience: vault
expirationSeconds: 3600
path: token
Zero-Trust for APIs — BeyondCorp Pattern
User Request
│
▼
Identity-Aware Proxy (IAP / Verified Access)
│ checks:
│ - Identity (Google/OIDC token)
│ - Device trust (MDM-enrolled, cert-based)
│ - Context (IP reputation, time of day)
│
▼ [Allow or Deny]
Backend Service (no direct internet exposure)
AWS Verified Access Example
# Terraform: AWS Verified Access endpoint
resource "aws_verifiedaccess_endpoint" "internal_api" {
verified_access_group_id = aws_verifiedaccess_group.main.id
endpoint_type = "load-balancer"
attachment_type = "vpc"
load_balancer_options {
load_balancer_arn = aws_lb.internal.arn
port = 443
protocol = "https"
subnet_ids = var.private_subnet_ids
}
security_group_ids = [aws_security_group.verified_access.id]
}
resource "aws_verifiedaccess_trust_provider" "oidc" {
trust_provider_type = "user"
user_trust_provider_type = "oidc"
oidc_options {
issuer = "https://accounts.google.com"
authorization_endpoint = "https://accounts.google.com/o/oauth2/v2/auth"
token_endpoint = "https://oauth2.googleapis.com/token"
client_id = var.oauth_client_id
client_secret = var.oauth_client_secret
scope = "openid email"
}
}
Zero-Trust Checklist
Before deploying any new service:
Tool Reference
| Tool | Purpose |
|---|
SPIRE | SPIFFE reference implementation — workload identity |
Istio | Full-featured service mesh — mTLS, traffic management, observability |
Linkerd | Lightweight service mesh — simpler, better performance |
Cilium | eBPF-based CNI — Layer-7 NetworkPolicies, identity-aware routing |
Vault | Secret management — dynamic secrets, lease-based rotation |
External Secrets Operator | Sync secrets from AWS/GCP/Azure into k8s |
OPA/Gatekeeper | Policy enforcement — enforce Zero-Trust rules as admission webhooks |
cert-manager | Automate certificate rotation for mTLS |
Related Skills
kubernetes-patterns — base k8s patterns (Deployments, Services, Ingress)
devsecops-patterns — SAST/DAST/OPA policy automation
auth-patterns — user authentication (JWT, OAuth2) — different from service identity
supply-chain-security — SBOM, SLSA, Sigstore for artifact trust