PRACTITIONER GUIDE | CLOUD SECURITY
Practitioner Guide15 min read

Cloud-Native Security Architecture: Patterns for Microservices and Kubernetes

96%
of organizations running Kubernetes have experienced a security incident in the past 12 months (Red Hat State of Kubernetes Security 2025)
Service mesh
adoption has grown 340% since 2021 -- mTLS is now the primary zero trust control for east-west microservice traffic
OPA/Gatekeeper
policy-as-code prevents 89% of common Kubernetes security misconfigurations at admission time before workloads run

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:

FeatureIstioLinkerdCilium (eBPF)
mTLSYes (sidecar)Yes (sidecar)Yes (eBPF, no sidecar)
Authorization policyFine-grainedBasicFine-grained
Operational complexityHighLowMedium
Performance overhead~10ms latency~1ms latencyMinimal (eBPF)
ObservabilityExcellent (Kiali)GoodGood
Free daily briefing

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:

PolicyWhat It Enforces
Require non-rootContainers must set runAsNonRoot: true
Disallow privilegedNo privileged: true containers
Read-only root filesystemreadOnlyRootFilesystem: true required
Drop all capabilitiescapabilities.drop: [ALL] required
Require resource limitsCPU and memory limits mandatory (prevents DoS)
Disallow hostNetwork/hostPIDPrevents host namespace breakout
Require signed imagesOnly images with valid Cosign signatures allowed
Disallow latest tagImage tags must be explicit (not :latest)
Require labelsowner, 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

  1. CNCF Cloud Native Security Whitepaper
  2. Istio Security Documentation
  3. OPA Gatekeeper Policy Library
  4. SPIFFE/SPIRE Project

Free resources

25
Free download

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.

No spam. Unsubscribe anytime.

Free download

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.

No spam. Unsubscribe anytime.

Free newsletter

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.

Eric Bang
Author

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.

Free Brief

The Mythos Brief is free.

AI that finds 27-year-old zero-days. What it means for your security program.

Joins Decryption Digest. Unsubscribe anytime.

Daily Briefing

Get briefings like this every morning

Actionable threat intelligence for working practitioners. Free. No spam. Trusted by 50,000+ SOC analysts, CISOs, and security engineers.

Unsubscribe anytime.

Mythos Brief

Anthropic's AI finds zero-days your scanners miss.