npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Privileged Account Access Review is a critical identity governance process that validates whether users with elevated permissions still require their access. This review covers domain admins, service accounts, database administrators, cloud IAM roles, and application-level privileged accounts. Regular access reviews are mandated by SOC 2, PCI DSS, HIPAA, and SOX compliance frameworks, typically required quarterly for high-privilege accounts.
When to Use
- When conducting security assessments that involve performing privileged account access review
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- PAM solution deployed (CyberArk, BeyondTrust, Delinea, or equivalent)
- Identity governance platform (SailPoint, Saviynt, or equivalent)
- Complete inventory of privileged accounts across all platforms
- Defined access review policy with SLAs and escalation procedures
- Designated reviewers (account owners, managers, security team)
Core Concepts
Privileged Account Categories
| Category | Examples | Risk Level | Review Frequency |
|---|---|---|---|
| Domain Admins | Enterprise Admin, Domain Admin, Schema Admin | Critical | Monthly |
| Service Accounts | SQL service, backup agents, monitoring agents | High | Quarterly |
| Cloud IAM | AWS root, Azure Global Admin, GCP Owner | Critical | Monthly |
| Database Admin | DBA accounts, sa/sys accounts | High | Quarterly |
| Application Admin | App admin roles, API keys with admin scope | Medium | Semi-annually |
| Emergency/Break-glass | Firecall accounts, emergency access | Critical | After each use |
Four-Pillar Review Framework
DISCOVER VALIDATE REMEDIATE MONITOR
│ │ │ │
├─ Enumerate all ├─ Verify business ├─ Remove excess ├─ Continuous
│ privileged accounts │ justification │ privileges │ monitoring
│ │ │ │
├─ Identify orphaned ├─ Confirm account ├─ Disable orphaned ├─ Anomaly
│ accounts │ ownership │ accounts │ detection
│ │ │ │
├─ Map permissions to ├─ Check compliance ├─ Enforce password ├─ Session
│ business roles │ with policies │ rotation │ recording
│ │ │ │
└─ Classify by risk └─ Review last usage └─ Implement JIT └─ Audit
level and activity access loggingWorkflow
Step 1: Account Discovery and Inventory
Enumerate all privileged accounts across the environment:
Active Directory:
- Domain Admins, Enterprise Admins, Schema Admins groups
- Accounts with AdminCount=1 attribute
- Service accounts with SPN (Service Principal Names)
- Accounts with delegation rights (Unconstrained/Constrained)
Cloud Platforms:
- AWS: IAM users/roles with AdministratorAccess, PowerUserAccess, or
iam:*permissions - Azure: Global Administrator, Privileged Role Administrator, Security Administrator roles
- GCP: Owner, Editor roles at organization/project level
Databases:
- SQL Server: sysadmin, db_owner, securityadmin fixed roles
- Oracle: DBA, SYSDBA, SYSOPER privileges
- PostgreSQL: superuser, createrole, createdb attributes
Step 2: Establish Review Criteria
Each privileged account must be evaluated against:
- Business Justification: Does the user's current role require this privilege?
- Least Privilege: Can the task be performed with lower privileges?
- Account Activity: Has the account been active in the last 90 days?
- Compliance Status: Does the account meet password policy, MFA requirements?
- Separation of Duties: Does the access create SoD conflicts?
- Ownership: Is a responsible owner assigned and active?
Step 3: Conduct the Review
For each account, the designated reviewer must:
- Review the account details, permissions, and last activity date
- Approve (certify) the access if still required with documented justification
- Revoke access if no longer needed or the reviewer cannot justify the privilege
- Flag for investigation if anomalous activity or policy violations are detected
- Escalate if the reviewer cannot make a determination
Decision matrix:
| Condition | Action |
|---|---|
| Active user, justified privilege | Certify - maintain access |
| Active user, excessive privilege | Remediate - reduce to least privilege |
| Inactive > 90 days | Disable account, notify owner |
| No owner identified | Disable account, escalate to security |
| SoD conflict detected | Remediate - reassign or add compensating controls |
| Break-glass account | Verify last use was authorized, reset credentials |
Step 4: Remediation and Enforcement
After reviews are completed:
- Revoke access for accounts that were not certified within the SLA period
- Implement automatic revocation for accounts not reviewed within 14 days
- Rotate credentials for all certified privileged accounts
- Convert standing privileges to just-in-time (JIT) access where possible
- Update PAM vault with current account inventory
Step 5: Reporting and Documentation
Generate review reports including:
- Total accounts reviewed vs. total in scope
- Certification rate (approved vs. revoked)
- Average review completion time
- Overdue reviews and escalations
- Remediation actions taken
- Comparison with previous review cycle
Validation Checklist
- Complete inventory of all privileged accounts documented
- All accounts assigned to a responsible owner/reviewer
- Review criteria and decision matrix defined
- Reviewers completed certification within SLA (14 days)
- Revoked accounts disabled and credentials rotated
- Orphaned accounts identified and disabled
- Service accounts reviewed for least privilege
- Break-glass accounts audited for authorized use only
- Review report generated with metrics and trends
- Remediation tickets created and tracked to completion
- Evidence preserved for compliance audit
References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.3 KB
Privileged Account Access Review — API Reference
CSV Input Format
The agent consumes a CSV file with these columns:
| Column | Type | Description |
|---|---|---|
username |
string | Account identifier (SAMAccountName or UPN) |
owner |
string | Assigned account owner / manager |
roles |
string | Semicolon-separated privilege roles |
last_used |
string | ISO date YYYY-MM-DD of last interactive logon |
last_certified |
string | ISO date YYYY-MM-DD of most recent access review |
account_type |
string | human, service, or shared |
Checks Performed
Stale Account Detection
Flags accounts whose last_used date exceeds a configurable threshold (default 90 days). Accounts without a last_used value are automatically flagged as high severity.
Shared Account Detection
Matches username against common shared-account patterns: admin, root, service, svc_, shared, generic, temp. Flags accounts matching these patterns that lack an assigned owner.
Excessive Privilege Detection
Compares the roles field against high-risk role names: Domain Admin, Enterprise Admin, Schema Admin, Global Admin, Super Admin, Root. Any match triggers a critical finding.
Recertification Compliance
Compares last_certified against a configurable interval (default 180 days). Accounts never certified are flagged as critical.
Output Schema
{
"report": "privileged_account_access_review",
"generated_at": "ISO-8601 timestamp",
"total_accounts": 150,
"total_findings": 12,
"severity_summary": {"critical": 3, "high": 7, "medium": 2},
"findings": [
{
"account": "svc_backup",
"issue": "shared_account_no_owner",
"severity": "critical",
"detail": "Appears shared (matches 'svc_') with no assigned owner"
}
]
}Compliance Frameworks
- NIST SP 800-53 AC-2: Account Management — periodic review of privileged accounts
- CIS Controls v8 5.3: Disable dormant accounts after 45 days of inactivity
- PCI DSS 8.1.4: Remove/disable inactive user accounts within 90 days
- SOX Section 404: Internal controls over financial reporting require access reviews
- ISO 27001 A.9.2.5: Review of user access rights at planned intervals
CLI Usage
python agent.py --input accounts.csv --stale-days 90 --cert-days 180 --output report.jsonstandards.md2.2 KB
Privileged Account Access Review - Standards Reference
Regulatory Requirements
SOC 2 Type II - CC6.1, CC6.2, CC6.3
- CC6.1: Logical and physical access controls restrict access to information assets
- CC6.2: Prior to issuing system credentials, the entity registers and authorizes new users
- CC6.3: The entity authorizes, modifies, or removes access in a timely manner
- Quarterly privileged access reviews required for audit evidence
PCI DSS v4.0 - Requirement 7
- 7.1: Processes and mechanisms for restricting access are defined and understood
- 7.2: Access to system components and data is appropriately defined and assigned
- 7.2.4: All user accounts and related access privileges are reviewed at least every six months
- 7.2.5: All application and system accounts and privileges are reviewed at least every six months
HIPAA Security Rule - 164.312(a)(1)
- Access control standard requiring unique user identification
- Emergency access procedure (break-glass accounts)
- Automatic logoff and encryption/decryption
- Periodic review and modification of access rights
SOX Section 404
- Internal controls over financial reporting
- Segregation of duties enforcement
- Access to financial systems must be reviewed quarterly
- Evidence of review decisions must be retained
NIST SP 800-53 Rev 5 - Access Control Family
- AC-2: Account Management (review periodically)
- AC-2(3): Disable Accounts (within defined time period)
- AC-2(4): Automated Audit Actions
- AC-2(12): Account Monitoring for Atypical Usage
- AC-6: Least Privilege
- AC-6(7): Review of User Privileges
Industry Frameworks
CIS Controls v8
- Control 5.1: Establish and maintain an inventory of accounts
- Control 5.2: Use unique passwords
- Control 5.3: Disable dormant accounts
- Control 5.4: Restrict administrator privileges to dedicated administrator accounts
- Control 5.5: Establish and maintain an inventory of service accounts
NIST Cybersecurity Framework 2.0
- PR.AA-01: Identities and credentials for authorized users are managed
- PR.AA-02: Identities are proofed and bound to credentials
- PR.AA-03: Users, services, and hardware are authenticated
- PR.AA-05: Access permissions, entitlements, and authorizations are defined
workflows.md3.5 KB
Privileged Account Access Review - Workflows
Quarterly Review Cycle
Week 1: PREPARATION
├── Extract privileged account inventory from PAM/AD/Cloud
├── Identify new accounts since last review
├── Assign reviewers based on account ownership
└── Send review campaign notifications
Week 2-3: REVIEW EXECUTION
├── Reviewers evaluate each account against criteria
├── Approve, revoke, or flag for investigation
├── Escalate unresponsive reviewers after 7 days
└── Security team reviews flagged accounts
Week 4: REMEDIATION
├── Disable/remove revoked accounts
├── Rotate credentials for all reviewed accounts
├── Create tickets for privilege reduction
└── Generate review completion reportAccount Discovery Workflow
1. Active Directory Enumeration
├── Query AdminCount=1 accounts
├── Enumerate privileged group memberships
├── Identify accounts with SPN (service accounts)
└── Check for accounts with delegation rights
2. Cloud Platform Enumeration
├── AWS: List IAM users/roles with admin policies
├── Azure: Export Entra ID directory role assignments
├── GCP: List IAM bindings with Owner/Editor roles
└── Cross-reference with known approved accounts
3. Database and Application Enumeration
├── Query database system role memberships
├── Export application admin role assignments
└── Identify shared/generic admin accounts
4. Consolidation
├── Merge all discovered accounts into single inventory
├── Deduplicate accounts across platforms
├── Assign risk classification
└── Identify accounts missing from PAM vaultReviewer Decision Workflow
Reviewer receives account for certification
│
├── Is the account owner still employed?
│ ├── NO → Revoke immediately, disable account
│ └── YES → Continue
│
├── Has the account been used in last 90 days?
│ ├── NO → Recommend disable, notify owner
│ └── YES → Continue
│
├── Does the user's current role require this privilege?
│ ├── NO → Revoke, provide lower-privilege alternative
│ └── YES → Continue
│
├── Can the privilege be reduced (least privilege)?
│ ├── YES → Approve with remediation to reduce
│ └── NO → Continue
│
├── Are there SoD conflicts?
│ ├── YES → Flag for risk acceptance or remediation
│ └── NO → Continue
│
└── CERTIFY the access with documented justificationEmergency Account Review Workflow
Break-glass account used
│
├── Alert generated to security team
│
├── Within 24 hours:
│ ├── Verify incident ticket exists for the usage
│ ├── Confirm authorized personnel used the account
│ ├── Review session recording (if available)
│ └── Validate actions taken were appropriate
│
├── Within 48 hours:
│ ├── Reset break-glass account credentials
│ ├── Store new credentials in sealed envelope/vault
│ └── Document usage in access review log
│
└── Monthly: Verify break-glass accounts have not been used
without corresponding incident documentationScripts 2
agent.py5.7 KB
#!/usr/bin/env python3
"""Privileged Account Access Review agent — audits privileged accounts for
compliance with least-privilege and periodic recertification requirements."""
import argparse
import csv
import json
from datetime import datetime, timedelta
from pathlib import Path
def load_accounts(csv_path: str) -> list[dict]:
"""Load privileged account inventory from CSV."""
with open(csv_path, newline="", encoding="utf-8") as fh:
reader = csv.DictReader(fh)
return list(reader)
def check_stale_accounts(accounts: list[dict], max_days: int = 90) -> list[dict]:
"""Flag accounts not used within max_days."""
findings = []
cutoff = datetime.utcnow() - timedelta(days=max_days)
for acct in accounts:
last_used = acct.get("last_used", "")
if not last_used:
findings.append({"account": acct.get("username", ""), "issue": "no_last_used_date",
"severity": "high", "detail": "Account has no recorded last-used date"})
continue
try:
used_dt = datetime.strptime(last_used, "%Y-%m-%d")
if used_dt < cutoff:
findings.append({"account": acct.get("username", ""),
"issue": "stale_account", "severity": "high",
"detail": f"Last used {last_used}, exceeds {max_days}-day threshold"})
except ValueError:
findings.append({"account": acct.get("username", ""), "issue": "invalid_date",
"severity": "medium", "detail": f"Cannot parse last_used: {last_used}"})
return findings
def check_shared_accounts(accounts: list[dict]) -> list[dict]:
"""Detect shared/generic privileged accounts."""
shared_patterns = ["admin", "root", "service", "svc_", "shared", "generic", "temp"]
findings = []
for acct in accounts:
uname = acct.get("username", "").lower()
owner = acct.get("owner", "").strip()
for pat in shared_patterns:
if pat in uname and not owner:
findings.append({"account": acct.get("username", ""),
"issue": "shared_account_no_owner", "severity": "critical",
"detail": f"Appears shared (matches '{pat}') with no assigned owner"})
break
return findings
def check_excessive_privileges(accounts: list[dict]) -> list[dict]:
"""Flag accounts with overly broad privilege sets."""
high_risk_roles = {"domain admin", "enterprise admin", "schema admin",
"global admin", "super admin", "root"}
findings = []
for acct in accounts:
roles = {r.strip().lower() for r in acct.get("roles", "").split(";")}
overlap = roles & high_risk_roles
if overlap:
findings.append({"account": acct.get("username", ""),
"issue": "excessive_privilege", "severity": "critical",
"detail": f"Holds high-risk roles: {', '.join(sorted(overlap))}"})
return findings
def check_recertification(accounts: list[dict], cert_interval_days: int = 180) -> list[dict]:
"""Flag accounts overdue for recertification."""
cutoff = datetime.utcnow() - timedelta(days=cert_interval_days)
findings = []
for acct in accounts:
cert_date = acct.get("last_certified", "")
if not cert_date:
findings.append({"account": acct.get("username", ""),
"issue": "never_certified", "severity": "critical",
"detail": "Account has never been certified"})
continue
try:
cert_dt = datetime.strptime(cert_date, "%Y-%m-%d")
if cert_dt < cutoff:
findings.append({"account": acct.get("username", ""),
"issue": "overdue_recertification", "severity": "high",
"detail": f"Last certified {cert_date}, exceeds {cert_interval_days}-day cycle"})
except ValueError:
pass
return findings
def generate_report(accounts: list[dict], stale_days: int, cert_days: int) -> dict:
"""Run all checks and produce a consolidated JSON report."""
findings = []
findings.extend(check_stale_accounts(accounts, stale_days))
findings.extend(check_shared_accounts(accounts))
findings.extend(check_excessive_privileges(accounts))
findings.extend(check_recertification(accounts, cert_days))
severity_counts = {}
for f in findings:
severity_counts[f["severity"]] = severity_counts.get(f["severity"], 0) + 1
return {
"report": "privileged_account_access_review",
"generated_at": datetime.utcnow().isoformat() + "Z",
"total_accounts": len(accounts),
"total_findings": len(findings),
"severity_summary": severity_counts,
"findings": findings,
}
def main():
parser = argparse.ArgumentParser(description="Privileged Account Access Review Agent")
parser.add_argument("--input", required=True, help="CSV file with privileged account inventory")
parser.add_argument("--stale-days", type=int, default=90, help="Max days of inactivity (default: 90)")
parser.add_argument("--cert-days", type=int, default=180, help="Recertification interval in days (default: 180)")
parser.add_argument("--output", help="Output JSON file path")
args = parser.parse_args()
accounts = load_accounts(args.input)
report = generate_report(accounts, args.stale_days, args.cert_days)
output = json.dumps(report, indent=2)
if args.output:
Path(args.output).write_text(output, encoding="utf-8")
print(f"Report written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()
process.py12.4 KB
#!/usr/bin/env python3
"""
Privileged Account Access Review Automation
Discovers privileged accounts from Active Directory, AWS IAM, and Azure AD,
generates review campaigns, and tracks certification decisions.
Requirements:
pip install ldap3 boto3 msal requests pandas openpyxl
"""
import json
import csv
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
try:
import ldap3
HAS_LDAP = True
except ImportError:
HAS_LDAP = False
try:
import boto3
HAS_BOTO = True
except ImportError:
HAS_BOTO = False
class PrivilegedAccountDiscovery:
"""Discovers privileged accounts across multiple platforms."""
def __init__(self):
self.accounts = []
def discover_ad_privileged_accounts(self, server_address, domain_dn,
bind_user, bind_password):
"""Enumerate privileged accounts from Active Directory."""
if not HAS_LDAP:
print("[WARN] ldap3 not installed, skipping AD discovery")
return []
server = ldap3.Server(server_address, use_ssl=True, get_info=ldap3.ALL)
conn = ldap3.Connection(server, user=bind_user, password=bind_password,
auto_bind=True)
privileged_groups = [
"Domain Admins",
"Enterprise Admins",
"Schema Admins",
"Administrators",
"Account Operators",
"Backup Operators",
"Server Operators",
"Print Operators",
]
discovered = []
for group_name in privileged_groups:
search_filter = f"(&(objectClass=group)(cn={group_name}))"
conn.search(domain_dn, search_filter,
attributes=["member", "distinguishedName"])
for entry in conn.entries:
members = entry.member.values if hasattr(entry.member, "values") else []
for member_dn in members:
conn.search(member_dn, "(objectClass=user)",
attributes=["sAMAccountName", "displayName",
"mail", "lastLogonTimestamp",
"whenCreated", "userAccountControl",
"adminCount", "servicePrincipalName"])
for user_entry in conn.entries:
uac = int(str(user_entry.userAccountControl)) if user_entry.userAccountControl else 0
is_disabled = bool(uac & 0x0002)
is_service = bool(user_entry.servicePrincipalName)
account = {
"platform": "Active Directory",
"username": str(user_entry.sAMAccountName),
"display_name": str(user_entry.displayName) if user_entry.displayName else "",
"email": str(user_entry.mail) if user_entry.mail else "",
"privileged_group": group_name,
"account_type": "service" if is_service else "user",
"is_disabled": is_disabled,
"created_date": str(user_entry.whenCreated) if user_entry.whenCreated else "",
"last_logon": str(user_entry.lastLogonTimestamp) if user_entry.lastLogonTimestamp else "Never",
"admin_count": str(user_entry.adminCount) if user_entry.adminCount else "0",
"risk_level": "Critical" if group_name in ["Domain Admins", "Enterprise Admins"] else "High",
}
discovered.append(account)
conn.unbind()
self.accounts.extend(discovered)
return discovered
def discover_aws_privileged_accounts(self, profile_name=None):
"""Enumerate privileged IAM users and roles in AWS."""
if not HAS_BOTO:
print("[WARN] boto3 not installed, skipping AWS discovery")
return []
session = boto3.Session(profile_name=profile_name)
iam = session.client("iam")
discovered = []
admin_policies = [
"arn:aws:iam::aws:policy/AdministratorAccess",
"arn:aws:iam::aws:policy/IAMFullAccess",
"arn:aws:iam::aws:policy/PowerUserAccess",
]
paginator = iam.get_paginator("list_users")
for page in paginator.paginate():
for user in page["Users"]:
username = user["UserName"]
create_date = user["CreateDate"]
attached_policies = iam.list_attached_user_policies(UserName=username)
user_policies = [p["PolicyArn"] for p in attached_policies["AttachedPolicies"]]
is_admin = any(p in admin_policies for p in user_policies)
user_groups = iam.list_groups_for_user(UserName=username)
for group in user_groups["Groups"]:
group_policies = iam.list_attached_group_policies(GroupName=group["GroupName"])
for gp in group_policies["AttachedPolicies"]:
if gp["PolicyArn"] in admin_policies:
is_admin = True
if is_admin:
try:
last_used = iam.get_user(UserName=username)
password_last_used = user.get("PasswordLastUsed", "Never")
except Exception:
password_last_used = "Unknown"
mfa_devices = iam.list_mfa_devices(UserName=username)
has_mfa = len(mfa_devices["MFADevices"]) > 0
access_keys = iam.list_access_keys(UserName=username)
active_keys = [k for k in access_keys["AccessKeyMetadata"]
if k["Status"] == "Active"]
account = {
"platform": "AWS IAM",
"username": username,
"display_name": username,
"email": "",
"privileged_group": ", ".join(user_policies),
"account_type": "user",
"is_disabled": False,
"created_date": create_date.isoformat(),
"last_logon": str(password_last_used),
"admin_count": str(len(active_keys)),
"risk_level": "Critical",
"mfa_enabled": has_mfa,
"active_access_keys": len(active_keys),
}
discovered.append(account)
self.accounts.extend(discovered)
return discovered
def generate_review_report(self, output_path):
"""Generate CSV report of all discovered privileged accounts for review."""
if not self.accounts:
print("[INFO] No accounts discovered. Run discovery methods first.")
return
fieldnames = [
"platform", "username", "display_name", "email",
"privileged_group", "account_type", "is_disabled",
"created_date", "last_logon", "risk_level",
"reviewer", "decision", "justification", "review_date"
]
with open(output_path, "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction="ignore")
writer.writeheader()
for account in self.accounts:
account.setdefault("reviewer", "")
account.setdefault("decision", "")
account.setdefault("justification", "")
account.setdefault("review_date", "")
writer.writerow(account)
print(f"[OK] Review report generated: {output_path}")
print(f"[OK] Total privileged accounts: {len(self.accounts)}")
critical = sum(1 for a in self.accounts if a["risk_level"] == "Critical")
high = sum(1 for a in self.accounts if a["risk_level"] == "High")
disabled = sum(1 for a in self.accounts if a.get("is_disabled"))
print(f" Critical: {critical} | High: {high} | Disabled: {disabled}")
class AccessReviewTracker:
"""Tracks review campaign progress and generates compliance reports."""
def __init__(self, review_file):
self.review_file = Path(review_file)
self.accounts = []
if self.review_file.exists():
self._load_reviews()
def _load_reviews(self):
with open(self.review_file, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
self.accounts = list(reader)
def get_review_status(self):
"""Calculate review campaign metrics."""
total = len(self.accounts)
reviewed = sum(1 for a in self.accounts if a.get("decision"))
approved = sum(1 for a in self.accounts if a.get("decision") == "Approve")
revoked = sum(1 for a in self.accounts if a.get("decision") == "Revoke")
flagged = sum(1 for a in self.accounts if a.get("decision") == "Flag")
pending = total - reviewed
return {
"total_accounts": total,
"reviewed": reviewed,
"pending": pending,
"approved": approved,
"revoked": revoked,
"flagged": flagged,
"completion_rate": f"{(reviewed/total*100):.1f}%" if total else "0%",
}
def identify_dormant_accounts(self, days_threshold=90):
"""Find accounts not used within the threshold period."""
cutoff = datetime.now(timezone.utc) - timedelta(days=days_threshold)
dormant = []
for account in self.accounts:
last_logon = account.get("last_logon", "")
if last_logon in ("Never", "", "Unknown"):
dormant.append(account)
continue
try:
logon_date = datetime.fromisoformat(last_logon.replace("Z", "+00:00"))
if logon_date < cutoff:
dormant.append(account)
except (ValueError, TypeError):
continue
return dormant
def generate_compliance_report(self, output_path):
"""Generate compliance-ready review summary."""
status = self.get_review_status()
dormant = self.identify_dormant_accounts()
report = {
"report_title": "Privileged Account Access Review - Compliance Report",
"generated_date": datetime.now(timezone.utc).isoformat(),
"review_period": "Quarterly",
"metrics": status,
"dormant_accounts": len(dormant),
"dormant_account_list": [
{"username": a["username"], "platform": a["platform"],
"last_logon": a.get("last_logon", "Unknown")}
for a in dormant
],
"findings": [],
}
if status["pending"] > 0:
report["findings"].append({
"finding": f"{status['pending']} accounts have not been reviewed",
"severity": "High",
"recommendation": "Complete reviews within SLA or auto-revoke"
})
if dormant:
report["findings"].append({
"finding": f"{len(dormant)} dormant privileged accounts detected",
"severity": "Critical",
"recommendation": "Disable dormant accounts and rotate credentials"
})
with open(output_path, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
print(f"[OK] Compliance report generated: {output_path}")
return report
if __name__ == "__main__":
discovery = PrivilegedAccountDiscovery()
print("=" * 60)
print("Privileged Account Access Review Tool")
print("=" * 60)
print()
print("Usage:")
print(" 1. Run discovery against your environment")
print(" 2. Generate review CSV for reviewer certification")
print(" 3. Track review progress and generate compliance report")
print()
print("Example:")
print(" discovery = PrivilegedAccountDiscovery()")
print(" discovery.discover_ad_privileged_accounts(server, dn, user, pw)")
print(" discovery.discover_aws_privileged_accounts(profile='prod')")
print(" discovery.generate_review_report('review_campaign.csv')")
print()
print(" tracker = AccessReviewTracker('review_campaign.csv')")
print(" print(tracker.get_review_status())")
print(" tracker.generate_compliance_report('compliance_report.json')")