Zero Trust Progress: Use a Maturity Model, Not a Product Checklist
For developers and technical leads trying to assess zero trust progress without getting trapped in vendor feature matrices. This guide shows how to measure concrete control coverage, enforcement quality, and operational reality using a maturity model tied to request flows, identities, devices, services, and data.
TL;DR — Zero trust progress is not "we bought SSO, VPN replacement, and endpoint tooling"; it is "for this request path, which identities, devices, workloads, and policies were actually verified and enforced, and how consistently?" The most useful move is to score a few critical flows against maturity levels such as inventory, visibility, policy, enforcement, and continuous verification, then use those scores to drive engineering work instead of checking off products. Reading time: ~7 min
What it is and where it sits
A zero trust maturity model is a way to measure how far your system has moved from implicit trust to explicit, continuously evaluated trust. It sits above individual tools. Instead of asking "do we have product X?", you ask questions like:
- Can this request be tied to a human or workload identity?
- Is access decided per request, or only once at network entry?
- Are device posture, service identity, and data sensitivity part of the decision?
- Can you prove enforcement from logs, not architecture slides?
- Can you revoke access quickly without waiting for IP changes, VPN disconnects, or cert rotation windows?
In architecture terms, the maturity model spans the whole request/data path:
- user identity at the IdP
- device trust source, if you use one
- edge or gateway enforcing authn/authz
- service-to-service identity and policy inside the platform
- data plane controls at the DB, object store, queue, and admin interfaces
- logs/telemetry proving what happened
What it replaces is the old procurement-style checklist:
- SSO: yes/no
- MFA: yes/no
- VPN: yes/no
- EDR: yes/no
- service mesh: yes/no
That checklist is nearly useless for decision-making because two companies can both answer "yes" while one still has broad flat network access, shared service accounts, and no per-request authorization.
A maturity model belongs in design reviews, threat modeling, platform roadmaps, and quarterly engineering planning. It is not a GRC-only artifact.
View as text
[Developer Laptop] ---- 1. GET /admin ----> [Edge Proxy] [Developer Laptop] ---- 2. OIDC login ----> [IdP] [IdP] ---- 3. token/claims ----> [Edge Proxy] [Edge Proxy] ---- 4. identity headers/JWT ----> [Admin API] [Admin API] ---- 5. mTLS + authz ----> [Policy/Service Layer] [Policy/Service Layer] ---- 6. scoped query ----> [Postgres] [Edge Proxy] | v [Logs/Telemetry] [Admin API] | v [Logs/Telemetry] [Postgres] | v [Logs/Telemetry]
The maturity question is not whether those boxes exist. It is whether each numbered hop has explicit identity, policy, enforcement, and evidence.
How it actually works
Use one realistic end-to-end flow, score it, then improve the weakest step. Repeat for your highest-risk flows.
A practical maturity rubric
For each critical flow, score these dimensions from 0 to 4:
| Dimension | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Identity | shared/implicit | user known only at login edge | strong user/workload identity propagated | identity bound to every hop | continuous re-eval and rapid revocation |
| Device/Workload posture | none | inventory only | posture checked for some access | posture enforced on sensitive access | posture/risk feeds affect live decisions |
| Policy | manual allowlists | coarse role checks | resource/action-based policy | centralized policy with tests | adaptive policy with change control and metrics |
| Enforcement | network perimeter only | edge only | edge + app | edge + app + service-to-service + data layer | consistent enforcement with break-glass controls |
| Telemetry | sparse logs | auth logs only | request and decision logs | correlated end-to-end evidence | measurable coverage and drift detection |
You do not need a giant framework to start. A spreadsheet with one row per critical flow is enough if the rows are concrete.
End-to-end example: admin access to a customer export endpoint
Take this flow: an engineer accesses /admin/customers/123/export in an internal admin app.
Step 1: Identify the exact path and trust boundaries.
- Browser to edge proxy
- Edge proxy to IdP for login
- Edge proxy to admin API
- Admin API to export worker or service
- Export service to Postgres/object storage
Step 2: Observe the current behavior, not the intended one.
For example, inspect the edge behavior:
curl -I https://admin.example.com/customers/123/export
A weak setup often looks like this:
HTTP/2 200
server: nginx
content-type: text/html
content-length: 8421
That means the app is serving something directly without an auth redirect or challenge at the edge. Maybe the app does auth later, maybe not. You cannot tell from the perimeter.
A stronger setup usually returns a redirect or unauthorized response before the app is reached:
HTTP/2 302
location: https://idp.example.net/authorize?client_id=admin&redirect_uri=https%3A%2F%2Fadmin.example.com%2Fcallback&response_type=code&scope=openid%20profile%20email
cache-control: no-store
Or for API-style access:
HTTP/2 401
www-authenticate: Bearer realm="admin", error="invalid_token"
Step 3: Verify identity propagation into the app.
At the app or ingress, inspect headers or JWT claims actually reaching the service. If your reverse proxy is supposed to inject identity headers, log them explicitly. If the app only sees X-Forwarded-User on some routes and anonymous traffic on others, maturity is lower than the architecture diagram claims.
Step 4: Verify authorization is resource-specific.
A low-maturity implementation often does this:
- user is in
adminsgroup - therefore user can export any customer data
A better implementation checks action + resource + environment:
- user is in
support-admin - ticket is linked to customer 123
- export scope excludes payment data
- session age < 15 min
- device posture is compliant for export action
Step 5: Verify service-to-service and data-layer controls.
If the admin API can reach Postgres with a broad role like app_rw and export any table, your edge auth is not enough. Zero trust maturity rises when the export path uses a dedicated workload identity and a DB role scoped to the exact schema/tables needed.
Step 6: Verify evidence.
You want to answer: who exported what, from which device/workload, under which policy, and was it allowed or denied? If you cannot reconstruct that from logs in a few minutes, telemetry maturity is low.
Example scoring
Suppose the current state is:
- OIDC at the edge: yes
- MFA enforced by IdP: yes
- app trusts any request from office IPs if cookie exists: still yes
- service-to-service auth inside cluster: none
- DB role shared by all admin functions: yes
- audit logs contain login events but not export decisions: yes
Score it honestly:
| Dimension | Score | Why |
|---|---|---|
| Identity | 2 | user identity exists at edge and app, but not consistently across internal hops |
| Device/Workload posture | 1 | maybe inventory exists, but not enforced for this action |
| Policy | 2 | role-based checks exist, but not resource/action scoped enough |
| Enforcement | 1 | mostly edge/app only; internal services and DB still broad |
| Telemetry | 1 | login visible, authorization and data access not correlated |
That tells you what to do next far better than "buy service mesh" or "deploy CASB".
When to use it (and when not to)
Use a maturity model when you need to prioritize engineering changes across multiple systems and prove that risk is actually going down.
| Scenario | Recommendation |
|---|---|
| You have SSO/MFA already but incidents still involve overbroad access | Use a maturity model now; your gap is likely policy/enforcement depth, not missing products |
| You are planning platform work for internal apps, APIs, and data stores | Use it to score the top 5 flows and sequence roadmap work |
| You are in procurement comparing vendor claims | Use the maturity model as the evaluation rubric, not the vendor checklist |
| You are a 5-person startup with one app and no sensitive data beyond basics | You probably do not need a formal model yet; implement sane auth, least privilege, and audit logs first |
| You only want a compliance artifact for an audit | A lightweight maturity table is fine, but do not pretend it measures real enforcement unless you test flows |
| Your environment is mostly batch jobs and offline data movement | Still useful, but model workload identity and data access paths rather than browser login flows |
You probably do not need this if your current problem is more basic:
- no central identity provider
- no MFA for admin access
- shared production accounts
- production DB reachable from random laptops
- no audit logs at all
In that case, skip the maturity workshop and fix the obvious control failures first.
Trade-offs
Every zero trust maturity gain costs something.
- Better identity propagation costs application and proxy changes. You may need to standardize on JWT validation libraries, mTLS, or signed identity headers.
- Finer-grained policy reduces blast radius but increases policy authoring, testing, and debugging work.
- Service-to-service auth improves internal trust boundaries but adds certificate/token rotation, bootstrap, and outage modes.
- Device posture checks improve admin-path security but can lock out legitimate users during agent failures or OS mismatches.
- Rich telemetry improves forensics and measurement but increases storage cost, privacy review, and log pipeline complexity.
- Centralized policy engines reduce duplicated auth logic but can create latency, availability dependencies, and a new form of lock-in if you bake policy too deeply into one stack.
A useful rule: pair each benefit with an explicit operational owner. If nobody owns cert rotation, policy tests, or audit log retention, your maturity score is aspirational.
In practice
Example 1: edge enforcement with nginx auth_request
server {
listen 443 ssl http2;
server_name admin.example.com;
location = /_auth {
internal;
proxy_pass http://auth-gateway/verify;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
auth_request /_auth;
error_page 401 = @login;
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $email $upstream_http_x_auth_request_email;
proxy_set_header X-User $user;
proxy_set_header X-Email $email;
proxy_pass http://admin-api;
}
location @login {
return 302 https://idp.example.net/authorize?client_id=admin&redirect_uri=https://admin.example.com/callback&response_type=code&scope=openid%20email;
}
}
This enforces authentication before the request reaches admin-api and propagates identity headers. Gotcha: if the app also trusts client-supplied X-User from direct internal traffic, you have a header spoofing bug; strip these headers on all untrusted ingress points.
You can test the failure mode with:
curl -I https://admin.example.com/
Expected unauthenticated shape:
HTTP/2 302
location: https://idp.example.net/authorize?client_id=admin&redirect_uri=https://admin.example.com/callback&response_type=code&scope=openid%20email
If you instead get:
HTTP/2 500
server: nginx
check the auth subrequest upstream first; auth_request commonly fails when the auth service returns a body with unexpected status handling or is unreachable.
Example 2: Postgres role scoping for an export worker
⚠️ Changing database roles and privileges can break production paths immediately. Run these statements first in staging against a recent schema copy, and keep a session open as a superuser so you can restore grants if the app starts returning
permission denied for table ....
CREATE ROLE export_worker NOLOGIN;
GRANT CONNECT ON DATABASE appdb TO export_worker;
GRANT USAGE ON SCHEMA customer_export TO export_worker;
GRANT SELECT ON customer_export.export_view TO export_worker;
REVOKE ALL ON SCHEMA public FROM export_worker;
CREATE ROLE export_job_runtime LOGIN PASSWORD 'replace-me';
GRANT export_worker TO export_job_runtime;
This moves the export path away from a broad shared app role to a narrower DB role. Gotcha: GRANT SELECT ON ALL TABLES IN SCHEMA ... does not automatically cover future tables unless you also set ALTER DEFAULT PRIVILEGES; many teams think they locked this down and later drift back into manual grants.
Test with:
psql "host=db.example.net dbname=appdb user=export_job_runtime sslmode=require" -c "SELECT * FROM customer_export.export_view LIMIT 1;"
And verify denial on unrelated tables:
psql "host=db.example.net dbname=appdb user=export_job_runtime sslmode=require" -c "SELECT * FROM public.users LIMIT 1;"
Expected failure shape:
ERROR: permission denied for table users
That error is good news here: it proves the data-layer boundary is real.
Example 3: a simple maturity score file you can keep in git
flow: admin-customer-export
owner: platform-security
reviewed: 2026-10-30
steps:
- name: browser-to-edge
identity: 3
posture: 2
policy: 2
enforcement: 3
telemetry: 2
notes: "OIDC enforced at edge; device posture only for admin group"
- name: edge-to-admin-api
identity: 2
posture: 0
policy: 2
enforcement: 2
telemetry: 2
notes: "Identity headers injected by proxy; no signed header verification in app"
- name: admin-api-to-export-service
identity: 1
posture: 0
policy: 1
enforcement: 1
telemetry: 1
notes: "Shared Kubernetes service account"
- name: export-service-to-postgres
identity: 2
posture: 0
policy: 2
enforcement: 2
telemetry: 1
notes: "Dedicated DB role, but query audit not correlated to request ID"
next_actions:
- "Move admin-api and export-service to distinct workload identities"
- "Add request ID propagation into DB audit trail"
- "Require compliant device posture for export action"
This is intentionally boring. That is a feature. Put it in the repo next to the service, review it in PRs, and update it when the flow changes. Gotcha: if nobody ties these scores to actual tests or log queries, the file turns into architecture fiction.
Further reading
- NIST SP 800-207 Zero Trust Architecture
- CISA Zero Trust Maturity Model
- Google BeyondCorp papers
- OWASP ASVS, Access Control section
- PostgreSQL Documentation, Roles and Privileges chapter
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI