AlgoInsight

Free lesson · System Design Advanced

Zero-Trust Architecture

Hard10 min readsystem-designadvanced

Picture a medieval castle. Thick walls, a moat, one drawbridge. If you're inside the walls, you're trusted. If you're outside, you're not. For decades, enterprise networks worked the same way - a corporate firewall was the moat, a VPN was the drawbridge, and once you were "inside the network," you could reach anything.

Then in 2013, an HVAC contractor's stolen credentials gave attackers access to Target's internal network. They moved laterally from the contractor's segment to the payment processing systems and stole 40 million credit card numbers. The attackers were "inside the castle." The castle model failed.

Zero-trust architecture starts from a different assumption: there is no inside. Every request, from every user, from every device, from every network, is untrusted until proven otherwise. Your engineer sitting in the office on the corporate Wi-Fi is treated with the same suspicion as a request arriving from an unknown IP in another country. Trust is not a property of the network location - it's verified on every single request.

The Old Model vs Zero-Trust

graph TD
    subgraph "Perimeter Security (Castle-and-Moat)"
        OUTSIDE["Internet<br/>(untrusted)"]
        OUTSIDE --> FIREWALL["Firewall / VPN<br/>──────────────<br/>One verification point<br/>Check credentials ONCE<br/>at the gate"]
        FIREWALL --> INSIDE["Corporate Network<br/>(trusted)<br/>──────────────<br/>Once inside, free movement<br/><br/>Developer laptop → Production DB ✓<br/>HR system → Payment system ✓<br/>Contractor VPN → Source code ✓<br/><br/>Everything trusts everything<br/>because it's 'inside'"]
    end
    
    subgraph "Zero-Trust"
        ANY["Any request<br/>(from anywhere)"]
        ANY --> VERIFY["Verify EVERY request<br/>──────────────<br/>Who are you? (identity)<br/>What device? (device health)<br/>Where from? (context)<br/>What access? (least privilege)<br/>Is it encrypted? (always)"]
        VERIFY --> RESOURCE["Specific resource<br/>──────────────<br/>Access to ONLY the requested<br/>resource, nothing else<br/><br/>Developer → staging DB only ✓<br/>HR system → HR data only ✓<br/>Contractor → their project only ✓<br/><br/>No lateral movement possible"]
    end
This diagram compares perimeter security and zero-trust models. In the castle-and-moat model, a firewall or VPN checks credentials once at the gate. Once inside the corporate network, everything trusts everything - a developer laptop can reach the production database, and a contractor VPN can access source code. In zero-trust, every request from anywhere is verified - checking identity, device health, context, least privilege, and encryption. Access is granted only to the specific requested resource with no lateral movement possible.

The Five Principles of Zero-Trust

graph TD
    subgraph "Zero-Trust Principles"
        P1["1. Never Trust, Always Verify<br/>──────────────────<br/>Every request is authenticated<br/>and authorised, regardless of<br/>network location<br/><br/>A request from the office<br/>is treated identically to<br/>a request from a coffee shop"]
        
        P2["2. Least Privilege Access<br/>──────────────────<br/>Grant the minimum permissions<br/>needed for the specific task<br/><br/>Not 'developer can access<br/>all databases' but<br/>'this developer can read<br/>this table for this session'"]
        
        P3["3. Assume Breach<br/>──────────────────<br/>Design as if attackers are<br/>already inside your network<br/><br/>Segment everything<br/>Encrypt everything<br/>Log everything<br/>Detect lateral movement"]
        
        P4["4. Verify Explicitly<br/>──────────────────<br/>Use all available signals<br/>to verify: identity + device<br/>+ location + time + behaviour<br/><br/>A valid password from an<br/>unknown device at 3 AM<br/>from a new country gets<br/>extra scrutiny"]
        
        P5["5. Micro-Segmentation<br/>──────────────────<br/>Every workload, service, and<br/>data store is its own security<br/>perimeter<br/><br/>Service A can talk to Service B<br/>but NOT Service C, even though<br/>they run on the same cluster"]
    end
This diagram shows the five zero-trust principles. First, never trust, always verify - authenticate every request regardless of network location. Second, least privilege - grant minimum permissions for the specific task. Third, assume breach - design as if attackers are already inside, segment and encrypt everything. Fourth, verify explicitly - use all available signals including identity, device, location, time, and behaviour. Fifth, micro-segmentation - every workload is its own security perimeter with explicit access rules.

