Skip to content

Repository files navigation

Project Wraith - Non-Human Identity Governance & Remediation

Deploy to Azure

A hands-on Azure/Entra ID lab simulating the discovery, risk classification, and remediation of 12 deliberately misconfigured non-human identities (NHIs) - service principals, app registrations, and a managed identity inside a fictional fintech tenant, Vantex.

This was built as part of my ongoing Azure IAM portfolio, following the same discovery → remediation → governance arc as my earlier projects (Citadel, Bedrock, Gatekeeper). I'm still building depth in PowerShell/Microsoft Graph SDK, so every change made here also has a documented portal-click path. I wanted to understand what was changing before I trusted a script to change it.

Why this project

Most NHI sprawl happens quietly, service principals get created for a one-off integration, nobody revisits the permissions, the original engineer leaves, and three years later there's a credential with tenant-wide write access that nobody remembers granting. Vantex's scenario simulates exactly that, and this repo documents how I found, scored, and fixed it.

The Scenario

Vantex is a fictional B2B payment infrastructure company. A SOC 2 renewal audit flags the absence of an NHI governance program. Human identities are mapped across The Office (US) cast to make the org chart easy to follow at a glance.

Tier Role
1 — Executive / Global Admin Michael Scott, Jan Levinson
2 — Security & Identity Ops Dwight Schrute, Oscar Martinez
3 — Business App Owners Jim, Pam, Angela, Kevin, Stanley, Phyllis
4 — Mystery Tier Creed Bratton

Phase 1 — Discovery & Inventory

Before fixing anything, I needed a complete picture of what existed. Deploy-ProblemState.ps1 provisions the baseline: 4 tier groups, 11 users, and 12 NHIs (VTX-01 through VTX-12), each with an intentional misconfiguration.

Initial NHI inventory snapshot

Full list of 12 NHIs in Entra ID

Resource group for VTX-06

A sample of what I found:

VTX-01 Application.ReadWrite.All

VTX-05 Directory.ReadWrite.All

VTX-10 over-scoped permissions

VTX-12 mystery tier permission

VTX-03 credential sprawl

VTX-04 orphaned identity

VTX-06 managed identity Owner RBAC

Vantex org chart mapped into Entra

What I actually did, in portal terms: Entra ID → App registrations → New registration (×12), API permissions → Add a permission → Grant admin consent, Certificates & secrets → New client secret, Owners → Add owners, and Subscription → Access control (IAM) → Add role assignment for the managed identity. The script automates this same sequence, it doesn't do anything the portal can't.


Phase 2 — Risk Classification

I scored each NHI across four dimensions; permission scope, credential hygiene, staleness, and ownership, and built a composite risk score by hand in Excel. This was deliberately the slowest phase, because I wanted to actually reason through why something was risky.

VTX-01 permission detail

VTX-03 secrets blade

VTX-04 ownership gap

Final risk-tiered inventory

Result: 5 High-tier, 6 Medium-tier, 1 Low-tier. No identity scored Critical, but the cluster sitting in High/Medium told me there wasn't one obvious fire, there was a pattern of under-governed sprawl across the whole estate, which is arguably the more realistic and more dangerous finding.

Full scoring data: wraith-risk-matrix.csv


Phase 3 — Least-Privilege Remediation

Starting with the highest-risk identities, I right-sized permissions to match actual function rather than convenience.

Identity Before After
VTX-12 RoleManagement.ReadWrite.Directory Revoked - no business justification existed
VTX-04 Orphaned, no owner Owned by Oscar Martinez
VTX-05 Directory.ReadWrite.All Group.Read.All
VTX-10 User+Directory.ReadWrite.All User.ReadWrite.All only
VTX-01 Application.ReadWrite.All Mail.Send
VTX-06 Subscription Owner Custom role, scoped to one resource group, VM start/stop only

VTX-12 revoked

VTX-04 owner assigned

VTX-05 revoked

VTX-05 narrowed permission

VTX-10 narrowed

VTX-01 narrowed

VTX-06 custom role

Honest note: the API permissions blade doesn't always show a newly-granted permission in the main list, it can show up under "Other permissions granted" instead, since that's reading from a different data source than the role assignment itself. I hit this with VTX-05 and verified the grant was actually live via PowerShell rather than assuming the UI was wrong. Worth knowing if you're following along.


Phase 4 — Credential Lifecycle & Rotation

VTX-02 rotated secret

VTX-03 consolidated

VTX-07 Key Vault migration

VTX-08 secret revoked

VTX-05 (vendor-controlled) couldn't be rotated from my side, that became a documented compensating control instead (see Phase 5).


Phase 5 — Conditional Access for Workload Identities

This phase taught me something I didn't expect: Conditional Access for workload identities only supports service principals, not managed identities. VTX-06 (a managed identity) literally cannot appear in the CA assignment picker, not a bug, a platform limitation. So VTX-06's risk is mitigated entirely through the Phase 3 scoping instead.

Named location

CA policy assignments

CA location condition

CA policy report-only

Enforcement for VTX-09/10/11 also depends on the Workload ID Premium add-on, which isn't currently licensed in this tenant, so this phase is documented as a designed, validated control rather than a live-enforced one. I think that's a more honest finding than pretending it's fully active.


Phase 6 — Governance Dashboard

An Azure Workbook pulling the full risk matrix, tier breakdown, and remediation status into one view.

Scores summary table

Risk count breakdown

Before/after remediation table

Dashboard workbook top

Dashboard workbook remediation


Deploy This Lab

The infra/azuredeploy.json template provisions the VTX-06 managed identity into a resource group of your choice. Click the badge at the top of this README, or run Deploy-ProblemState.ps1 for the full Entra-side environment (users, groups, app registrations).

Custom RBAC role creation and assignment is handled by the PowerShell script rather than the ARM template, that's a deliberate boundary, since a one-click deploy shouldn't request subscription-level role-definition permissions from someone who didn't write it.

What I'd do differently next time

  • Confirm exact display names (Entra appended the VTX-0X prefix automatically) before hardcoding filters in scripts, I lost time on this more than once.
  • Check the Az PowerShell module's installed version before assuming hashtable syntax for New-AzRoleDefinition , it changed between versions, mine needed the Permissions object structure instead of flat Actions.
  • Build the Key Vault RBAC role assignment for myself before trying to write the first secret, not after the first 403 error.

Lessons Learned

Midway through the build, my local Az.Accounts module hit a broken Azure.Identity.Broker conflict that no amount of execution-policy fixing solved. I ended up finishing the deployment from Azure Cloud Shell instead, which sidesteps local module conflicts entirely. I'm including this because real environments break in exactly this way, and knowing how to route around a broken local toolchain felt like as real a skill as anything else in this project.

Tech Stack

Microsoft Graph PowerShell SDK · Az PowerShell · Entra ID (App registrations, Conditional Access, Identity Governance) · Azure Key Vault · Azure RBAC (built-in + custom roles) · Azure Workbooks · ARM templates

Certification Alignment

This project maps to SC-500 (replacing AZ-500, August 2026 retirement), covering NHI/workload identity governance, application access management, and Conditional Access for workload identities.

About

Entra ID NHI governance lab - discovered, risk-scored, and remediated 12 misconfigured service principals and managed identities across a fictional fintech tenant (Vantex).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages