SECURITY / DISTRIBUTED SYSTEMS
Zero Trust Microservices
MSc thesis at the University of Oslo. I threat-modelled a .NET 8 microservice system with STRIDE, PASTA and LINDDUN, implemented layered controls (Keycloak with OAuth 2.0/OIDC, HashiCorp Vault Transit, mTLS, RBAC and microsegmentation), and measured the overhead with k6, Prometheus and Grafana.
- MSc thesis, UiO
- p95 144 ms under k6 load
- 0% errors
- Context
- MSc thesis, University of Oslo
- Period
- 2024 – 2025
- Links
- microservice_dot_net
The base application is public. The thesis and hardening code are in a private repository; I can share them on request.
Stack
- .NET 8
- ASP.NET Core
- Keycloak
- OAuth 2.0 / OIDC
- HashiCorp Vault
- mTLS
- Docker
- Kubernetes
- SQLite
- Prometheus
- Grafana
- k6
Project overview
The system under study is a .NET 8 ordering application: an MVC store front-end and separate Auth, Product, Coupon, ShoppingCart, Order and Rewards APIs. I used it as a realistic target, mapped its threats, applied Zero Trust controls service by service, and then measured whether the system was still fast enough to use.
Problem
Splitting an application into services multiplies the network paths, tokens and secrets an attacker can abuse. The threat analysis highlighted three risks above the rest: unauthorised access, injection attacks, and lateral movement across service boundaries. Zero Trust answers these by verifying every request, but each check adds work. The thesis asks how far you can go before the cost is too high.
Requirements
- Centralised identity: no service trusts a request because of where it comes from.
- Every service authorises each call itself, based on token claims and roles.
- Sensitive data encrypted with keys the services never hold.
- Encrypted service-to-service traffic, with each workload confined to the smallest possible blast radius.
- Measured, not assumed: latency, throughput, errors and CPU under normal and peak load.
Architecture
Client
- Store (MVC)ASP.NET Core
Edge
- API gatewaysingle entry point
Services
- Auth API
- Product · Coupon
- ShoppingCart
- Order API
- Rewards API
Data
- Database per service
- Rewards catalogencrypted, SQLite
Trust plane
- KeycloakOAuth 2.0 / OIDC
- VaultTransit encryption
- Prometheus · Grafanametrics, alerts
Authentication flow
- 01User→StoreOpen protected page
- 02Store→KeycloakOIDC authorization code flow
- 03Keycloak→StoreID token + access token (roles, audience)
- 04Store→API gatewayRequest with Bearer token
- 05API gateway→Order APIForward; Order API validates signature, issuer, audience, expiry
- 06Rewards API→VaultTransit encrypt / decrypt; the key never leaves Vault
Authorisation combines roles (RBAC) with attributes (ABAC), and service-to-service traffic is encrypted with mTLS. During testing, invalid and tampered tokens were sent on purpose to confirm they were rejected without slowing valid traffic.
Secrets management
- The Rewards API stores its data encrypted through the HashiCorp Vault Transit secrets engine: it sends plaintext and gets ciphertext back, so the AES-256 key never reaches the application.
- Keys can be rotated inside Vault without redeploying the service.
- Most Transit encrypt and decrypt calls completed in under 2 ms, with a few encryptions reaching about 10 ms.
Threat model
| Method | Used for |
|---|---|
| STRIDE | Threats per component: MVC app, Auth API, the business APIs and the Rewards API |
| PASTA | Risk-centred analysis linking attack scenarios to business impact |
| LINDDUN | Privacy threats around user and order data |
| MITRE ATT&CK | Mapping attack vectors to known adversary techniques |
| Zero Trust | Design principle for the countermeasures: never trust, always verify |
Each threat was mapped to a countermeasure in a threat-to-solution matrix, then implemented system-wide (identity, encryption, segmentation) or per component.
Observability
- Every service exposes Prometheus metrics: http_request_duration_seconds and process_cpu_seconds_total.
- Grafana dashboards cover latency, request volume, CPU and errors such as invalid tokens and database failures.
- Alerts fire when p95 latency goes above 500 ms or CPU or memory goes above 80%.
Performance testing
Two k6 scripts drove the tests. loadtest.js simulated normal traffic: 20 virtual users ramped over two minutes. apigateway_test.js simulated spikes and attacks: up to 100 virtual users within 30 seconds, mixing authentication flows with invalid and tampered credentials.
- p95 latency
- 144 ms
- p90 70 ms, mean 27 ms
- requests / second
- ~99
- sustained through the test
- error rate
- 0%
- order, rewards and store endpoints
- mean latency, normal load
- < 2 ms
- 20 virtual users
k6 results reported in the thesis. The slowest request at peak load was 839 ms.
Key engineering decisions
- Keycloak as the single identity provider
- One place to issue, rotate and revoke tokens, using standard OAuth 2.0 and OIDC instead of custom auth code in every service.
- Vault Transit rather than encrypting inside each service
- Key material never leaves Vault. Rotating a key doesn't require redeploying services.
- Measure the cost, don't assume it
- Instrument first, then add controls, so any change in latency can be traced to a specific layer.
What I learned
- Zero Trust is mostly an identity and key-management problem. The network controls are the easier part.
- Under load, the cost of security shows up in the tail (p95 and max), not the average, so report percentiles.
- Threat modelling before writing controls kept the work focused on the risks that mattered for this system.