identity access management

Performing Privileged Account Access Review

Conduct systematic reviews of privileged accounts to validate access rights, identify excessive permissions, and enforce least privilege across PAM infrastructure.

access-reviewauditcomplianceidentity-governanceleast-privilegepamprivileged-accounts
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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                  logging

Workflow

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:

  1. Business Justification: Does the user's current role require this privilege?
  2. Least Privilege: Can the task be performed with lower privileges?
  3. Account Activity: Has the account been active in the last 90 days?
  4. Compliance Status: Does the account meet password policy, MFA requirements?
  5. Separation of Duties: Does the access create SoD conflicts?
  6. Ownership: Is a responsible owner assigned and active?

Step 3: Conduct the Review

For each account, the designated reviewer must:

  1. Review the account details, permissions, and last activity date
  2. Approve (certify) the access if still required with documented justification
  3. Revoke access if no longer needed or the reviewer cannot justify the privilege
  4. Flag for investigation if anomalous activity or policy violations are detected
  5. 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

Source materials

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.json
standards.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 report

Account 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 vault

Reviewer 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 justification

Emergency 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 documentation

Scripts 2

agent.py5.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/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
Display-only source. This catalog never executes bundled scripts.
#!/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')")

Assets 1

template.mdtext/markdown · 1.7 KB
Keep exploring