mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
313 lines
12 KiB
Python
313 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Access Review and Certification Engine
|
|
|
|
Automates access review campaigns by collecting entitlement data,
|
|
assigning reviewers, tracking certification decisions, generating
|
|
compliance reports, and identifying SOD violations.
|
|
"""
|
|
|
|
import json
|
|
import datetime
|
|
import csv
|
|
import io
|
|
from typing import Dict, List, Optional, Set, Tuple
|
|
from dataclasses import dataclass, field
|
|
from collections import defaultdict
|
|
|
|
|
|
@dataclass
|
|
class UserEntitlement:
|
|
"""A user-to-entitlement mapping for review."""
|
|
user_id: str
|
|
user_name: str
|
|
department: str
|
|
manager: str
|
|
application: str
|
|
entitlement: str
|
|
risk_level: str # critical, high, medium, low
|
|
last_used: Optional[str] = None
|
|
granted_date: Optional[str] = None
|
|
review_status: str = "pending" # pending, approved, revoked, escalated
|
|
reviewer: str = ""
|
|
decision_date: Optional[str] = None
|
|
justification: str = ""
|
|
|
|
|
|
@dataclass
|
|
class SODRule:
|
|
"""Separation of Duties conflict rule."""
|
|
rule_id: str
|
|
description: str
|
|
entitlement_a: str
|
|
application_a: str
|
|
entitlement_b: str
|
|
application_b: str
|
|
severity: str # critical, high, medium
|
|
|
|
|
|
@dataclass
|
|
class CampaignConfig:
|
|
"""Access review campaign configuration."""
|
|
campaign_id: str
|
|
name: str
|
|
start_date: str
|
|
end_date: str
|
|
review_model: str # manager, app_owner, hybrid
|
|
scope_applications: List[str] = field(default_factory=list)
|
|
escalation_days: int = 21
|
|
auto_revoke_unreviewed: bool = False
|
|
|
|
|
|
class AccessReviewEngine:
|
|
"""Manages access review and certification campaigns."""
|
|
|
|
def __init__(self, config: CampaignConfig):
|
|
self.config = config
|
|
self.entitlements: List[UserEntitlement] = []
|
|
self.sod_rules: List[SODRule] = []
|
|
self.sod_violations: List[Dict] = []
|
|
|
|
def load_entitlements(self, entitlements: List[Dict]):
|
|
"""Load user-entitlement data for review."""
|
|
for e in entitlements:
|
|
ue = UserEntitlement(**e)
|
|
if not self.config.scope_applications or \
|
|
ue.application in self.config.scope_applications:
|
|
self.entitlements.append(ue)
|
|
|
|
def load_sod_rules(self, rules: List[Dict]):
|
|
"""Load SOD conflict rules."""
|
|
for r in rules:
|
|
self.sod_rules.append(SODRule(**r))
|
|
|
|
def assign_reviewers(self):
|
|
"""Assign reviewers based on campaign review model."""
|
|
for ent in self.entitlements:
|
|
if ent.review_status != "pending":
|
|
continue
|
|
if self.config.review_model == "manager":
|
|
ent.reviewer = ent.manager
|
|
elif self.config.review_model == "app_owner":
|
|
ent.reviewer = f"owner_{ent.application}"
|
|
elif self.config.review_model == "hybrid":
|
|
if ent.risk_level in ("critical", "high"):
|
|
ent.reviewer = f"owner_{ent.application}"
|
|
else:
|
|
ent.reviewer = ent.manager
|
|
|
|
def detect_sod_violations(self) -> List[Dict]:
|
|
"""Detect separation of duties violations."""
|
|
self.sod_violations = []
|
|
user_entitlements = defaultdict(list)
|
|
|
|
for ent in self.entitlements:
|
|
user_entitlements[ent.user_id].append(ent)
|
|
|
|
for user_id, ents in user_entitlements.items():
|
|
for rule in self.sod_rules:
|
|
has_a = any(
|
|
e.application == rule.application_a and e.entitlement == rule.entitlement_a
|
|
for e in ents
|
|
)
|
|
has_b = any(
|
|
e.application == rule.application_b and e.entitlement == rule.entitlement_b
|
|
for e in ents
|
|
)
|
|
if has_a and has_b:
|
|
user_name = next(e.user_name for e in ents)
|
|
self.sod_violations.append({
|
|
"user_id": user_id,
|
|
"user_name": user_name,
|
|
"rule_id": rule.rule_id,
|
|
"description": rule.description,
|
|
"severity": rule.severity,
|
|
"entitlement_a": f"{rule.application_a}:{rule.entitlement_a}",
|
|
"entitlement_b": f"{rule.application_b}:{rule.entitlement_b}"
|
|
})
|
|
|
|
return self.sod_violations
|
|
|
|
def identify_stale_access(self, days_threshold: int = 90) -> List[UserEntitlement]:
|
|
"""Identify entitlements not used within threshold."""
|
|
stale = []
|
|
now = datetime.datetime.now()
|
|
|
|
for ent in self.entitlements:
|
|
if ent.last_used:
|
|
try:
|
|
last = datetime.datetime.fromisoformat(ent.last_used)
|
|
if (now - last).days > days_threshold:
|
|
stale.append(ent)
|
|
except ValueError:
|
|
pass
|
|
else:
|
|
stale.append(ent)
|
|
|
|
return stale
|
|
|
|
def identify_orphaned_access(self, active_users: Set[str]) -> List[UserEntitlement]:
|
|
"""Identify entitlements belonging to inactive/terminated users."""
|
|
return [e for e in self.entitlements if e.user_id not in active_users]
|
|
|
|
def process_decision(self, user_id: str, application: str, entitlement: str,
|
|
decision: str, justification: str = ""):
|
|
"""Process a reviewer's certification decision."""
|
|
for ent in self.entitlements:
|
|
if (ent.user_id == user_id and ent.application == application and
|
|
ent.entitlement == entitlement):
|
|
ent.review_status = decision
|
|
ent.decision_date = datetime.datetime.now().isoformat()
|
|
ent.justification = justification
|
|
break
|
|
|
|
def get_campaign_metrics(self) -> Dict:
|
|
"""Calculate campaign progress metrics."""
|
|
total = len(self.entitlements)
|
|
if total == 0:
|
|
return {"total": 0, "completion_rate": 0}
|
|
|
|
by_status = defaultdict(int)
|
|
by_risk = defaultdict(lambda: defaultdict(int))
|
|
by_reviewer = defaultdict(lambda: {"total": 0, "completed": 0})
|
|
|
|
for ent in self.entitlements:
|
|
by_status[ent.review_status] += 1
|
|
by_risk[ent.risk_level][ent.review_status] += 1
|
|
by_reviewer[ent.reviewer]["total"] += 1
|
|
if ent.review_status in ("approved", "revoked"):
|
|
by_reviewer[ent.reviewer]["completed"] += 1
|
|
|
|
completed = by_status.get("approved", 0) + by_status.get("revoked", 0)
|
|
revocation_rate = by_status.get("revoked", 0) / max(completed, 1) * 100
|
|
|
|
return {
|
|
"total": total,
|
|
"pending": by_status.get("pending", 0),
|
|
"approved": by_status.get("approved", 0),
|
|
"revoked": by_status.get("revoked", 0),
|
|
"escalated": by_status.get("escalated", 0),
|
|
"completion_rate": round(completed / total * 100, 1),
|
|
"revocation_rate": round(revocation_rate, 1),
|
|
"by_risk": dict(by_risk),
|
|
"sod_violations": len(self.sod_violations),
|
|
"reviewer_progress": {k: v for k, v in by_reviewer.items()}
|
|
}
|
|
|
|
def generate_compliance_report(self) -> str:
|
|
"""Generate compliance-ready access review report."""
|
|
metrics = self.get_campaign_metrics()
|
|
stale = self.identify_stale_access()
|
|
|
|
lines = [
|
|
"=" * 70,
|
|
"ACCESS REVIEW AND CERTIFICATION REPORT",
|
|
"=" * 70,
|
|
f"Campaign: {self.config.name} ({self.config.campaign_id})",
|
|
f"Period: {self.config.start_date} to {self.config.end_date}",
|
|
f"Review Model: {self.config.review_model}",
|
|
f"Report Generated: {datetime.datetime.now().isoformat()}",
|
|
"-" * 70,
|
|
"",
|
|
"CAMPAIGN METRICS",
|
|
f" Total Entitlements Reviewed: {metrics['total']}",
|
|
f" Completion Rate: {metrics['completion_rate']}%",
|
|
f" Approved: {metrics['approved']}",
|
|
f" Revoked: {metrics['revoked']}",
|
|
f" Pending: {metrics['pending']}",
|
|
f" Escalated: {metrics['escalated']}",
|
|
f" Revocation Rate: {metrics['revocation_rate']}%",
|
|
f" Stale Access Items: {len(stale)}",
|
|
f" SOD Violations: {metrics['sod_violations']}",
|
|
""
|
|
]
|
|
|
|
if self.sod_violations:
|
|
lines.append("SOD VIOLATIONS:")
|
|
lines.append("-" * 40)
|
|
for v in self.sod_violations:
|
|
lines.append(f" [{v['severity'].upper()}] {v['user_name']} ({v['user_id']})")
|
|
lines.append(f" Rule: {v['description']}")
|
|
lines.append(f" Conflict: {v['entitlement_a']} <-> {v['entitlement_b']}")
|
|
lines.append("")
|
|
|
|
# Reviewer progress
|
|
lines.append("REVIEWER PROGRESS:")
|
|
lines.append("-" * 40)
|
|
for reviewer, progress in metrics["reviewer_progress"].items():
|
|
pct = round(progress["completed"] / max(progress["total"], 1) * 100, 1)
|
|
lines.append(f" {reviewer}: {progress['completed']}/{progress['total']} ({pct}%)")
|
|
lines.append("")
|
|
|
|
# Revoked access details
|
|
revoked = [e for e in self.entitlements if e.review_status == "revoked"]
|
|
if revoked:
|
|
lines.append("REVOKED ACCESS:")
|
|
lines.append("-" * 40)
|
|
for e in revoked:
|
|
lines.append(f" {e.user_name} - {e.application}:{e.entitlement} [{e.risk_level}]")
|
|
lines.append("")
|
|
|
|
lines.append("=" * 70)
|
|
overall = "COMPLIANT" if metrics["completion_rate"] >= 95 else "NON-COMPLIANT"
|
|
lines.append(f"COMPLIANCE STATUS: {overall}")
|
|
lines.append("=" * 70)
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
"""Run access review with sample data."""
|
|
config = CampaignConfig(
|
|
campaign_id="AR-2026-Q1",
|
|
name="Q1 2026 Quarterly Access Review",
|
|
start_date="2026-01-01",
|
|
end_date="2026-03-31",
|
|
review_model="hybrid",
|
|
scope_applications=["SAP", "Salesforce", "AWS", "GitHub"]
|
|
)
|
|
|
|
engine = AccessReviewEngine(config)
|
|
|
|
sample_entitlements = [
|
|
{"user_id": "U001", "user_name": "Alice Johnson", "department": "Finance",
|
|
"manager": "Bob Smith", "application": "SAP", "entitlement": "AP_Create",
|
|
"risk_level": "high", "last_used": "2026-02-20", "granted_date": "2024-06-15"},
|
|
{"user_id": "U001", "user_name": "Alice Johnson", "department": "Finance",
|
|
"manager": "Bob Smith", "application": "SAP", "entitlement": "AP_Approve",
|
|
"risk_level": "critical", "last_used": "2026-02-18", "granted_date": "2025-01-10"},
|
|
{"user_id": "U002", "user_name": "Charlie Brown", "department": "Engineering",
|
|
"manager": "Diana Prince", "application": "AWS", "entitlement": "AdminAccess",
|
|
"risk_level": "critical", "last_used": "2025-10-01", "granted_date": "2024-03-20"},
|
|
{"user_id": "U003", "user_name": "Eve Wilson", "department": "Sales",
|
|
"manager": "Frank Castle", "application": "Salesforce", "entitlement": "Standard_User",
|
|
"risk_level": "low", "last_used": "2026-02-22", "granted_date": "2025-08-01"},
|
|
{"user_id": "U004", "user_name": "Grace Lee", "department": "Engineering",
|
|
"manager": "Diana Prince", "application": "GitHub", "entitlement": "Org_Admin",
|
|
"risk_level": "high", "last_used": "2026-02-21", "granted_date": "2025-05-15"},
|
|
]
|
|
|
|
sod_rules = [
|
|
{"rule_id": "SOD-001", "description": "AP Create and AP Approve conflict",
|
|
"entitlement_a": "AP_Create", "application_a": "SAP",
|
|
"entitlement_b": "AP_Approve", "application_b": "SAP",
|
|
"severity": "critical"}
|
|
]
|
|
|
|
engine.load_entitlements(sample_entitlements)
|
|
engine.load_sod_rules(sod_rules)
|
|
engine.assign_reviewers()
|
|
engine.detect_sod_violations()
|
|
|
|
# Simulate some decisions
|
|
engine.process_decision("U001", "SAP", "AP_Create", "approved", "Required for daily AP processing")
|
|
engine.process_decision("U002", "AWS", "AdminAccess", "revoked", "Stale access - user no longer needs admin")
|
|
engine.process_decision("U003", "Salesforce", "Standard_User", "approved", "Active sales team member")
|
|
|
|
report = engine.generate_compliance_report()
|
|
print(report)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|