identity access management

Implementing Azure AD Privileged Identity Management

Configure Microsoft Entra Privileged Identity Management to enforce just-in-time role activation, approval workflows, and access reviews for Azure AD privileged roles.

azure-adentra-ididentity-governancejust-in-timepimprivileged-roleszero-trust
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Overview

Microsoft Entra Privileged Identity Management (PIM) provides time-based and approval-based role activation to mitigate risks from excessive, unnecessary, or misused access to critical resources. PIM replaces permanent (standing) privilege assignments with eligible assignments that require users to explicitly activate their role before use, with configurable duration, MFA enforcement, approval workflows, and justification requirements. This is a core component of Zero Trust identity governance in Microsoft environments.

When to Use

  • When deploying or configuring implementing azure ad privileged identity management capabilities in your environment
  • When establishing security controls aligned to compliance requirements
  • When building or improving security architecture for this domain
  • When conducting security assessments that require this implementation

Prerequisites

  • Microsoft Entra ID P2 or Microsoft Entra ID Governance license
  • Global Administrator or Privileged Role Administrator role
  • Azure subscription for Azure resource role management
  • MFA configured for all privileged users
  • Microsoft Authenticator or FIDO2 key for admin accounts

Core Concepts

Assignment Types

Type Behavior Use Case
Eligible User must activate the role before use; expires after configured duration Day-to-day admin work
Active Role is always active; no activation needed Service accounts, break-glass accounts
Time-Bound Either type with explicit start/end dates Temporary project access, contractor access

PIM Activation Flow

User with Eligible Assignment

        ├── Opens PIM portal → My Roles

        ├── Clicks "Activate" on the desired role

        ├── Provides justification and optional ticket number

        ├── Completes MFA challenge (if required)

        ├── [If approval required] → Notification sent to approvers
        │       │
        │       ├── Approver reviews and approves/denies
        │       └── User notified of decision

        ├── Role activated for configured duration (e.g., 8 hours)

        └── Role automatically deactivated when duration expires

Supported Resource Types

  1. Microsoft Entra Roles: Global Admin, Exchange Admin, Security Admin, etc.
  2. Azure Resource Roles: Owner, Contributor, User Access Administrator on subscriptions/resource groups
  3. PIM for Groups: Manage membership in privileged security groups

Workflow

Step 1: Plan Role Assignments

Audit current permanent role assignments and determine which should be converted to eligible:

Current Role Permanent Holders Action
Global Administrator 2-3 admins Convert to eligible, keep 1 break-glass active
Exchange Administrator IT team Convert all to eligible
Security Administrator SOC team Convert to eligible
User Administrator Help desk Convert to eligible
Application Administrator DevOps Convert to eligible

Best practice: Maintain no more than 2 permanent Global Administrators (break-glass accounts).

Step 2: Configure Role Settings

For each Entra directory role, configure PIM settings:

Via Microsoft Entra Admin Center:

  1. Navigate to Identity Governance > Privileged Identity Management > Microsoft Entra roles
  2. Select "Settings" and choose the role to configure
  3. Configure the following:

Activation Settings:

  • Maximum activation duration: 8 hours (recommended; max 72 hours)
  • Require MFA on activation: Enabled
  • Require justification: Enabled
  • Require ticket information: Enabled (for change management integration)
  • Require approval: Enabled for Global Admin, Security Admin

Assignment Settings:

  • Allow permanent eligible assignment: No (set expiry)
  • Expire eligible assignments after: 6 months (requires re-certification)
  • Allow permanent active assignment: Only for break-glass accounts
  • Require MFA on active assignment: Enabled
  • Require justification on active assignment: Enabled

Notification Settings:

  • Send email when members are assigned eligible: Role assigners, admins
  • Send email when members activate: Admins, security team
  • Send email when eligible members activate roles: Role assignees

Step 3: Configure via Microsoft Graph API

import requests
 
# Acquire token for Microsoft Graph
def get_graph_token(tenant_id, client_id, client_secret):
    url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
    data = {
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret,
        "scope": "https://graph.microsoft.com/.default"
    }
    response = requests.post(url, data=data)
    return response.json()["access_token"]
 
