Zero trust architecture means no request is trusted because of where it comes from. Every access to a resource is authenticated, authorized against current identity and device signals, and logged, whether it starts inside the VPC or on hotel Wi-Fi. NIST SP 800-207 models this as a policy decision point (a policy engine plus a policy administrator) and policy enforcement points in front of every resource. You get there in a fixed order: strong identity, then device posture, then an identity-aware proxy in place of the VPN, then service identity and default-deny networking.
Key takeaways
- Zero trust removes network location as an input to authorization. Being on the corporate network or inside the VPC grants nothing by itself.
- Identity is the foundation. A single identity provider and phishing-resistant MFA come before any network work.
- Put authorization in one policy engine, as code, with tests. Logic scattered across services cannot be reviewed or audited.
- Make elevated access expire on its own. Least privilege fails because permissions accumulate, not because they are granted.
- Observe before you enforce. A default-deny policy written from the architecture diagram will block traffic the diagram never mentioned.
- Design the break-glass path and the policy engine's failure mode up front. Both get tested during your worst incident.
What zero trust is, and what it is not
| Perimeter model | Zero trust | |
|---|---|---|
| Trust basis | Network location: inside the firewall or on the VPN | Identity of the user or workload, device state, request context |
| Access granularity | Network segments | Individual applications and resources |
| Authentication | Once, at the VPN or login | Every request or session, re-evaluated as signals change |
| Lateral movement after a compromise | Easy within the segment | Limited to what that identity may reach |
| Typical access path | VPN into a flat network | Identity-aware proxy per application |
| Service to service | Trusted if on the internal network | Mutually authenticated with workload identity |
Zero trust is not a product. Vendors sell components (identity providers, access proxies, device management, service meshes), and none of them is zero trust on its own. It is not a one-off project either. It is a set of properties you can test, and testing them is how you measure progress.
The reference model: NIST SP 800-207
NIST SP 800-207 (2020) is the reference most frameworks build on. Its core components map onto tools you can deploy:
| NIST component | Role | Typical implementation |
|---|---|---|
| Policy engine (PE) | Decides whether a request is allowed, from policy and signals | Open Policy Agent, Cedar, or the policy layer of your access proxy |
| Policy administrator (PA) | Opens or closes the session based on the decision | The access proxy or identity provider issuing short-lived credentials |
| Policy enforcement point (PEP) | Sits in front of the resource and enforces the decision | Identity-aware proxy, API gateway, service mesh sidecar, admission controller |
| Signal sources | Feed the decision | Identity provider, device management and EDR, threat intelligence, activity logs |
For a user reaching an internal application, the flow looks like this:
For a maturity yardstick, CISA's Zero Trust Maturity Model (version 2.0, 2023) scores five pillars (identity, devices, networks, applications and workloads, data) across four stages: traditional, initial, advanced and optimal. It is written for US federal agencies, but it works as a self-assessment for anyone.
The rollout order that works
Order matters because each layer depends on the one before. Device posture means nothing without trustworthy identity, and per-application access needs both.
1. Identity first
- One identity provider for every human, federated to every application. No local accounts where SSO is possible.
- Phishing-resistant MFA: passkeys or FIDO2 security keys. One-time codes and push approvals can be phished or fatigued; treat them as a fallback, not the target.
- Automated lifecycle: joiners and leavers driven from the HR system through SCIM, so a departure revokes access everywhere at once.
- No shared accounts. Every action must be attributable to one person or one workload.
Our identity and access management guide covers this layer in depth.
2. Device posture
The policy needs to know whether a request comes from a device you manage. Enroll devices in MDM, issue each one a device certificate, and feed posture to the policy engine: disk encryption on, OS within supported versions, EDR running, screen lock set. Evaluate posture continuously, not only at login. A laptop that was compliant at 9am can have its EDR agent stopped by 10am.
3. Replace the VPN with an identity-aware proxy
A VPN authenticates once and then grants a network. An identity-aware proxy authenticates every request and grants one application. Google's BeyondCorp papers popularized the pattern. Today it is available as managed services (Google Cloud IAP, Cloudflare Access and similar) and as self-hosted software (Pomerium, or Teleport for infrastructure access).
Migrate one application at a time. Put the proxy in front of an internal tool, make it the only way in, and keep the VPN for whatever has not moved yet. Set a date for switching the VPN off, or it will outlive the program.
4. Policy as code
Put decisions in one policy engine that receives every signal as structured input and returns allow or deny. Open Policy Agent is a common choice. The policy below, in Rego v1 syntax, lets platform engineers reach production data only from a managed, encrypted device, with MFA in the last hour and an approved change ticket. The second rule is the break-glass path: it is allowed only with an incident ID, and like every decision it lands in OPA's decision log.
package authz
default allow := false
mfa_max_age_ns := 3600 * 1000000000 # one hour
allow if {
"platform-engineering" in input.subject.groups
input.resource.classification == "production-data"
input.device.managed
input.device.disk_encrypted
time.now_ns() - input.auth.mfa_time_ns < mfa_max_age_ns
input.request.change_ticket != ""
}
allow if {
"incident-commander" in input.subject.groups
input.request.break_glass
startswith(input.request.incident_id, "INC-")
}Policies are code, so they get tests and run in CI with opa test . -v:
package authz_test
import data.authz
engineer := {
"subject": {"groups": ["platform-engineering"]},
"resource": {"classification": "production-data"},
"device": {"managed": true, "disk_encrypted": true},
"auth": {"mfa_time_ns": time.now_ns()},
"request": {"change_ticket": "CHG-1042"},
}
test_managed_device_allowed if {
authz.allow with input as engineer
}
test_unmanaged_device_denied if {
not authz.allow with input as object.union(engineer, {"device": {"managed": false, "disk_encrypted": true}})
}Writing the break-glass path into the policy is deliberate. If the only way to work an incident is to disable the policy engine, it will be disabled during an incident and often left off afterwards.
5. Least privilege that expires
Least privilege fails for a mundane reason: permissions accumulate and nothing removes them. Make expiry the default rather than an audit finding. In AWS, a condition on aws:CurrentTime makes a grant stop working on its own, even if nobody remembers to remove it:
resource "time_offset" "debug_expiry" {
offset_hours = 4
}
data "aws_iam_policy_document" "prod_debug" {
statement {
actions = ["logs:GetLogEvents", "logs:FilterLogEvents"]
resources = ["${aws_cloudwatch_log_group.api.arn}:*"]
condition {
test = "DateLessThan"
variable = "aws:CurrentTime"
values = [time_offset.debug_expiry.rfc3339]
}
}
}time_offset is computed when the resource is created and stays fixed afterwards, so later applies do not quietly extend the grant. For humans, use just-in-time elevation (Azure PIM, a temporary elevated access workflow on AWS IAM Identity Center, or an access broker such as Teleport) so elevated sessions expire within hours and every activation is logged with a reason.
6. Service identity and mutual TLS
Services calling services need identity too, and an IP address is not an identity. Workload identity standards such as SPIFFE give each workload a verifiable ID. A service mesh such as Istio issues certificates for those IDs and enforces mutual TLS, so a compromised pod cannot impersonate another service even with full network access:
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: payments
spec:
mtls:
mode: STRICT
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: ledger-callers
namespace: payments
spec:
selector:
matchLabels:
app: ledger
action: ALLOW
rules:
- from:
- source:
principals: ["cluster.local/ns/payments/sa/payments-api"]
to:
- operation:
methods: ["POST"]
paths: ["/v1/entries"]Once an ALLOW policy selects a workload, any request it does not match is denied. Meshes usually start in PERMISSIVE mode so plaintext clients keep working during migration; the job is not done until the mode is STRICT. Our service mesh guide covers that rollout.
7. Microsegmentation with default deny
Default-deny networking limits lateral movement when everything else has failed. In Kubernetes it takes a few policies, including the one exception everyone forgets:
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-dns
namespace: payments
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-egress-to-ledger
namespace: payments
spec:
podSelector:
matchLabels:
app: payments-api
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchLabels:
app: ledger
ports:
- port: 8443
protocol: TCP
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ledger-ingress-from-api
namespace: payments
spec:
podSelector:
matchLabels:
app: ledger
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: payments-api
ports:
- port: 8443
protocol: TCPWith egress denied by default, every allowed flow needs two rules: egress on the sender and ingress on the receiver. Forgetting the sender side is the usual reason a "correct" ingress policy still times out.
Apply deny-all to a namespace that never had it and things will break. That is the control working: every connection it interrupts was an undocumented dependency. Observe first. Run your CNI in audit mode where it has one (Cilium's policy audit mode with Hubble flow logs, for example), collect real flows for a week or two, and write the allow list from evidence rather than from the architecture diagram. Our Kubernetes security guide covers the cluster hardening this sits on.
Data and monitoring
Log every decision with the inputs that produced it. OPA decision logs, proxy access logs and mesh telemetry together answer what an investigation will ask: who reached what, from which device, under which policy version. Ship them to your SIEM and alert on spikes in denials, on any break-glass use, and on access from devices that have just fallen out of compliance.
Classify the data the policies protect. A policy can only say "production data requires a managed device" if resources carry a classification the policy can read.
How to tell it is actually working
Zero trust is easy to claim and hard to evidence. These tests separate a deployed model from a described one:
- Can an engineer reach production data from an unmanaged laptop? Try it.
- If one pod is compromised, what else can it reach on the network? Test it from inside the pod rather than reasoning about it.
- How long do elevated permissions last, and what removes them?
- When someone leaves, how many systems need manual revocation?
- Can you list, for a given record, every identity that read it last quarter?
The last one is usually the hardest, and it is the one auditors ask. These controls map directly onto SOC 2's logical access criteria and ISO 27001's access control, authentication and network segregation controls, so audit evidence becomes a by-product of the model rather than a quarterly scramble.
What goes wrong in production
- The policy engine becomes a single point of failure. If every request waits on a central decision service, its outage is your outage. Run OPA as a sidecar or local daemon next to the enforcement point, distribute policy as bundles, and decide explicitly whether each path fails open or closed. Production data fails closed; the status page does not.
- Break-glass that means turning security off. If the emergency path is disabling the proxy, the proxy will be disabled at 3am and forgotten.
- Default-deny egress breaks DNS. This is the most common first-day outage: every pod in the namespace stops resolving names. Ship the DNS allow policy together with the deny policy, every time.
- Long sessions quietly restore implicit trust. A twelve-hour session token issued at login is a perimeter with a timer. Keep sessions short and re-evaluate posture during them.
- Posture checked only at login. A device that drifts out of compliance keeps its session. Feed posture changes into session revocation.
- Service accounts left out. Humans get passkeys and device checks while a CI service account keeps standing admin rights and a static key. Attackers notice which identities the program skipped.
- PERMISSIVE forever. A mesh rolled out in permissive mode to avoid breakage, and never switched to strict, gives you encryption on some paths and enforcement on none.
- The VPN that never dies. The proxy is live, but the VPN stays for "a few legacy apps", and with it a flat network path around every control you built.
Frequently Asked Questions
What is zero trust architecture?
Zero trust architecture is a security model in which no user, device or workload is trusted because of its network location. Every request to a resource is authenticated and authorized using current identity, device and context signals, and access is granted per resource with least privilege. NIST SP 800-207 is the standard reference for the model.
What are the core principles of zero trust?
The core principles are to verify explicitly on every request, to grant least-privilege access that expires, and to assume breach by limiting what any compromised identity or workload can reach. In practice that means strong identity, device posture checks, per-application access, service identity and default-deny networking. Every decision is logged so it can be reviewed later.
Does zero trust replace the VPN?
For application access in a mature deployment, yes. An identity-aware proxy authenticates every request to each application instead of granting a whole network after one login. Most organizations migrate application by application and keep the VPN for a shrinking set of legacy systems, which is why it pays to set a retirement date.
What is NIST SP 800-207?
NIST Special Publication 800-207, published in 2020, defines zero trust architecture for US federal agencies and has become the general industry reference. It describes a policy engine and policy administrator that make access decisions, and policy enforcement points that sit in front of each resource and apply them. It is vendor-neutral and describes deployment models rather than products.
How long does it take to implement zero trust?
Zero trust is a sequence of capabilities rather than a project with an end date. Identity and phishing-resistant MFA can land quickly, while replacing a VPN and segmenting service-to-service traffic take longer because each application and dependency has to be migrated and observed. Measure progress with testable properties, such as whether an unmanaged device can reach production data.
Need help with zero trust?
We design and run production systems with these controls built in, and we stay on the pager for what we build. That makes us care about the parts that fail at 3am: policy engines that degrade gracefully, break-glass paths that leave a trail, and segmentation written from real traffic rather than diagrams.