Skip to main content

PIM Monitor

Continuous monitoring of Microsoft Entra ID PIM state with a git-based audit trail.

PIM Monitor is a scheduled Azure DevOps pipeline. It scans your Entra ID Privileged Identity Management configuration, detects changes, and commits inventory JSON files to git. Every change becomes a commit. Your audit trail is version history.

How it works

Each scheduled run fetches the full PIM state from Microsoft Graph, compares it against JSON inventory files committed in the repository, classifies any differences as High, Medium, or Low severity, writes updated inventory files, commits the changes to git, and sends notifications if changes were detected. No database, no external state. The repository is the source of truth.

Why git?

  • Audit trail - every change is a commit with timestamp and diff
  • Version control - git log shows who changed what and when
  • Rollback - reset a commit to undo a scan result (useful for testing)
  • No backend - inventory lives in the repo, no database needed

Key concepts

Inventory structure

inventory/
├── directory-roles/{slug}/
│ ├── definition.json # role properties
│ ├── policy.json # activation/approval/notification rules
│ └── assignments.json # permanent/eligible/active members
├── pim-groups/{slug}/
│ ├── definition.json
│ ├── policy.json # { member: {...}, owner: {...} }
│ └── assignments.json
├── authentication-contexts/{slug}/
│ └── definition.json # lookup for conditional access auth contexts
└── administrative-units/{slug}/
└── definition.json # lookup for AU scope resolution

Each entity gets its own folder so git diffs show exactly which role or group changed.

Severity classification

Changes are classified by rule ID prefix matching, not property inspection:

RuleSeverity
Enablement_EndUser_AssignmentHigh - MFA/justification on activation
Approval_EndUser_AssignmentHigh - Approval requirement
AuthenticationContext_EndUser_AssignmentHigh - Conditional Access auth context
Expiration_*Medium - Duration limits
Enablement_Admin_*Medium - Direct assignment MFA
Notification_*Low - Notifications
Permanent assignment (no expiration)High - Direct permanent role grant
New eligible/active with expirationMedium - Scheduled assignments

Add new rules by editing src/diff.ps1. No code changes needed beyond that.

Access Model and desired-state compliance (optional)

Off by default. There is no variable: creating the AccessModel/ folder is the switch. If the folder is absent, both checks below are silently skipped and PIM Monitor behaves exactly as if this feature does not exist.

When the folder is present, two checks run on every scan:

  • Compliance: role is in a model file with expectedConfig, but the actual policy deviates, so you get a notification at the file's severity level
  • Coverage: role is in inventory but appears in no access-model file, so you get a notification in a dedicated "Classification" section, separate from PIM state changes
AccessModel/ ← create this folder to enable; delete it to disable
├── ControlPlane.json # identity infrastructure roles + expectedConfig
├── ManagementPlane.json # workload-specific admins
├── DataWorkloadPlane.json
├── Specialized.json # high-impact non-identity roles
└── coverage-exclusions.json

See Access Model and Desired-State Compliance for setup, EAM plane mapping, and copy-paste examples.

Notifications (optional)

When changes are detected:

  • Email - via Graph sendMail (requires Mail.Send permission)
  • Webhook - format is auto-detected from the URL:
    • *.logic.azure.com / *.azure-apim.net → Teams Adaptive Card (Power Automate)
    • hooks.slack.com → Slack blocks
    • discord.com/api/webhooks → Discord embed
    • Other → generic JSON

Enable by setting environment variables in your Azure DevOps pipeline.

Next steps