# Create eligible role assignment
def create_eligible_assignment(token, role_definition_id, principal_id,
                                directory_scope="/", duration_hours=8):
    url = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilityScheduleRequests"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    body = {
        "action": "adminAssign",
        "justification": "PIM eligible assignment",
        "roleDefinitionId": role_definition_id,
        "directoryScopeId": directory_scope,
        "principalId": principal_id,
        "scheduleInfo": {
            "startDateTime": "2025-01-01T00:00:00Z",
            "expiration": {
                "type": "afterDuration",
                "duration": "P180D"  # 180-day eligible window
            }
        }
    }
    response = requests.post(url, headers=headers, json=body)
    return response.json()
 
# Activate a role (user self-service)
def activate_role(token, role_definition_id, principal_id, justification,
                   duration_hours=8):
    url = "https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleRequests"
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }
    body = {
        "action": "selfActivate",
        "principalId": principal_id,
        "roleDefinitionId": role_definition_id,
        "directoryScopeId": "/",
        "justification": justification,
        "scheduleInfo": {
            "startDateTime": None,  # Now
            "expiration": {
                "type": "afterDuration",
                "duration": f"PT{duration_hours}H"
            }
        }
    }
    response = requests.post(url, headers=headers, json=body)
    return response.json()

Step 4: Configure Access Reviews

Set up recurring access reviews to verify eligible assignments remain appropriate:

  1. Navigate to Identity Governance > Access Reviews > New Access Review
  2. Configure:
    • Review scope: Privileged Identity Management role assignments
    • Roles: Select all critical roles (Global Admin, Security Admin, etc.)
    • Reviewers: Managers or self-review with justification
    • Frequency: Quarterly for critical roles, semi-annually for others
    • Auto-apply results: Remove access for non-responsive reviews
    • Duration: 14 days for reviewers to respond

Step 5: Configure Alerts

Enable PIM security alerts:

Alert Trigger Action
Too many global admins > 5 Global Admins Review and reduce
Roles being assigned outside PIM Direct role assignment Investigate and convert to PIM
Roles not requiring MFA Activation without MFA Enable MFA requirement
Stale eligible assignments Not activated in 90 days Review and potentially remove
Potential stale service accounts Active assignments not used Investigate and decommission

Validation Checklist

  • All permanent privileged role assignments converted to eligible (except break-glass)
  • Break-glass accounts configured as active with monitoring alerts
  • MFA required for all role activations
  • Approval workflow configured for Global Administrator and Security Administrator
  • Maximum activation duration set to 8 hours or less for critical roles
  • Eligible assignments expire after 6 months (requires re-certification)
  • Justification and ticket information required for activations
  • Email notifications configured for role assignments and activations
  • Access reviews scheduled quarterly for all privileged roles
  • PIM alerts enabled and reviewed weekly
  • Audit logs forwarded to SIEM for monitoring

References

Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 3

api-reference.md1.9 KB

API Reference: Azure AD PIM Audit Agent

Dependencies

Library Version Purpose
requests >=2.28 HTTP client for Microsoft Graph API

CLI Usage

python scripts/agent.py \
  --tenant-id YOUR_TENANT_ID \
  --client-id YOUR_CLIENT_ID \
  --client-secret YOUR_SECRET \
  --output-dir /reports/ \
  --output pim_report.json

Functions

PIMClient(tenant_id, client_id, client_secret)

Authenticates via OAuth2 client credentials flow to Microsoft Graph API.

list_role_definitions() -> list

GET /roleManagement/directory/roleDefinitions - Available directory roles.

list_eligible_assignments() -> list

GET /roleManagement/directory/roleEligibilityScheduleInstances - PIM eligible roles.

list_active_assignments() -> list

GET /roleManagement/directory/roleAssignmentScheduleInstances - Active assignments.

list_role_settings() -> list

GET /policies/roleManagementPolicyAssignments - PIM policy configurations.

audit_permanent_assignments(active, eligible) -> list

Identifies permanent role assignments not managed via PIM eligible workflow.

compute_pim_coverage(active, eligible) -> dict

Calculates percentage of assignments managed through PIM.

Microsoft Graph Endpoints

Endpoint Purpose
POST /oauth2/v2.0/token Client credentials auth
GET /roleManagement/directory/roleDefinitions Role catalog
GET /roleManagement/directory/roleEligibilityScheduleInstances Eligible assignments
GET /roleManagement/directory/roleAssignmentScheduleInstances Active assignments

Output Schema

{
  "coverage": {"active_assignments": 15, "eligible_assignments": 42, "pim_coverage_pct": 73.7},
  "permanent_assignments": [{"role": "Global Administrator", "recommendation": "Convert to eligible"}],
  "recommendations": ["Convert 5 permanent assignments to PIM-eligible"]
}
standards.md2.0 KB