Implementation Patterns

Pattern 1: Identity-Based Access (BeyondCorp Model)

Google pioneered this after the 2009 Operation Aurora attack. Their internal systems now use identity, not network location, as the access control primitive.

graph TD
    USER["Employee request<br/>(from any network)"]
    
    USER --> IDP["Identity Provider<br/>(Okta, Azure AD, Google Workspace)<br/>──────────────────<br/>Authenticate: who are you?<br/>MFA required"]
    
    IDP --> DEVICE["Device Trust Check<br/>──────────────────<br/>Is this a managed device?<br/>Is the OS patched?<br/>Is disk encryption on?<br/>Is the endpoint agent running?"]
    
    DEVICE --> POLICY["Policy Engine<br/>──────────────────<br/>Evaluate: given this identity,<br/>this device state, this time,<br/>this location - is this access<br/>allowed?<br/><br/>Policy: 'Engineers can access<br/>staging during business hours<br/>from managed devices only'"]
    
    POLICY -->|"Allowed"| PROXY["Access Proxy<br/>(BeyondCorp / Cloudflare Access /<br/>Zscaler ZPA)<br/>──────────────────<br/>Terminate TLS<br/>Inject identity headers<br/>Forward to internal app"]
    
    PROXY --> APP["Internal Application<br/>──────────────────<br/>Receives request with verified<br/>identity - no VPN needed<br/><br/>Application itself enforces<br/>authorisation (RBAC / ABAC)"]
    
    POLICY -->|"Denied"| BLOCK["Access Denied<br/>──────────────────<br/>Log the attempt<br/>Alert if suspicious pattern"]
This diagram shows the BeyondCorp zero-trust pattern. An employee's request from any network hits the identity provider for authentication with MFA. A device trust check verifies the device is managed, patched, encrypted, and running endpoint agents. A policy engine evaluates whether access is allowed given the identity, device state, time, and location. If allowed, an access proxy forwards the request to the internal application with verified identity headers. If denied, the attempt is logged and suspicious patterns trigger alerts. No VPN is needed.

Pattern 2: Service-to-Service Zero-Trust

graph TD
    subgraph "Microservice Zero-Trust"
        SVC_A["Service A<br/>(order-service)"]
        
        SVC_A --> MTLS["mTLS Connection<br/>──────────────────<br/>Both sides present certificates<br/>Service A proves it IS order-service<br/>Service B proves it IS payment-service<br/><br/>Network identity verified<br/>cryptographically"]
        
        MTLS --> AUTHZ["Authorisation Policy<br/>──────────────────<br/>Even with verified identity:<br/>IS order-service ALLOWED to<br/>call payment-service?<br/><br/>Policy: order-service → payment-service ✓<br/>Policy: order-service → HR-service ✗<br/><br/>Enforced by service mesh sidecar<br/>or API gateway"]
        
        AUTHZ --> SVC_B["Service B<br/>(payment-service)<br/>──────────────────<br/>Receives verified request<br/>from known, authorised caller"]
    end
This diagram shows service-to-service zero-trust. Service A connects to Service B via mTLS where both sides present certificates proving their identity cryptographically. An authorisation policy then checks whether the calling service is allowed to call the destination service - order-service can call payment-service but not HR-service. This is enforced by service mesh sidecars or API gateways. The destination receives a request from a known, cryptographically verified, authorised caller.

Pattern 3: Micro-Segmentation with Network Policies

graph TD
    subgraph "Kubernetes Network Policies"
        NS_ORDER["Namespace: orders<br/>──────────────────<br/>order-service pods"]
        
        NS_PAY["Namespace: payments<br/>──────────────────<br/>payment-service pods"]
        
        NS_USER["Namespace: users<br/>──────────────────<br/>user-service pods"]
        
        NS_DB["Namespace: databases<br/>──────────────────<br/>PostgreSQL pods"]
        
        NS_ORDER -->|"Allowed by<br/>NetworkPolicy"| NS_PAY
        NS_ORDER -->|"Allowed"| NS_DB
        NS_ORDER -->|"❌ DENIED by<br/>NetworkPolicy"| NS_USER
        
        NS_PAY -->|"Allowed"| NS_DB
        NS_PAY -->|"❌ DENIED"| NS_USER
        
        NS_USER -->|"Allowed"| NS_DB
        NS_USER -->|"❌ DENIED"| NS_PAY
    end
