Zero Trust Security in 2026: Beyond the Perimeter
on Security, Zero trust, Iam, Ztna, Cloudflare, Oauth, Mtls, Devsecops
The old security model—trust the network, guard the perimeter—is dead. Zero Trust flips it: trust nothing, verify everything, assume breach.
Photo by FlyD on Unsplash
Core Principles
- Never trust, always verify - Every request is authenticated and authorized
- Least privilege access - Minimum permissions needed for the task
- Assume breach - Design as if attackers are already inside
- Micro-segmentation - Isolate resources, limit blast radius
The Zero Trust Stack
┌─────────────────────────────────────────────────────────┐
│ Identity Provider │
│ (Okta, Azure AD, Auth0) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Access Proxy / ZTNA │
│ (Cloudflare Access, Tailscale, Zscaler) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Service Mesh (mTLS) │
│ (Istio, Linkerd, Cilium) │
└──────────────────────────┬──────────────────────────────┘
│
┌──────────────────────────▼──────────────────────────────┐
│ Applications & Services │
└─────────────────────────────────────────────────────────┘
Implementing Zero Trust Access
Cloudflare Access Setup
# terraform/cloudflare-access.tf
# Access application for internal dashboard
resource "cloudflare_access_application" "dashboard" {
zone_id = var.zone_id
name = "Internal Dashboard"
domain = "dashboard.internal.company.com"
type = "self_hosted"
session_duration = "8h"
# Require identity verification
auto_redirect_to_identity = true
}
# Access policy - require SSO + device posture
resource "cloudflare_access_policy" "dashboard_policy" {
application_id = cloudflare_access_application.dashboard.id
zone_id = var.zone_id
name = "Require corp identity"
precedence = 1
decision = "allow"
include {
login_method = ["oidc"]
}
require {
# Must be in engineering group
group = ["engineering@company.com"]
# Device must be managed
device_posture = [
cloudflare_device_posture_rule.managed_device.id
]
}
}
# Device posture check
resource "cloudflare_device_posture_rule" "managed_device" {
account_id = var.account_id
name = "Managed Device Check"
type = "warp"
match {
platform = "mac"
}
input {
require_gateway = true
check_operator = "or"
}
}
Photo by Markus Spiske on Unsplash
Service-to-Service Authentication
// internal/auth/mtls.go
package auth
import (
"crypto/tls"
"crypto/x509"
"net/http"
"os"
)
// NewMTLSClient creates an HTTP client with mutual TLS
func NewMTLSClient(certFile, keyFile, caFile string) (*http.Client, error) {
// Load client certificate
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
// Load CA cert for server verification
caCert, err := os.ReadFile(caFile)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Configure TLS
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
MinVersion: tls.VersionTLS13,
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: tlsConfig,
},
}, nil
}
// VerifyServiceIdentity middleware validates client certificates
func VerifyServiceIdentity(allowedServices []string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || len(r.TLS.PeerCertificates) == 0 {
http.Error(w, "Client certificate required", http.StatusUnauthorized)
return
}
clientCert := r.TLS.PeerCertificates[0]
serviceID := clientCert.Subject.CommonName
// Check if service is allowed
allowed := false
for _, s := range allowedServices {
if s == serviceID {
allowed = true
break
}
}
if !allowed {
http.Error(w, "Service not authorized", http.StatusForbidden)
return
}
// Add service identity to context
ctx := context.WithValue(r.Context(), "service_id", serviceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}
SPIFFE/SPIRE for Workload Identity
# spire/server-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: spire-server
namespace: spire
data:
server.conf: |
server {
bind_address = "0.0.0.0"
bind_port = "8081"
trust_domain = "company.internal"
data_dir = "/run/spire/data"
ca_key_type = "rsa-4096"
default_x509_svid_ttl = "1h"
federation {
bundle_endpoint {
address = "0.0.0.0"
port = 8443
}
}
}
plugins {
DataStore "sql" {
plugin_data {
database_type = "postgres"
connection_string = "dbname=spire host=postgres user=spire"
}
}
NodeAttestor "k8s_psat" {
plugin_data {
clusters = {
"production" = {
service_account_allow_list = ["spire:spire-agent"]
}
}
}
}
KeyManager "disk" {
plugin_data {
keys_path = "/run/spire/data/keys.json"
}
}
}
Network Micro-Segmentation
Kubernetes Network Policies
# network-policies/database-isolation.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-isolation
namespace: production
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
- Egress
ingress:
# Only allow from API service
- from:
- podSelector:
matchLabels:
app: api-server
ports:
- protocol: TCP
port: 5432
egress:
# Only DNS and nothing else
- to:
- namespaceSelector:
matchLabels:
name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
Cilium for Advanced Policies
# cilium/api-to-database-policy.yaml
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: api-database-access
namespace: production
spec:
endpointSelector:
matchLabels:
app: postgres
ingress:
- fromEndpoints:
- matchLabels:
app: api-server
toPorts:
- ports:
- port: "5432"
protocol: TCP
rules:
l7proto: postgres
l7:
# Allow only SELECT, INSERT, UPDATE on specific tables
- action: "allow"
request: "/users"
- action: "allow"
request: "/orders"
Continuous Verification
Context-Aware Access Decisions
# access_decision.py
from dataclasses import dataclass
from typing import Optional
import httpx
@dataclass
class AccessContext:
user_id: str
device_id: str
ip_address: str
geo_location: str
time_of_access: str
resource_sensitivity: str
async def evaluate_access(ctx: AccessContext) -> tuple[bool, Optional[str]]:
"""Evaluate access based on multiple signals."""
risk_score = 0
# Check device posture
device_status = await check_device_posture(ctx.device_id)
if not device_status.is_compliant:
risk_score += 30
# Check for impossible travel
if await detect_impossible_travel(ctx.user_id, ctx.geo_location):
risk_score += 50
# Check for unusual access time
if await is_unusual_access_time(ctx.user_id, ctx.time_of_access):
risk_score += 20
# Check IP reputation
ip_reputation = await get_ip_reputation(ctx.ip_address)
if ip_reputation.is_suspicious:
risk_score += 40
# Decision based on resource sensitivity
thresholds = {
"low": 70,
"medium": 50,
"high": 30,
"critical": 10
}
threshold = thresholds.get(ctx.resource_sensitivity, 30)
if risk_score >= threshold:
# Require step-up authentication
return False, "step_up_required"
return True, None
async def check_device_posture(device_id: str):
"""Check if device meets security requirements."""
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://mdm.company.com/api/devices/{device_id}/posture"
)
data = response.json()
return DevicePosture(
is_compliant=data["compliant"],
os_version=data["os_version"],
encryption_enabled=data["disk_encrypted"],
antivirus_active=data["av_active"]
)
Monitoring & Detection
# falco/zero-trust-rules.yaml
- rule: Unexpected Outbound Connection
desc: Detect connections to unauthorized external services
condition: >
outbound and
not (fd.sip in (allowed_external_ips)) and
container
output: >
Unexpected outbound connection
(user=%user.name command=%proc.cmdline connection=%fd.name
container=%container.name image=%container.image.repository)
priority: WARNING
tags: [network, zero_trust]
- rule: Lateral Movement Attempt
desc: Detect attempts to access other services without proper identity
condition: >
inbound and
not fd.sport in (approved_service_ports) and
not proc.name in (approved_processes)
output: >
Potential lateral movement attempt
(source=%fd.cip dest=%fd.sip port=%fd.sport
container=%container.name)
priority: CRITICAL
tags: [network, lateral_movement, zero_trust]
Implementation Roadmap
| Phase | Duration | Focus |
|---|---|---|
| 1. Inventory | 2-4 weeks | Map all assets, data flows, identities |
| 2. Identity | 4-8 weeks | SSO, MFA, device management |
| 3. Access Proxy | 4-6 weeks | ZTNA for applications |
| 4. Micro-seg | 6-12 weeks | Network policies, service mesh |
| 5. Continuous | Ongoing | Monitoring, anomaly detection |
Quick Wins
- Enable MFA everywhere - Immediate impact, low effort
- Deploy access proxy - Cloudflare Access or Tailscale in a day
- Default-deny network policies - Start with new namespaces
- Audit existing access - Find and revoke stale permissions
Zero Trust isn’t a product—it’s a strategy. Start with identity, add verification layers, and assume you’re already compromised. Because statistically, you probably are.
이 글이 도움이 되셨다면 공감 및 광고 클릭을 부탁드립니다 :)
