Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions compliance/frameworks/cis_azure_benchmark.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@
"control_name": "Ensure that Azure Key Vault Purge Protection is Enabled",
"description": "Azure Key Vaults without purge protection enabled allow permanent deletion of vaults and their secrets, keys, and certificates during the soft-delete retention period. Even with soft delete enabled, a malicious insider or privileged account can purge vault objects before the retention period expires. Enabling purge protection prevents this by blocking purge operations for the full retention period."
},
"AZ-DB-004": {
"control_id": "4.1.2",
"control_name": "Ensure that 'Allow access to Azure services' for SQL Servers is disabled",
"description": "Enabling 'Allow access to Azure services' on a SQL Server firewall creates a rule that permits any Azure-hosted resource \u2014 including services from other tenants \u2014 to connect to the server. This significantly increases the attack surface. Access should be restricted to specific trusted IP ranges or private endpoints."
},
"AZ-IDN-004": {
"control_id": "1.14",
"control_name": "Ensure that 'Privileged Identity Management' is used to manage privileged access",
"description": "Privileged Identity Management provides time-based and approval-based role activation to mitigate the risk of excessive, unnecessary, or misused access permissions on resources. Without PIM, admin roles are permanently assigned with no just-in-time controls or approval workflows."
},
"AZ-KV-005": {
"control_id": "8.5",
"control_name": "Ensure that the expiration date is set on all certificates",
Expand Down
5 changes: 5 additions & 0 deletions compliance/frameworks/iso27001.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@
"control_id": "A.13.1.1",
"control_name": "Network controls",
"description": "Enabling 'Allow access to Azure services' on a SQL Server firewall bypasses network controls by permitting any Azure-hosted resource to connect to the database server. Networks should be managed and controlled with explicit rules that restrict access to known and trusted sources only."
},
"AZ-IDN-004": {
"control_id": "A.9.2.3",
"control_name": "Management of privileged access rights",
"description": "The allocation and use of privileged access rights should be restricted and controlled. PIM enforces just-in-time access with time limits and approval workflows, ensuring privileged access rights are tightly managed and not permanently assigned."
}
}
}
5 changes: 5 additions & 0 deletions compliance/frameworks/nist_csf.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@
"control_id": "PR.AC-3",
"control_name": "Remote access is managed",
"description": "Enabling 'Allow access to Azure services' on a SQL Server firewall permits any Azure-hosted resource to connect to the database remotely without restriction. PR.AC-3 requires that remote access is managed and controlled. Access should be restricted to specific trusted IP ranges or private endpoints to ensure only authorised systems can reach the database."
},
"AZ-IDN-004": {
"control_id": "PR.AC-4",
"control_name": "Access permissions and authorizations are managed",
"description": "PIM ensures privileged access permissions are managed with time-bound activation and approval workflows. Without PIM, permanently assigned admin roles violate the principle of least privilege and increase the blast radius of compromised accounts."
}
}
}
7 changes: 6 additions & 1 deletion compliance/frameworks/soc2.json
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,12 @@
"AZ-DB-004": {
"control_id": "CC6.6",
"control_name": "Restricts Access from Outside the Network Boundary",
"description": "Enabling 'Allow access to Azure services' on a SQL Server firewall creates a rule that permits any Azure-hosted resource — including services from other tenants — to connect to the database. CC6.6 requires that access from outside the network boundary is restricted to authorised sources. Disabling this setting and replacing it with explicit firewall rules or private endpoints enforces the network boundary and ensures only known and trusted systems can reach the SQL Server."
"description": "Enabling 'Allow access to Azure services' on a SQL Server firewall creates a rule that permits any Azure-hosted resource \u2014 including services from other tenants \u2014 to connect to the database. CC6.6 requires that access from outside the network boundary is restricted to authorised sources. Disabling this setting and replacing it with explicit firewall rules or private endpoints enforces the network boundary and ensures only known and trusted systems can reach the SQL Server."
},
"AZ-IDN-004": {
"control_id": "CC6.3",
"control_name": "Role-based access control",
"description": "PIM provides role-based access control with time-bound activation for privileged roles. Without PIM, admin roles are permanently assigned with no controls, violating the requirement for managed and restricted privileged access."
}
}
}
64 changes: 64 additions & 0 deletions playbooks/cli/fix_az_idn_004.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/bash
# Playbook: fix_az_idn_004.sh
# Rule: AZ-IDN-004 — No Privileged Identity Management for admin roles

set -euo pipefail

echo "========================================"
echo " AZ-IDN-004 Remediation Playbook"
echo " Enable PIM for Admin Roles"
echo "========================================"
echo ""
echo "NOTE: PIM must be configured manually in the Azure Portal."
echo "Automated PIM assignment requires Azure AD Premium P2 license."
echo ""
echo "Step 1 — Verify PIM is available"
echo " Navigate to: portal.azure.com"
echo " Go to: Entra ID > Identity Governance > Privileged Identity Management"
echo " Confirm your tenant has Azure AD Premium P2 licensing"
echo ""
echo "Step 2 — Configure PIM for each admin role"
echo " Go to: PIM > Azure AD roles > Roles"
echo " For each role listed below, click the role and select Settings:"
echo " - Global Administrator"
echo " - Privileged Role Administrator"
echo " - Security Administrator"
echo " - Exchange Administrator"
echo " - SharePoint Administrator"
echo " - Conditional Access Administrator"
echo " - Helpdesk Administrator"
echo " - User Administrator"
echo " - Application Administrator"
echo " - Cloud Application Administrator"
echo ""
echo "Step 3 — Configure each role with:"
echo " - Activation maximum duration: 8 hours or less"
echo " - Require MFA on activation: Enabled"
echo " - Require justification on activation: Enabled"
echo " - Require approval for activation: Enabled (for Global Admin)"
echo ""
echo "Step 4 — Convert permanent assignments to eligible"
echo " Go to: PIM > Azure AD roles > Assignments"
echo " For each permanent admin assignment:"
echo " Click the assignment > Update > Change to Eligible"
echo ""