Azure AD PIM - Standards Reference

Microsoft Entra ID Licensing

Feature Required License
PIM for Entra Roles Entra ID P2 or Entra ID Governance
PIM for Azure Resources Entra ID P2 or Entra ID Governance
PIM for Groups Entra ID P2 or Entra ID Governance
Access Reviews Entra ID P2 or Entra ID Governance
Conditional Access Entra ID P1 (minimum)

Critical Entra Directory Roles

Role Risk Level Recommended PIM Setting
Global Administrator Critical Eligible, approval required, max 1hr activation
Privileged Role Administrator Critical Eligible, approval required
Security Administrator High Eligible, MFA required
Exchange Administrator High Eligible, MFA required
SharePoint Administrator High Eligible, MFA required
User Administrator Medium Eligible, MFA required
Application Administrator High Eligible, MFA required
Cloud Application Administrator High Eligible, MFA required
Intune Administrator Medium Eligible, justification required
Compliance Administrator Medium Eligible, justification required

Compliance Framework Mapping

NIST SP 800-53 Rev 5

  • AC-2(4): Automated Audit Actions (PIM audit logs)
  • AC-2(5): Inactivity Logout (time-bound activations)
  • AC-6(1): Authorize Access to Security Functions
  • AC-6(2): Non-Privileged Access for Non-Security Functions
  • AC-6(5): Privileged Accounts (eligible vs. active)

CIS Microsoft 365 Foundations Benchmark

  • 1.1.1: Ensure MFA is enabled for all users in admin roles
  • 1.1.3: Ensure that between two and four Global Admins are designated
  • 1.1.6: Ensure Administrative accounts are separate and cloud-only
  • 1.3.1: Ensure PIM is used to manage roles

SOC 2 Trust Service Criteria

  • CC6.1: Logical and physical access controls
  • CC6.2: Prior to issuing credentials, registration and authorization
  • CC6.3: Authorize, modify, or remove access timely
workflows.md3.8 KB

Azure AD PIM - Workflows

PIM Deployment Workflow

Phase 1: DISCOVERY
    ├── Export all permanent role assignments via Microsoft Graph
    ├── Identify users with multiple admin roles
    ├── Flag accounts without MFA enabled
    └── Document break-glass account strategy
 
Phase 2: PLANNING
    ├── Define activation settings per role (duration, MFA, approval)
    ├── Identify approvers for each critical role
    ├── Create communication plan for affected admins
    └── Schedule pilot group for initial rollout
 
Phase 3: CONFIGURATION
    ├── Configure PIM role settings (activation, assignment, notification)
    ├── Convert permanent assignments to eligible (except break-glass)
    ├── Configure conditional access policies for admin activation
    └── Enable audit logging and SIEM integration
 
Phase 4: TESTING
    ├── Test role activation with pilot users
    ├── Test approval workflow end-to-end
    ├── Test MFA enforcement during activation
    ├── Test auto-deactivation after duration expires
    └── Validate audit logs capture all PIM events
 
Phase 5: ROLLOUT
    ├── Convert remaining permanent assignments to eligible
    ├── Notify all affected users with activation instructions
    ├── Monitor for activation failures and help desk tickets
    └── Configure access reviews on quarterly schedule

Role Activation Workflow

Admin needs to perform privileged task

    ├── Navigate to PIM portal (Entra Admin Center > PIM > My Roles)

    ├── Click "Activate" on the needed role

    ├── Select activation duration (up to configured max)

    ├── Enter justification and optional ticket number

    ├── Complete MFA challenge

    ├── [If approval required]
    │   ├── Request submitted to approvers
    │   ├── Approvers receive email notification
    │   ├── Approver reviews justification and approves/denies
    │   └── Admin receives approval notification

    ├── Role becomes active

    ├── Admin performs required task

    └── Role automatically deactivates when duration expires
        (or admin manually deactivates early)

Access Review Workflow

Quarterly Access Review Triggered

    ├── PIM sends review notifications to designated reviewers

    ├── For each eligible assignment:
    │   ├── Reviewer checks: Is this role still needed?
    │   ├── Reviewer checks: When was role last activated?
    │   ├── Decision: Approve (maintain), Deny (remove), or Don't know
    │   └── Provide justification for decision

    ├── Review period expires (14 days default)

    ├── Auto-apply results:
    │   ├── Approved assignments maintained
    │   ├── Denied assignments removed
    │   └── No-response: configurable (remove or maintain)

    └── Review summary report generated for compliance

