Managed Identity Classification in Platform Identity Vocabulary
Status: Draft — pending team discussion Date: 2026-02-24 (updated 2026-02-25 with peer review findings) Author: TBD
Executive Summary
What is the problem?
Managed identities are platform-managed credentials used by cloud workloads (Azure Function Apps, VMs, AI services) to authenticate without secrets. They are a growing share of non-human identities in enterprise environments. Our platform currently classifies them inconsistently — one connector labels them as "service principal," another as "machine account" — which can produce confusing findings and makes it harder for analysts to filter and prioritize.
Why does it matter?
- Misleading findings: Rules designed for secret-based service principals (credential rotation, orphaned ownership) fire on managed identities where they don't apply — the cloud platform manages the credentials automatically.
- Analyst confusion: When reviewing findings, a CISO or SOC analyst sees "service principal" and assumes there's a secret to rotate or an owner to track down. Managed identities have neither.
- Incomplete coverage: User-assigned managed identities can be shared across many workloads, creating a larger blast radius than typical service principals. Without proper classification, this risk is invisible.
Our recommendation
Standardize first, don't add complexity yet.
- Now: Align both connectors to use the same label (
machine_account) with a property flag indicating managed identity type (system vs user-assigned). No platform schema changes needed. - Next: Add a simple helper in the platform so evaluator rules can check "is this a managed identity?" without string-matching properties.
- Later: Revisit adding a dedicated identity subtype only if rule logic or analyst workflows require it after the standardization is done.
This approach is zero-risk (no schema migration, no data re-ingestion) and can be done independently of the authority paths work currently in progress.
Does this block anything?
No. The authority paths plan for function key-authenticated scheduled jobs proceeds independently regardless of which option is chosen here.
Technical Detail
Context
The platform's canonical identity subtype vocabulary is defined in sv0-platform/src/domain/graph/identity-subtypes.ts as the IDENTITY_SUBTYPES array. This vocabulary determines how connectors classify non-human identities when emitting NormalizedGraph payloads, and how the evaluator and UI categorize entities downstream.
Currently, managed_identity is not a member of IDENTITY_SUBTYPES. When connectors resolve managed identities via the ARM API (for example, the system-assigned managed identity on a Function App), there is no dedicated subtype. Two different workarounds are in use:
Entra-ServiceNow connector (function-key path, transformer.py):
{
"nodeType": "identity",
"properties": {
"identitySubtype": "service_principal",
"managedIdentityType": "system_assigned",
"servicePrincipalType": "ManagedIdentity"
}
}
Azure Foundry connector (transformer.py, test_transformer.py):
{
"nodeType": "identity",
"properties": {
"identitySubtype": "machine_account"
}
}
These two connectors emit different subtypes for the same underlying Azure concept. This inconsistency should be resolved regardless of whether a new subtype is added.
Cross-Cloud Comparison
Managed identity is not an Azure-only concept. Every major cloud has a mechanism for workloads to authenticate without long-lived secrets. The vendor names and object models differ, but the underlying pattern is the same: the cloud platform issues short-lived credentials to a workload automatically, with no human managing secrets.
| Cloud | Pattern | Object model | Credential lifecycle |
|---|---|---|---|
| Azure | Managed identity (system-assigned / user-assigned) | Implemented as a service principal with servicePrincipalType: "ManagedIdentity". System-assigned is 1:1 with the resource; user-assigned is N:M (one identity shared across many resources). | Platform issues tokens via IMDS (169.254.169.254). No client secret or certificate. Rotation is invisible. |
| AWS | IAM roles for workloads — EC2 instance profiles, Lambda execution roles, ECS task roles, EKS pod identity | No single "managed identity" object. The IAM role + trust policy is the identity; STS AssumeRole issues temporary credentials. Instance profiles bind a role to an EC2 instance (analogous to system-assigned). | STS issues temporary credentials (default 1h, configurable). Automatically refreshed by SDK. No static access keys needed. |
| GCP | Service accounts attached to resources + Workload Identity Federation | Service account is the identity object (email-shaped: name@project.iam.gserviceaccount.com). Attached to Compute Engine VMs, Cloud Functions, Cloud Run services. Workload Identity Federation extends this to external workloads (GitHub Actions, AWS). | Metadata server issues OAuth2 access tokens (default 1h). No service account keys needed when attached to a resource. |
How each cloud's analog maps to our model
| Aspect | Azure | AWS | GCP | Our platform today |
|---|---|---|---|---|
| Identity object | Service principal (type=ManagedIdentity) | IAM role | Service account | identity node with identitySubtype |
| Binding to workload | System-assigned: implicit. User-assigned: explicit attachment. | Instance profile / execution role ARN in resource config | Service account email in resource config | RUNS_AS edge (workload → identity) |
| Permission model | ARM RBAC role assignments on scopes | IAM policies attached to role | IAM roles bound to service account | HAS_ROLE → GRANTS → APPLIES_TO chain |
| "Orphan" failure mode | System-assigned: deleted with resource. User-assigned: can outlive all consumers. | Role can outlive all instances that assumed it. Zombie trust policies. | Service account can outlive all resources. Unused key grants persist. | orphaned_ownership rule — needs to distinguish these lifecycle patterns |
| Blast radius concern | User-assigned MI shared across many resources | Cross-account role assumption via trust policies | Single SA attached to many resources, or impersonation chains | blast_radius section in evidence packs |
Implication for subtype design
All three clouds share the same fundamental pattern: platform-managed, secretless, resource-coupled credentials. The distinguishing characteristic is the credential model (no human-managed secrets, automatic rotation), not the identity object type. This supports Option A (property-based classification) over Option C (separate subtypes per assignment mode) — because the AWS and GCP analogs don't have a clean system/user-assigned split, and a cross-cloud model should accommodate all three.
If we later build AWS or GCP connectors, the same managedIdentityType property approach works: AWS execution roles would be machine_account + managedIdentityType: "execution_role", GCP attached service accounts would be machine_account + managedIdentityType: "attached_service_account".
Why It Matters (Technical)
Managed identities differ from regular service principals in ways that affect evaluator rules and UI display:
Credential management: No explicit credentials (no client secret, no certificate, no rotation schedule). Rules that reason about credential hygiene (scope_drift, privilege_justification_gap) do not apply in the same way.
Lifecycle coupling: System-assigned managed identities are tied to the Azure resource lifecycle. They cannot be "orphaned" in the traditional sense. User-assigned managed identities are standalone but explicitly attached to resources.
Risk profile: Risk centers on scope (what ARM RBAC roles the identity holds) and whether the hosting resource is appropriately governed, rather than credential exposure and rotation.
Blast radius for user-assigned: A single user-assigned managed identity can be attached to multiple Function Apps, VMs, or Container Apps simultaneously. The blast radius is potentially much larger.
Real-Life Examples
-
System-assigned managed identity on a Function App —
sv0-edr-stub-7165has a system-assigned managed identity resolved via ARM API. The Entra-ServiceNow connector emits this asservice_principalwithmanagedIdentityType: "system_assigned". -
Azure Foundry project identity — Azure AI Foundry projects default to system-assigned identity. Role assignment to the project managed identity is part of setup. Foundry connections/auth explicitly include Managed Identity as an auth mode. The Foundry connector emits this as
machine_account. -
VM-level managed identity for Key Vault access — A VM with system-assigned identity granted
Key Vault Secrets User. If the VM is decommissioned but RBAC isn't cleaned up, the identity is deleted but the role assignment becomes a "zombie" entry — a different failure mode from an orphaned service principal.
Peer Review Recommendation
Short term (recommended): Do not add a new subtype yet. Standardize mapping first.
- Use
identitySubtype: "machine_account"consistently across all connectors whenservicePrincipalType == "ManagedIdentity". - Add/normalize
managedIdentityTypeproperty (system_assigned/user_assigned) where available. - Fix Azure Foundry and Entra-ServiceNow to use the same subtype.
Medium term: Add a derived helper, not a subtype.
Add isManagedIdentity() helper (or derived flag) in the platform that evaluator rules can use for branching. This avoids changing the NormalizedGraph contract while giving rules the information they need.
Revisit subtype addition only if rule/UI pressure remains after mapping cleanup.
Options (for team discussion)
Option A: Standardize existing subtypes (recommended short-term)
- What: Use
machine_account+managedIdentityTypeproperty consistently across connectors. AddisManagedIdentity()helper in platform. - Pros: No schema change. No migration needed. Existing data remains valid. Connectors can be updated independently.
- Cons: Rules must use helper function rather than direct subtype comparison.
- Files:
entra-servicenow/core/transformer.py,azure-foundry/transformer.py, platform evaluator rules
Option B: Add managed_identity as a single subtype
- What: Add
"managed_identity"toIDENTITY_SUBTYPES. RetainmanagedIdentityTypeas property for system/user distinction. - Pros: Explicit at the type level. Clean rule branching. UI can display directly.
- Cons: Requires NormalizedGraph contract update. Existing entities need re-ingest. Evaluator rules need audit.
- Files:
identity-subtypes.ts,types.ts, all connectors, evaluator rules,01-data-model.md
Option C: Add two subtypes (system_assigned_managed_identity, user_assigned_managed_identity)
- What: Two distinct subtypes for the two assignment modes.
- Pros: Most explicit. No supplementary property needed.
- Cons: Largest surface area. Most rule changes. Not aligned with how other clouds model this.
Option D: Defer entirely
- What: Continue with current workaround until W2 evaluator extension.
- Pros: Zero work. Focus on authority paths plan (which is independent of this decision).
- Cons: Connector inconsistency persists. May produce misleading findings.
Relationship to Authority Paths Plan
The function key authority paths plan uses identitySubtype: "service_principal" with managedIdentityType: "system_assigned" as a property. This is compatible with all options above:
- Option A would change this to
machine_account— the authority paths plan code change is a one-line property update, independent of the graph structure fix. - Options B/C would change the subtype value — same one-line update.
- Option D — no change needed.
The authority paths plan can proceed regardless of this decision. The graph structure fix (Permission nodes, GRANTS edges, scope handling) is about edge topology, not identity subtype vocabulary.
Files That Would Need Changes (if subtype is added)
sv0-platform:
src/domain/graph/identity-subtypes.ts— Add toIDENTITY_SUBTYPESarraysrc/evaluator/rules/— Audit rules that branch on identity subtype:orphaned_ownership— system-assigned identities cannot be "orphaned" conventionallydormant_authority— dormancy signal is hosting resource execution, not identity sign-in logs- Future
credential_drift/secret_rotationrules should exclude managed identities
src/ingestion/types.ts— NormalizedGraph contract (additive, non-breaking)
sv0-connectors:
entra-servicenow/core/transformer.py— Update function-app identity subtypeazure-foundry/transformer.py— Already usesmachine_account, would need update if different subtype chosen
sv0-documentation:
docs/architecture/01-data-model.md— Document under identity subtype vocabulary
Related
sv0-platform/src/domain/graph/identity-subtypes.ts— current vocabulary definitionsv0-platform/src/ingestion/types.ts— NormalizedGraph ingestion contractsv0-documentation/docs/architecture/01-data-model.md— entity type and subtype documentationsv0-documentation/docs/architecture/05-connectors.md— connector interface contractsv0-connectors/integrations/azure-foundry/— Azure Foundry connector (usesmachine_account)sv0-connectors/integrations/entra-servicenow/— Entra-ServiceNow connector (usesservice_principalworkaround)sv0-documentation/docs/plans/2026-02-25-function-key-authority-paths.md— authority paths plan (independent)