Cloud-Native Security Architecture: Patterns for Microservices and Kubernetes
Traditional security architecture places controls at the perimeter -- a firewall inspecting traffic entering and leaving the network, with implicit trust inside. Cloud-native architectures shatter this model: hundreds of microservices communicate laterally across the cluster, any of which could be a pivot point for an attacker who compromises one container. Zero trust for cloud-native environments means every service-to-service call is authenticated, every API call is authorized, and every secret is injected dynamically -- never baked into images or config files. This guide covers the architectural patterns that implement this model in practice.
The Cloud-Native Security Model
Cloud-native security operates on different assumptions than traditional security:
Traditional model:
- Trust the network perimeter; distrust outside
- North-south traffic (ingress/egress) is inspected; east-west traffic (service-to-service) is trusted
- Credentials are long-lived and stored in config files
- Security controls are applied to VMs by configuration management
Cloud-native zero trust model:
- No implicit trust based on network location -- every request is authenticated regardless of origin
- East-west traffic is mutually authenticated and encrypted (service mesh mTLS)
- Credentials are short-lived and dynamically injected -- never stored in images or env files
- Security policy is code (OPA, Kyverno) -- enforced at admission time and runtime
- Workloads have cryptographic identities (SPIFFE/SPIRE) -- not IP-based trust
Security architecture layers:
┌─────────────────────────────────────────────────────┐
│ Supply Chain Security │
│ (Image signing, SBOM, trusted registries) │
├─────────────────────────────────────────────────────┤
│ Admission Control (Policy-as-Code) │
│ (OPA/Gatekeeper, Kyverno -- what can run) │
├─────────────────────────────────────────────────────┤
│ Workload Identity & Service-to-Service Auth │
│ (SPIFFE/SPIRE, service mesh mTLS) │
├─────────────────────────────────────────────────────┤
│ Secrets Management │
│ (Vault injection, External Secrets Operator) │
├─────────────────────────────────────────────────────┤
│ Network Policy (Microsegmentation) │
│ (Kubernetes NetworkPolicy, Cilium) │
├─────────────────────────────────────────────────────┤
│ Runtime Security (Anomaly Detection) │
│ (Falco, Sysdig -- what is actually happening) │
└─────────────────────────────────────────────────────┘
Each layer is independently valuable but the combination provides defense-in-depth: if an attacker bypasses supply chain controls and gets a malicious image running, admission control limits what it can do; if it bypasses admission control, network policy limits what it can reach; if it bypasses network policy, it cannot get secrets; if it somehow gets secrets, runtime detection alerts on anomalous behavior.
Service Mesh mTLS: Zero Trust for East-West Traffic
A service mesh is an infrastructure layer that handles service-to-service communication. Security-relevant capabilities: mutual TLS (mTLS) for all east-west traffic, traffic policies (which services can communicate with which), and observability (every request logged with full context).
How mTLS works in a service mesh:
Each pod gets a sidecar proxy (Envoy in Istio, Linkerd proxy) that intercepts all inbound and outbound traffic. The proxy presents a certificate identifying the service -- issued by the mesh's certificate authority, derived from the Kubernetes service account. Certificates rotate automatically (every 24 hours by default in Istio).
Without mTLS: Service A calls Service B over plaintext HTTP. Any pod in the cluster can make the same call. There is no authentication of the caller.
With mTLS: Service A's Envoy proxy presents its certificate to Service B's Envoy proxy. B's proxy verifies A's certificate against the mesh CA. Only if the certificate is valid and the authorization policy allows A to call B does the request succeed.
Istio mTLS setup:
# Enable strict mTLS across the entire mesh (no plaintext allowed)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system # applies mesh-wide
spec:
mtls:
mode: STRICT # reject any non-mTLS connection
---
# Authorization policy: only the payment service can call the database service
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: db-access-policy
namespace: data
spec:
selector:
matchLabels:
app: postgres
rules:
- from:
- source:
principals: ["cluster.local/ns/payments/sa/payment-service"]
to:
- operation:
ports: ["5432"]
Linkerd as a simpler alternative:
Linkerd is a CNCF project with a smaller operational footprint than Istio. It enables mTLS by default upon installation with minimal configuration:
# Install Linkerd
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
# Inject Linkerd sidecar into a namespace (enables mTLS for all pods)
kubectl annotate namespace payments linkerd.io/inject=enabled
# Verify mTLS is active
linkerd viz stat deployment -n payments
Service mesh comparison:
| Feature | Istio | Linkerd | Cilium (eBPF) |
|---|---|---|---|
| mTLS | Yes (sidecar) | Yes (sidecar) | Yes (eBPF, no sidecar) |
| Authorization policy | Fine-grained | Basic | Fine-grained |
| Operational complexity | High | Low | Medium |
| Performance overhead | ~10ms latency | ~1ms latency | Minimal (eBPF) |
| Observability | Excellent (Kiali) | Good | Good |
Briefings like this, every morning before 9am.
Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.
Policy-as-Code with OPA Gatekeeper and Kyverno
Kubernetes admission controllers validate and mutate resources before they are persisted to etcd. Policy-as-code tools implement admission control as declarative policies -- preventing insecure workloads from running rather than detecting and alerting after the fact.
OPA (Open Policy Agent) with Gatekeeper:
# Install Gatekeeper
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml
# Create a constraint template: require non-root containers
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequirenonroot
spec:
crd:
spec:
names:
kind: K8sRequireNonRoot
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequirenonroot
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.runAsNonRoot != true
msg := sprintf("Container %v must run as non-root", [container.name])
}
---
# Apply the constraint to all pods
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireNonRoot
metadata:
name: require-non-root
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
Essential Gatekeeper / Kyverno policies:
| Policy | What It Enforces |
|---|---|
| Require non-root | Containers must set runAsNonRoot: true |
| Disallow privileged | No privileged: true containers |
| Read-only root filesystem | readOnlyRootFilesystem: true required |
| Drop all capabilities | capabilities.drop: [ALL] required |
| Require resource limits | CPU and memory limits mandatory (prevents DoS) |
| Disallow hostNetwork/hostPID | Prevents host namespace breakout |
| Require signed images | Only images with valid Cosign signatures allowed |
| Disallow latest tag | Image tags must be explicit (not :latest) |
| Require labels | owner, team, environment labels required |
Kyverno (simpler alternative to OPA for Kubernetes-specific policies):
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-privileged-containers
spec:
validationFailureAction: Enforce
rules:
- name: check-privileged
match:
resources:
kinds: [Pod]
validate:
message: "Privileged containers are not allowed."
pattern:
spec:
containers:
- securityContext:
privileged: "false | ~X"
Secrets Management and Dynamic Credential Injection
Static secrets (passwords in config files, API keys in environment variables baked into images) are the most common cause of cloud-native credential exposure.
Anti-patterns to eliminate:
# Never: hardcoded in Deployment manifest
env:
- name: DB_PASSWORD
value: "supersecret123"
# Never: from Kubernetes Secret as env var (stored in etcd unencrypted by default)
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
Recommended: Vault Agent Injector
HashiCorp Vault injects secrets directly into the pod filesystem at startup via an init container and sidecar, with no Kubernetes Secrets created:
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
template:
metadata:
annotations:
# These annotations trigger Vault injection
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "payment-service"
vault.hashicorp.com/agent-inject-secret-config: "database/creds/payment-db"
vault.hashicorp.com/agent-inject-template-config: |
{{- with secret "database/creds/payment-db" -}}
DB_USER={{ .Data.username }}
DB_PASSWORD={{ .Data.password }}
{{- end }}
spec:
serviceAccountName: payment-service # Vault authenticates via K8s service account JWT
containers:
- name: payment-service
# App reads /vault/secrets/config at startup -- no env vars needed
External Secrets Operator:
ESO syncs secrets from external stores (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager) into Kubernetes Secrets -- with automatic rotation:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: payment-db-secret
spec:
refreshInterval: 1h # sync every hour (picks up rotated secrets)
secretStoreRef:
name: aws-secretsmanager
kind: ClusterSecretStore
target:
name: payment-db-secret # creates this Kubernetes Secret
data:
- secretKey: password
remoteRef:
key: production/payment-db
property: password
Workload Identity (IRSA / Workload Identity Federation):
Rather than storing cloud credentials in secrets, bind Kubernetes service accounts to cloud IAM roles directly:
# AWS IRSA: annotate the service account with the IAM role ARN
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-service
namespace: payments
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789:role/payment-service-role
# The pod can now call AWS APIs as this role -- no credentials stored anywhere
Network Microsegmentation and Runtime Detection
Kubernetes Network Policies:
By default, all pods can communicate with all other pods in a cluster. Network policies implement microsegmentation at the Kubernetes level:
# Default deny all ingress and egress for a namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {} # applies to all pods
policyTypes:
- Ingress
- Egress
---
# Allow: payment-service can receive traffic from api-gateway only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: payment-service-ingress
namespace: payments
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: api-gateway
podSelector:
matchLabels:
app: api-gateway
ports:
- protocol: TCP
port: 8080
Cilium for L7 network policy:
Kubernetes native NetworkPolicy operates at L3/L4. Cilium (eBPF-based) adds L7 enforcement -- allowing or denying specific HTTP methods, paths, and headers:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
spec:
description: "Only allow GET /api/v1/products from frontend"
endpointSelector:
matchLabels:
app: product-service
ingress:
- fromEndpoints:
- matchLabels:
app: frontend
toPorts:
- ports:
- port: "8080"
rules:
http:
- method: GET
path: /api/v1/products
Runtime security with Falco:
Falco detects anomalous container behavior by monitoring Linux system calls:
# Custom Falco rule: alert if any container reads /etc/shadow
- rule: Read shadow file in container
desc: An attempt to read /etc/shadow in a container
condition: >
open_read and container and fd.name = /etc/shadow
output: >
Shadow file read in container (user=%user.name command=%proc.cmdline
container=%container.name image=%container.image)
priority: CRITICAL
tags: [container, credential-access]
# Built-in rules catch:
# - Shell spawned in a container (rare in production -- indicates attacker pivot)
# - Outbound connection to non-expected destination
# - Cryptominer process patterns
# - /proc filesystem access (reconnaissance indicator)
The bottom line
Cloud-native security requires layered controls that work together: admission control prevents insecure workloads from running, service mesh mTLS ensures every service-to-service call is authenticated, network policies microsegment east-west traffic, and dynamic secret injection eliminates static credentials. No single layer is sufficient -- an attacker who bypasses one should encounter the next. Start with the highest-leverage controls: OPA/Kyverno admission policies (prevents misconfigured workloads), Kubernetes NetworkPolicy default-deny (limits lateral movement), and Vault or ESO for secret management. Add service mesh mTLS as the program matures and the operational overhead is acceptable.
Frequently asked questions
What is a service mesh and do we need one for security?
A service mesh is an infrastructure layer that manages service-to-service communication in a microservices environment. For security, its primary value is automatic mutual TLS (mTLS) for all east-west traffic -- every service call is encrypted and both parties authenticate with certificates. This implements zero trust networking without requiring application code changes. Whether you need one depends on your environment: if you have 5-10 microservices, Kubernetes NetworkPolicy plus application-level authentication may be sufficient. If you have 50+ microservices with complex communication patterns, a service mesh (Istio or Linkerd) provides significantly better security and observability. The operational overhead of Istio is substantial -- consider Linkerd for a lighter-weight option.
What is OPA (Open Policy Agent) and how is it different from Kyverno?
Both are Kubernetes admission controllers that enforce policies on resources before they are created or updated. OPA (with Gatekeeper) is a general-purpose policy engine using Rego, a purpose-built policy language -- it can enforce policies across Kubernetes, Terraform, HTTP APIs, and other systems. Kyverno is Kubernetes-native and uses YAML-based policy rules that are easier to read and write without learning Rego. For teams new to policy-as-code, Kyverno has a lower learning curve. For teams that want a unified policy engine across their entire infrastructure (not just Kubernetes), OPA provides more flexibility. Both are CNCF projects with strong adoption.
What is SPIFFE/SPIRE and how does it relate to service mesh identity?
SPIFFE (Secure Production Identity Framework For Everyone) is a standard for workload identity in cloud-native environments. It defines how services prove their identity cryptographically using SVIDs (SPIFFE Verifiable Identity Documents) -- X.509 certificates or JWTs. SPIRE is the reference implementation. Service meshes like Istio use SPIFFE-compatible identity for their mTLS certificates -- each pod's Envoy proxy has a certificate with the pod's SPIFFE ID (for example, spiffe://cluster.local/ns/payments/sa/payment-service). This identity can be used in authorization policies across the mesh. SPIRE can also be used outside a service mesh to provide workload identity for any process that needs to authenticate to another service.
How do we manage secrets in Kubernetes without storing them in etcd?
Kubernetes Secrets are stored in etcd -- by default unencrypted, though etcd encryption-at-rest can be enabled. Even with encryption, any process with access to the Kubernetes API can read Secrets. Alternatives: (1) Vault Agent Injector -- secrets are injected directly into the pod filesystem by a sidecar; no Kubernetes Secret is created; secrets are retrieved from Vault using the pod's service account JWT for authentication; (2) External Secrets Operator -- syncs secrets from external stores (AWS Secrets Manager, Azure Key Vault) into Kubernetes Secrets, with automatic rotation; the secret still exists in etcd but rotation is automated; (3) CSI Secrets Store -- mounts secrets directly from external stores as volumes without creating Kubernetes Secrets. For most organizations, External Secrets Operator provides the best balance of security and operational simplicity.
What is the difference between Kubernetes NetworkPolicy and a service mesh for network security?
Kubernetes NetworkPolicy operates at L3/L4 -- it allows or denies traffic based on pod labels, namespace labels, and ports. It is implemented by the CNI plugin (Calico, Cilium, Weave) and does not require a service mesh. Service mesh traffic policies operate at L7 -- they can allow or deny based on HTTP methods, paths, headers, and service identity (via mTLS certificates). NetworkPolicy is simpler to configure and has lower overhead; service mesh policies are more powerful but require deploying a mesh. Use both: NetworkPolicy for coarse-grained segmentation (payments namespace cannot receive traffic from logging namespace), service mesh policies for fine-grained application-layer control (payment service can only call POST /transactions on the ledger service, not GET /admin).
How do we implement zero trust for microservices without a service mesh?
A service mesh is the most operationally efficient way to implement zero trust for microservices, but it is not the only way. Without a mesh: (1) Implement mTLS at the application level -- each service loads its own certificate and validates the caller's certificate; libraries like grpc-go handle this natively for gRPC services; (2) Use JWT-based service authentication -- each service authenticates to others using a short-lived JWT signed by a trusted authority (this is easier to implement but tokens can be exfiltrated); (3) Use cloud provider service identity -- on EKS, services use IRSA to authenticate to AWS services; on GKE, Workload Identity provides similar capability; for service-to-service calls, use the cloud provider's built-in service identity tokens. The application-level approach requires more development work per service and is harder to audit consistently -- a service mesh solves this operationally across all services.
Sources & references
Free resources
Critical CVE Reference Card 2025–2026
25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.
Ransomware Incident Response Playbook
Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.
Get threat intel before your inbox does.
50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, weekly, no spam.
Unsubscribe anytime. We never sell your data.

Founder & Cybersecurity Evangelist, Decryption Digest
Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals weekly.
The Mythos Brief is free.
AI that finds 27-year-old zero-days. What it means for your security program.