Break-Glass Account Workflow

Normal Operations:
    └── Break-glass accounts exist as ACTIVE Global Admin
        ├── Stored in secure physical safe (password printout)
        ├── Excluded from conditional access policies
        ├── Monitored by Azure Monitor alert rule
        └── Monthly verification: confirm no unauthorized sign-ins
 
Emergency Use:
    ├── Primary admin methods unavailable (MFA outage, PIM issue)
    ├── Retrieve break-glass credentials from safe
    ├── Sign in and resolve the emergency
    ├── Document all actions taken
    ├── Reset break-glass credentials after use
    └── Review and document in incident log

Scripts 2

agent.py5.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Azure AD Privileged Identity Management agent using Microsoft Graph API via requests."""

import argparse
import json
import logging
import os
import sys
from datetime import datetime
from typing import List

try:
    import requests
except ImportError:
    sys.exit("requests required: pip install requests")

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

GRAPH_BASE = "https://graph.microsoft.com/v1.0"


class PIMClient:
    """Client for Microsoft Entra PIM via Graph API."""

    def __init__(self, tenant_id: str, client_id: str, client_secret: str):
        self.tenant_id = tenant_id
        self.token = self._acquire_token(client_id, client_secret)
        self.session = requests.Session()
        self.session.headers.update({"Authorization": f"Bearer {self.token}"})

    def _acquire_token(self, client_id: str, client_secret: str) -> str:
        resp = requests.post(
            f"https://login.microsoftonline.com/{self.tenant_id}/oauth2/v2.0/token",
            data={
                "grant_type": "client_credentials",
                "client_id": client_id,
                "client_secret": client_secret,
                "scope": "https://graph.microsoft.com/.default",
            }, timeout=15)
        resp.raise_for_status()
        return resp.json()["access_token"]

    def list_role_definitions(self) -> List[dict]:
        """List available directory role definitions."""
        resp = self.session.get(f"{GRAPH_BASE}/roleManagement/directory/roleDefinitions", timeout=30)
        resp.raise_for_status()
        return resp.json().get("value", [])

    def list_eligible_assignments(self) -> List[dict]:
        """List PIM eligible role assignments."""
        resp = self.session.get(
            f"{GRAPH_BASE}/roleManagement/directory/roleEligibilityScheduleInstances", timeout=30)
        resp.raise_for_status()
        return resp.json().get("value", [])

    def list_active_assignments(self) -> List[dict]:
        """List currently active (activated) role assignments."""
        resp = self.session.get(
            f"{GRAPH_BASE}/roleManagement/directory/roleAssignmentScheduleInstances", timeout=30)
        resp.raise_for_status()
        return resp.json().get("value", [])

    def list_role_settings(self) -> List[dict]:
        """List PIM role management policy assignments."""
        resp = self.session.get(
            f"{GRAPH_BASE}/policies/roleManagementPolicyAssignments?"
            "$filter=scopeId eq '/' and scopeType eq 'DirectoryRole'", timeout=30)
        resp.raise_for_status()
        return resp.json().get("value", [])

    def get_activation_requests(self, top: int = 50) -> List[dict]:
        """List recent role activation requests."""
        resp = self.session.get(
            f"{GRAPH_BASE}/roleManagement/directory/roleEligibilityScheduleRequests?"
            f"$top={top}&$orderby=createdDateTime desc", timeout=30)
        resp.raise_for_status()
        return resp.json().get("value", [])


def audit_permanent_assignments(active: List[dict], eligible: List[dict]) -> List[dict]:
    """Identify permanent (non-PIM) role assignments that should be converted to eligible."""
    eligible_ids = {a.get("principalId") for a in eligible}
    permanent = []
    for a in active:
        if a.get("assignmentType") == "Assigned" and a.get("principalId") not in eligible_ids:
            permanent.append({
                "principal_id": a.get("principalId", ""),
                "role": a.get("roleDefinition", {}).get("displayName", ""),
                "start": a.get("startDateTime", ""),
                "recommendation": "Convert to eligible assignment with JIT activation",
            })
    return permanent


def compute_pim_coverage(active: List[dict], eligible: List[dict]) -> dict:
    """Calculate PIM coverage metrics."""
    total = len(active)
    eligible_count = len(eligible)
    pim_pct = (eligible_count / (total + eligible_count) * 100) if (total + eligible_count) else 0
    return {
        "active_assignments": total,
        "eligible_assignments": eligible_count,
        "pim_coverage_pct": round(pim_pct, 1),
    }


def generate_report(client: PIMClient) -> dict:
    """Generate PIM compliance report."""
    roles = client.list_role_definitions()
    eligible = client.list_eligible_assignments()
    active = client.list_active_assignments()
    permanent = audit_permanent_assignments(active, eligible)
    coverage = compute_pim_coverage(active, eligible)

    report = {
        "analysis_date": datetime.utcnow().isoformat(),
        "role_definitions": len(roles),
        "coverage": coverage,
        "permanent_assignments": permanent,
        "permanent_count": len(permanent),
        "recommendations": [],
    }
    if permanent:
        report["recommendations"].append(
            f"Convert {len(permanent)} permanent assignments to PIM-eligible")
    if coverage["pim_coverage_pct"] < 80:
        report["recommendations"].append("Increase PIM coverage above 80%")
    return report


def main():
    parser = argparse.ArgumentParser(description="Azure AD PIM Audit Agent")
    parser.add_argument("--tenant-id", required=True, help="Azure AD tenant ID")
    parser.add_argument("--client-id", required=True, help="App registration client ID")
    parser.add_argument("--client-secret", required=True, help="App registration secret")
    parser.add_argument("--output-dir", default=".")
    parser.add_argument("--output", default="pim_report.json")
    args = parser.parse_args()

    os.makedirs(args.output_dir, exist_ok=True)
    client = PIMClient(args.tenant_id, args.client_id, args.client_secret)
    report = generate_report(client)
    out_path = os.path.join(args.output_dir, args.output)
    with open(out_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    logger.info("Report saved to %s", out_path)
    print(json.dumps(report, indent=2, default=str))


if __name__ == "__main__":
    main()
process.py8.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Azure AD PIM Audit and Configuration Tool

Audits Entra ID role assignments, identifies permanent privileged assignments
that should be converted to PIM eligible, and monitors PIM activation events.

Requirements:
    pip install msal requests pandas
"""