This diagram shows micro-segmentation using Kubernetes NetworkPolicies. The orders namespace can communicate with the payments namespace and the databases namespace but is denied access to the users namespace. The payments namespace can access databases but not users. The users namespace can access databases but not payments. Each namespace is its own security segment with explicit allow rules - all other traffic is denied by default.

Important: Default deny is the foundation of micro-segmentation. In Kubernetes, without NetworkPolicies, all pods can talk to all other pods - it's a flat network. The first step toward zero-trust is a default-deny policy on every namespace, then explicitly allowing only the communication paths your architecture requires. This means you need a complete service dependency map before you can implement micro-segmentation - which is why most organisations implement service mesh (which gives them the dependency map as a side effect) before micro-segmentation.

Zero-Trust Architecture Components

Component Purpose Examples
Identity Provider (IdP) Central authentication for users Okta, Azure AD, Google Workspace
Device Trust Verify device health before granting access CrowdStrike, Jamf, Intune
Access Proxy Broker access to internal apps without VPN Cloudflare Access, Zscaler ZPA, BeyondCorp Enterprise
Policy Engine Evaluate access decisions based on multiple signals OPA (Open Policy Agent), Cedar (AWS)
Service Mesh Enforce mTLS and authorisation between services Istio, Linkerd, Cilium
SIEM / XDR Detect anomalous access patterns Splunk, CrowdStrike Falcon, Microsoft Sentinel
Secrets Manager Manage and rotate credentials Vault, AWS Secrets Manager, GCP Secret Manager

Tip: Zero-trust is a journey, not a deployment. No organisation goes from VPN-based access to full zero-trust in one project. The typical progression: (1) Deploy an identity provider with MFA. (2) Move one internal app behind an access proxy. (3) Add device trust checks. (4) Implement service mesh with mTLS for service-to-service communication. (5) Add network policies for micro-segmentation. (6) Build the policy engine that ties signals together. Each step adds security. You don't need all six to start getting value.

The Practical Challenges

Zero-trust sounds clean in architecture diagrams. In practice, three things make it hard.

Legacy applications don't support modern identity. A 15-year-old internal Java app that reads credentials from a config file can't participate in mTLS or OIDC-based authentication. You either wrap it behind a reverse proxy that handles authentication on its behalf, or you accept that the legacy app is a trust boundary gap. Most organisations have dozens of these.

Policy management gets complex at scale. With 500 microservices and fine-grained authorisation policies, you quickly have thousands of policy rules. Who reviews them? How do you audit them? How do you test that a policy change doesn't break a critical service? The policy-as-code movement (OPA, Cedar) helps - you can version, review, and test policies in CI/CD - but the governance overhead is real.

User experience can degrade. Continuous verification means more authentication prompts, more device checks, more context evaluations. If the system challenges a user every time they switch tools, productivity suffers. The art is setting verification frequency high enough for security but low enough for usability - session tokens, risk-based step-up authentication, and device trust caching all help.

Interview: "How would you design the authentication and authorisation architecture for a microservices system?" The strong answer goes beyond "use OAuth": "I'd implement zero-trust at two layers. For user-to-service traffic, an API gateway validates JWTs issued by our identity provider, enforces MFA, and checks device trust posture. For service-to-service traffic, a service mesh enforces mTLS - every service has a cryptographic identity via SPIFFE, certificates rotate automatically every 24 hours, and authorisation policies define which services can call which endpoints. Network policies provide defense-in-depth at the infrastructure layer - even if mTLS is compromised, the network won't route traffic between services that shouldn't communicate. This gives us identity-based security instead of perimeter-based security - no VPN needed, and a compromised service can only reach what it's explicitly authorised to reach."

The Production War Story

At Amazon, we migrated an internal tooling platform from VPN-based access to zero-trust. The platform had 15,000 monthly active users - engineers, support staff, and operations teams - accessing 200 internal tools through a corporate VPN.