if [[ $# -lt 1 ]]; then
echo "Usage: $0 <tenant_id>"
echo "Running in guidance-only mode — no tenant ID provided"
exit 0
fi

TENANT_ID="$1"

echo "Step 5 — Verify PIM eligible assignments via CLI"
echo "Checking existing role eligibility schedules for tenant $TENANT_ID..."
az rest \
--method GET \
--url "https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilitySchedules" \
--query "value[].{role:roleDefinitionId, principal:principalId, status:status}" \
--output table 2>/dev/null || echo "Run az login first and ensure RoleManagement.Read.Directory permission."

echo ""
echo "Remediation guidance complete."
echo "Re-run the scanner after configuring PIM to verify compliance."
115 changes: 115 additions & 0 deletions scanner/rules/az_idn_004.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""AZ-IDN-004: No Privileged Identity Management for admin roles."""
import logging
from typing import Any, Dict, List

RULE_ID = "AZ-IDN-004"
RULE_NAME = "No Privileged Identity Management for Admin Roles"
SEVERITY = "HIGH"
CATEGORY = "Identity"
FRAMEWORKS = {"CIS": "1.14", "NIST": "PR.AC-4", "ISO27001": "A.9.2.3", "SOC2": "CC6.3"}
DESCRIPTION = (
"Privileged Identity Management (PIM) is not configured for one or more admin roles "
"in Entra ID. Without PIM, admin roles are permanently assigned with no just-in-time "
"access controls, approval workflows, or time-bound activation. Any compromised admin "
"account has constant unrestricted access with no time limit."
)
REMEDIATION = (
"Enable Privileged Identity Management for all admin roles in Entra ID. "
"Navigate to: Entra ID > Identity Governance > Privileged Identity Management > "
"Azure AD roles > Settings. Configure eligible assignments with time-bound "
"activation, MFA on activation, and approval workflows for all privileged roles."
)
PLAYBOOK = "playbooks/cli/fix_az_idn_004.sh"

logger = logging.getLogger(__name__)

PRIVILEGED_ROLE_NAMES = {
"Global Administrator",
"Privileged Role Administrator",
"Security Administrator",
"Exchange Administrator",
"SharePoint Administrator",
"Conditional Access Administrator",
"Helpdesk Administrator",
"User Administrator",
"Application Administrator",
"Cloud Application Administrator",
}


def scan(azure_client: Any, subscription_id: str) -> List[Dict[str, Any]]:
"""Detect admin roles without PIM eligible assignments configured."""
findings: List[Dict[str, Any]] = []

try:
import requests

# Fetch token once and reuse headers for both API calls
token = azure_client.credential.get_token(
"https://graph.microsoft.com/.default"
)
headers = {"Authorization": f"Bearer {token.token}"}

# Step 1 — Get all role definitions
response = requests.get(
"https://graph.microsoft.com/v1.0/roleManagement/directory/roleDefinitions",
headers=headers,
timeout=30,
)
response.raise_for_status()
role_definitions = response.json().get("value", [])

# Step 2 — Get all PIM eligible role assignments
response = requests.get(
"https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilitySchedules",
headers=headers,
timeout=30,
)
response.raise_for_status()
eligible_schedules = response.json().get("value", [])

except Exception as exc:
logger.error(
"AZ-IDN-004: Failed to fetch data from Graph API: %s", exc
)
logger.warning(
"AZ-IDN-004: Ensure the service principal has "
"RoleManagement.Read.Directory permission on Microsoft Graph."
)
return findings

# Build set of role definition IDs that have PIM eligible assignments
pim_protected_role_ids = {
schedule.get("roleDefinitionId", "")
for schedule in eligible_schedules
}

# Check each privileged role
for role in role_definitions:
role_name = role.get("displayName", "")
role_id = role.get("id", "")

if role_name not in PRIVILEGED_ROLE_NAMES:
continue

if role_id not in pim_protected_role_ids:
findings.append({
"rule_id": RULE_ID,
"rule_name": RULE_NAME,
"severity": SEVERITY,
"category": CATEGORY,
"resource_id": f"/roleManagement/directory/roleDefinitions/{role_id}",
"resource_name": role_name,
"resource_type": "Microsoft.Graph/roleDefinitions",
"description": DESCRIPTION,
"remediation": REMEDIATION,
"playbook": PLAYBOOK,
"frameworks": FRAMEWORKS,
"metadata": {
"role_id": role_id,
"role_name": role_name,
"pim_configured": False,
},
})

return findings
Loading