npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Configure Microsoft Entra ID (Azure AD) Conditional Access policies for zero trust access control. Covers signal-based policy design, device compliance requirements, risk-based authentication, named locations, session controls, and integration with NIST SP 1800-35 zero trust architecture.
When to Use
- When deploying or configuring implementing conditional access policies azure ad 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
- Familiarity with identity access management concepts and tools
- Access to a test or lab environment for safe execution
- Python 3.8+ with required dependencies installed
- Appropriate authorization for any testing activities
Objectives
- Implement comprehensive implementing conditional access policies in azure ad capability
- Establish automated discovery and monitoring processes
- Integrate with enterprise IAM and security tools
- Generate compliance-ready documentation and reports
- Align with NIST 800-53 access control requirements
Security Controls
| Control | NIST 800-53 | Description |
|---|---|---|
| Account Management | AC-2 | Lifecycle management |
| Access Enforcement | AC-3 | Policy-based access control |
| Least Privilege | AC-6 | Minimum necessary permissions |
| Audit Logging | AU-3 | Authentication and access events |
| Identification | IA-2 | User and service identification |
Verification
- Implementation tested in non-production environment
- Security policies configured and enforced
- Audit logging enabled and forwarding to SIEM
- Documentation and runbooks complete
- Compliance evidence generated
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md1.5 KB
API Reference: Azure AD Conditional Access Audit Agent
Dependencies
| Library | Version | Purpose |
|---|---|---|
| requests | >=2.28 | HTTP client for Microsoft Graph API |
CLI Usage
python scripts/agent.py \
--tenant-id TENANT_ID --client-id CLIENT_ID --client-secret SECRET \
--output-dir /reports/Functions
ConditionalAccessClient(tenant_id, client_id, client_secret)
Authenticates via OAuth2 client credentials to Microsoft Graph.
list_policies() -> list
GET /identity/conditionalAccess/policies - All conditional access policies.
list_named_locations() -> list
GET /identity/conditionalAccess/namedLocations - Named locations for geo-fencing.
audit_policy(policy) -> dict
Checks for: MFA requirement, enabled state, app coverage, grant controls.
check_baseline_policies(policies) -> list
Verifies essential baselines: MFA for admins, block legacy auth, require compliant devices.
generate_report(client) -> dict
Full audit with per-policy findings and baseline gap analysis.
Microsoft Graph Endpoints
| Endpoint | Purpose |
|---|---|
GET /identity/conditionalAccess/policies |
List CA policies |
GET /identity/conditionalAccess/namedLocations |
Named locations |
Output Schema
{
"total_policies": 15, "enabled_policies": 12,
"summary": {"high_risk": 3, "missing_baselines": 1},
"baseline_checks": [{"baseline": "Require MFA for admins", "implemented": true}]
}Scripts 1
agent.py5.5 KB
#!/usr/bin/env python3
"""Azure AD Conditional Access policy audit agent using Microsoft Graph API."""
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 ConditionalAccessClient:
"""Client for Microsoft Graph Conditional Access API."""
def __init__(self, tenant_id: str, client_id: str, client_secret: str):
self.token = self._auth(tenant_id, client_id, client_secret)
self.headers = {"Authorization": f"Bearer {self.token}"}
def _auth(self, tenant_id, client_id, client_secret) -> str:
resp = requests.post(
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"}, timeout=15)
resp.raise_for_status()
return resp.json()["access_token"]
def list_policies(self) -> List[dict]:
"""List all conditional access policies."""
resp = requests.get(f"{GRAPH_BASE}/identity/conditionalAccess/policies",
headers=self.headers, timeout=30)
resp.raise_for_status()
return resp.json().get("value", [])
def list_named_locations(self) -> List[dict]:
"""List named locations used in policies."""
resp = requests.get(f"{GRAPH_BASE}/identity/conditionalAccess/namedLocations",
headers=self.headers, timeout=30)
resp.raise_for_status()
return resp.json().get("value", [])
def audit_policy(policy: dict) -> dict:
"""Audit a single conditional access policy for security gaps."""
findings = []
conditions = policy.get("conditions", {})
grant = policy.get("grantControls", {})
users = conditions.get("users", {})
if "All" in users.get("includeUsers", []) and not users.get("excludeUsers"):
pass
if not grant.get("builtInControls"):
findings.append("No grant controls configured")
if "mfa" not in (grant.get("builtInControls") or []):
findings.append("MFA not required")
if policy.get("state") != "enabled":
findings.append("Policy is not enabled")
apps = conditions.get("applications", {})
if "All" not in apps.get("includeApplications", []):
findings.append("Policy does not cover all applications")
return {
"name": policy.get("displayName", ""),
"state": policy.get("state", ""),
"grant_controls": grant.get("builtInControls", []),
"findings": findings,
"risk_level": "HIGH" if len(findings) >= 2 else "MEDIUM" if findings else "LOW",
}
def check_baseline_policies(policies: List[dict]) -> List[dict]:
"""Check for essential baseline conditional access policies."""
baselines = [
{"name": "Require MFA for admins", "check": lambda p: "mfa" in str(p.get("grantControls", {})).lower() and "admin" in str(p.get("conditions", {}).get("users", {})).lower()},
{"name": "Block legacy authentication", "check": lambda p: "block" in str(p.get("grantControls", {})).lower()},
{"name": "Require compliant devices", "check": lambda p: "compliantDevice" in str(p.get("grantControls", {}))},
]
results = []
for baseline in baselines:
found = any(baseline["check"](p) for p in policies)
results.append({"baseline": baseline["name"], "implemented": found,
"priority": "CRITICAL" if not found else "OK"})
return results
def generate_report(client: ConditionalAccessClient) -> dict:
"""Generate conditional access audit report."""
policies = client.list_policies()
locations = client.list_named_locations()
audited = [audit_policy(p) for p in policies]
baselines = check_baseline_policies(policies)
enabled = sum(1 for p in policies if p.get("state") == "enabled")
report = {
"analysis_date": datetime.utcnow().isoformat(),
"total_policies": len(policies),
"enabled_policies": enabled,
"named_locations": len(locations),
"policy_audits": audited,
"baseline_checks": baselines,
"summary": {
"high_risk": sum(1 for a in audited if a["risk_level"] == "HIGH"),
"missing_baselines": sum(1 for b in baselines if not b["implemented"]),
},
}
return report
def main():
parser = argparse.ArgumentParser(description="Azure AD Conditional Access Audit Agent")
parser.add_argument("--tenant-id", required=True)
parser.add_argument("--client-id", required=True)
parser.add_argument("--client-secret", required=True)
parser.add_argument("--output-dir", default=".")
parser.add_argument("--output", default="conditional_access_report.json")
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
client = ConditionalAccessClient(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["summary"], indent=2))
if __name__ == "__main__":
main()