import json
import sys
from datetime import datetime, timezone

try:
    import requests
    import msal
except ImportError:
    print("[ERROR] Required: pip install msal requests")
    sys.exit(1)


class EntraPIMAuditor:
    """Audit and manage Microsoft Entra PIM configuration."""

    def __init__(self, tenant_id, client_id, client_secret):
        self.tenant_id = tenant_id
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = None
        self._authenticate()

    def _authenticate(self):
        """Acquire Microsoft Graph access token using client credentials."""
        app = msal.ConfidentialClientApplication(
            self.client_id,
            authority=f"https://login.microsoftonline.com/{self.tenant_id}",
            client_credential=self.client_secret,
        )
        result = app.acquire_token_for_client(
            scopes=["https://graph.microsoft.com/.default"]
        )
        if "access_token" in result:
            self.token = result["access_token"]
            print("[OK] Authenticated to Microsoft Graph")
        else:
            raise Exception(f"Auth failed: {result.get('error_description')}")

    def _graph_get(self, endpoint):
        """Make authenticated GET request to Microsoft Graph."""
        headers = {"Authorization": f"Bearer {self.token}"}
        url = f"https://graph.microsoft.com/v1.0{endpoint}"
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        return response.json()

    def get_directory_roles(self):
        """List all Entra directory role definitions."""
        result = self._graph_get("/roleManagement/directory/roleDefinitions")
        roles = {}
        for role in result.get("value", []):
            roles[role["id"]] = {
                "displayName": role["displayName"],
                "description": role.get("description", ""),
                "isBuiltIn": role.get("isBuiltIn", False),
            }
        return roles

    def get_active_assignments(self):
        """List all permanently active role assignments (non-PIM)."""
        result = self._graph_get(
            "/roleManagement/directory/roleAssignments?$expand=principal"
        )
        assignments = []
        for item in result.get("value", []):
            assignments.append({
                "roleDefinitionId": item["roleDefinitionId"],
                "principalId": item["principalId"],
                "directoryScopeId": item.get("directoryScopeId", "/"),
                "principalDisplayName": item.get("principal", {}).get("displayName", ""),
                "principalType": item.get("principal", {}).get("@odata.type", ""),
            })
        return assignments

    def get_eligible_assignments(self):
        """List all PIM eligible role assignments."""
        result = self._graph_get(
            "/roleManagement/directory/roleEligibilityScheduleInstances"
        )
        eligible = []
        for item in result.get("value", []):
            eligible.append({
                "roleDefinitionId": item["roleDefinitionId"],
                "principalId": item["principalId"],
                "directoryScopeId": item.get("directoryScopeId", "/"),
                "startDateTime": item.get("startDateTime"),
                "endDateTime": item.get("endDateTime"),
            })
        return eligible

    def get_pim_activation_history(self, days=30):
        """Retrieve PIM role activation audit events."""
        result = self._graph_get(
            f"/auditLogs/directoryAudits?"
            f"$filter=category eq 'RoleManagement' and "
            f"activityDisplayName eq 'Add member to role completed (PIM activation)'"
            f"&$top=100"
        )
        activations = []
        for event in result.get("value", []):
            activations.append({
                "activityDateTime": event.get("activityDateTime"),
                "activityDisplayName": event.get("activityDisplayName"),
                "initiatedBy": event.get("initiatedBy", {}).get("user", {}).get("displayName", ""),
                "targetResources": [
                    t.get("displayName", "") for t in event.get("targetResources", [])
                ],
                "result": event.get("result"),
            })
        return activations

    def identify_permanent_admins(self):
        """Find permanently active admin assignments that should be PIM eligible."""
        roles = self.get_directory_roles()
        active = self.get_active_assignments()
        eligible = self.get_eligible_assignments()

        critical_roles = {
            rid: info for rid, info in roles.items()
            if info["displayName"] in [
                "Global Administrator",
                "Privileged Role Administrator",
                "Security Administrator",
                "Exchange Administrator",
                "SharePoint Administrator",
                "User Administrator",
                "Application Administrator",
                "Cloud Application Administrator",
                "Intune Administrator",
                "Compliance Administrator",
            ]
        }

        findings = []
        eligible_principals = {
            (e["roleDefinitionId"], e["principalId"]) for e in eligible
        }

        for assignment in active:
            role_id = assignment["roleDefinitionId"]
            if role_id in critical_roles:
                is_also_eligible = (role_id, assignment["principalId"]) in eligible_principals
                findings.append({
                    "role": critical_roles[role_id]["displayName"],
                    "principal": assignment["principalDisplayName"],
                    "principalType": assignment["principalType"],
                    "hasEligibleAssignment": is_also_eligible,
                    "recommendation": "Convert to PIM eligible" if not is_also_eligible else "Review - has both active and eligible",
                })

        return findings

    def generate_audit_report(self):
        """Generate comprehensive PIM audit report."""
        roles = self.get_directory_roles()
        active = self.get_active_assignments()
        eligible = self.get_eligible_assignments()
        permanent_findings = self.identify_permanent_admins()

        report = {
            "report_title": "Microsoft Entra PIM Audit Report",
            "tenant_id": self.tenant_id,
            "generated_at": datetime.now(timezone.utc).isoformat(),
            "summary": {
                "total_directory_roles": len(roles),
                "total_active_assignments": len(active),
                "total_eligible_assignments": len(eligible),
                "permanent_admin_findings": len(permanent_findings),
            },
            "findings": permanent_findings,
            "recommendations": [],
        }

        if len(permanent_findings) > 0:
            report["recommendations"].append({
                "priority": "Critical",
                "finding": f"{len(permanent_findings)} permanent privileged role assignments found",
                "action": "Convert to PIM eligible assignments with MFA and approval requirements"
            })

        active_global_admins = sum(
            1 for f in permanent_findings
            if f["role"] == "Global Administrator"
        )
        if active_global_admins > 2:
            report["recommendations"].append({
                "priority": "High",
                "finding": f"{active_global_admins} permanent Global Administrators (should be max 2 break-glass)",
                "action": "Reduce to 2 break-glass accounts, convert rest to PIM eligible"
            })

        return report


if __name__ == "__main__":
    print("=" * 60)
    print("Microsoft Entra PIM Audit Tool")
    print("=" * 60)
    print()
    print("Usage:")
    print("  auditor = EntraPIMAuditor(tenant_id, client_id, client_secret)")
    print("  report = auditor.generate_audit_report()")
    print("  print(json.dumps(report, indent=2))")
    print()
    print("Required Microsoft Graph permissions:")
    print("  - RoleManagement.Read.All")
    print("  - AuditLog.Read.All")
    print("  - Directory.Read.All")

Assets 1

template.mdtext/markdown · 1.9 KB
Keep exploring