The VPN was the bottleneck. During COVID, 15,000 remote users saturated the VPN concentrators. Latency spiked, connections dropped, and productivity suffered. The security team also flagged a risk: once on the VPN, any employee could reach any internal tool, regardless of whether they needed it. An engineer with access to a CI/CD dashboard could also reach the HR compensation tool. Least privilege was not enforced - the VPN was a binary gate.

We replaced the VPN with an access proxy (similar to BeyondCorp). Each internal tool registered with the proxy and defined an access policy: who can access it, from what device state, at what times. Authentication went through the corporate IdP with hardware security key MFA. Device trust checked that the laptop was managed, encrypted, and had the endpoint agent running.

The migration took 8 months. The hardest part wasn't the technology - it was the policy definition. For 200 tools, someone had to define who should have access. Many tools had no documented access requirements - they'd been "on the network" and that was the access control. We ran a 60-day observation period, logged who accessed what, and generated draft policies from actual usage patterns. Tool owners reviewed and approved.

After migration: VPN was decommissioned. Zero-trust proxy handled 15,000 users without capacity issues (it's cloud-based, not bottlenecked through a hardware concentrator). Access was least-privilege by default. And the security team could see, in real time, who accessed what tool from what device - something the VPN never provided.

Six Takeaways

  1. Zero-trust means "never trust, always verify." Network location is not a trust signal. Every request - from the office, from home, from a coffee shop - is authenticated, authorised, and encrypted identically.

  2. Least privilege replaces network-wide access. Instead of "on the VPN = access to everything," zero-trust grants access to specific resources based on identity, device, context, and explicit policy.

  3. Assume breach is a design principle, not pessimism. If attackers are already inside (and statistically, they are - the average dwell time before detection is 21 days), then segmentation, encryption, and detection are your controls, not the perimeter.

  4. Micro-segmentation limits blast radius. Kubernetes NetworkPolicies, service mesh authorisation, and firewall rules restrict lateral movement. Default deny + explicit allow is the foundation.

  5. The access proxy replaces the VPN. Cloud-based access proxies (Cloudflare Access, Zscaler ZPA) eliminate the VPN bottleneck while adding identity-aware, policy-based access control to every internal application.

  6. Zero-trust is incremental. Start with MFA and an access proxy for one application. Add device trust. Add service mesh. Add micro-segmentation. Each step improves security. Don't wait for the perfect architecture before starting.

Exercises

Exercise: Starter: Your company has 500 employees accessing 20 internal tools through a corporate VPN. (a) List three security problems with VPN-based access. (b) An attacker steals one engineer's VPN credentials. What can they access? How does zero-trust limit the damage? (c) You move one tool - the internal wiki - behind Cloudflare Access. What changes for users? What changes for attackers? (d) Define the access policy for the wiki: who should have access, what device requirements, what additional verification for sensitive pages?

Exercise: Intermediate: Design the zero-trust architecture for a SaaS company with 200 employees, 50 microservices on Kubernetes, and 10 internal tools. (a) Choose the identity provider, access proxy, and policy engine. Justify each choice. (b) Define the network segmentation: which services can communicate with which? Draw the allowed communication paths. (c) An engineer needs to debug a production issue at 2 AM from their personal laptop. Under zero-trust, what access do they get? What if their personal laptop doesn't pass device trust? Design the fallback. (d) A contractor needs temporary access to one internal tool for a 3-month engagement. Design the access lifecycle: onboarding, access scope, monitoring, offboarding.

Exercise: Advanced: Design the zero-trust architecture for a healthcare platform handling patient data (HIPAA-regulated). (a) Doctors access patient records from hospital workstations, personal phones, and home computers. Each context has different risk. Design the adaptive access policy: what access level for each context? How does device trust differ? (b) An emergency room doctor needs immediate access to any patient's records - even patients not assigned to them. How does the policy engine handle emergency override while maintaining an audit trail? (c) Third-party lab systems send results to your platform via API. Design the service-to-service zero-trust: how does the lab system authenticate, what access does it get, and how do you detect a compromised lab API key? (d) An insider threat: a system administrator with elevated access exfiltrates patient data. What zero-trust controls detect this? Design the anomaly detection for privileged access abuse.

Back to System Design Advanced