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.

GT
Gonnect Team
January 12, 202515 min readView on GitHub
KubernetesIstioSPIFFE/SPIREOPAVaultmTLS

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:

PrincipleDescriptionImplementation
Never Trust, Always VerifyAll access requests must be authenticatedmTLS, SPIFFE identities
Least Privilege AccessGrant minimal permissions requiredRBAC, OPA policies
Assume BreachDesign as if attackers are already insideMicro-segmentation, monitoring
Verify ExplicitlyAuthenticate based on all available dataContext-aware access control
Secure All CommunicationsEncrypt all traffic, internal and externalmTLS 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

Loading diagram...

Architecture Layers

LayerComponentsPurpose
ExternalUsers, External ServicesTraffic sources
Edge SecurityWAF, API Gateway, DDoS ProtectionPerimeter defense
IdentityIdentity Provider, SPIRE, VaultAuthentication and secrets
PolicyOPA/Gatekeeper, Authorization ServiceAccess control
Service MeshIstio, Envoy ProxiesmTLS and traffic management
WorkloadsServices A, B, CApplication layer
DataDatabase, Cache, Message QueueData 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

Loading diagram...

SPIFFE Identity Model

ComponentFunction
SPIRE ServerIssues and manages SPIFFE identities
SPIRE AgentRuns on each node, attests workloads
SVIDSPIFFE Verifiable Identity Document
Trust BundleRoot 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

Loading diagram...

Vault Integration Patterns

PatternUse CasePros
Sidecar InjectorInject secrets into podsAutomatic rotation, transparent
CSI DriverMount secrets as volumesNative K8s integration
External SecretsSync to K8s secretsGitOps 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

AreaBest PracticeImplementation
IdentityUse short-lived credentialsSPIFFE SVIDs with 1h TTL
AuthenticationRequire mutual TLSIstio STRICT mTLS mode
AuthorizationDeny by defaultEmpty AuthorizationPolicy
SecretsDynamic secretsVault database engine
NetworkMicro-segmentationNamespace-level NetworkPolicy
MonitoringContinuous verificationPrometheus + Falco alerts
CompliancePolicy-as-codeOPA 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.

Further Reading