#!/usr/bin/env python3
"""CyberArk PAM configuration audit agent.

Audits CyberArk Privileged Access Management via the REST API to
verify safe configurations, privileged account inventory, platform
assignments, and password rotation compliance.
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone

try:
    import requests
except ImportError:
    print("[!] 'requests' required: pip install requests", file=sys.stderr)
    sys.exit(1)


def get_cyberark_config():
    """Return CyberArk PVWA URL."""
    pvwa_url = os.environ.get("CYBERARK_PVWA_URL", "").rstrip("/")
    if not pvwa_url:
        print("[!] Set CYBERARK_PVWA_URL env var", file=sys.stderr)
        sys.exit(1)
    return pvwa_url


def authenticate(pvwa_url, username, password, auth_type="CyberArk"):
    """Authenticate and get session token."""
    url = f"{pvwa_url}/PasswordVault/API/Auth/{auth_type}/Logon"
    resp = requests.post(url, json={"username": username, "password": password},
                         verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    token = resp.json().strip('"')
    print(f"[+] Authenticated as {username}")
    return token


def api_call(pvwa_url, token, endpoint, method="GET", data=None, params=None):
    """Make authenticated API call."""
    url = f"{pvwa_url}/PasswordVault/API{endpoint}"
    headers = {"Authorization": token, "Content-Type": "application/json"}
    if method == "POST":
        resp = requests.post(url, headers=headers, json=data, params=params,
                             verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    else:
        resp = requests.get(url, headers=headers, params=params,
                            verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
    resp.raise_for_status()
    return resp.json()


def audit_safes(pvwa_url, token):
    """Audit safe configurations."""
    findings = []
    print("[*] Auditing safes...")
    data = api_call(pvwa_url, token, "/Safes", params={"limit": 1000})
    safes = data.get("value", data.get("Safes", []))

    for safe in safes:
        name = safe.get("safeName", safe.get("SafeName", ""))
        member_count = safe.get("numberOfMembers", safe.get("NumberOfMembers", 0))
        days_retention = safe.get("numberOfDaysRetention", safe.get("NumberOfDaysRetention", 0))
        versions = safe.get("numberOfVersionsRetention", safe.get("NumberOfVersionsRetention", 0))

        if member_count == 0:
            findings.append({
                "safe": name, "check": "Empty safe (no members)",
                "severity": "MEDIUM", "detail": "Safe has no members assigned",
            })
        if days_retention == 0 and versions == 0:
            findings.append({
                "safe": name, "check": "No retention policy",
                "severity": "HIGH",
                "detail": "No password history retention configured",
            })

    print(f"[+] Audited {len(safes)} safes")
    return findings, safes


def audit_accounts(pvwa_url, token, safe_name=None):
    """Audit privileged accounts."""
    findings = []
    print("[*] Auditing privileged accounts...")
    params = {"limit": 1000}
    if safe_name:
        params["filter"] = f"safeName eq {safe_name}"
    data = api_call(pvwa_url, token, "/Accounts", params=params)
    accounts = data.get("value", [])

    now = datetime.now(timezone.utc)
    for acct in accounts:
        acct_name = acct.get("name", "")
        platform = acct.get("platformId", "")
        safe = acct.get("safeName", "")
        secret_mgmt = acct.get("secretManagement", {})
        last_modified = secret_mgmt.get("lastModifiedTime", 0)
        auto_mgmt = secret_mgmt.get("automaticManagementEnabled", False)
        status = secret_mgmt.get("status", "")

        if not auto_mgmt:
            findings.append({
                "account": acct_name, "safe": safe, "platform": platform,
                "check": "Automatic password management disabled",
                "severity": "HIGH",
                "detail": "Password not managed by CyberArk CPM",
            })

        if last_modified:
            last_mod_dt = datetime.fromtimestamp(last_modified, tz=timezone.utc)
            age_days = (now - last_mod_dt).days
            if age_days > 90:
                findings.append({
                    "account": acct_name, "safe": safe,
                    "check": "Password age exceeds 90 days",
                    "severity": "HIGH",
                    "detail": f"Last rotated {age_days} days ago",
                })

        if status and status != "success":
            findings.append({
                "account": acct_name, "safe": safe,
                "check": f"CPM status: {status}",
                "severity": "CRITICAL" if "fail" in status.lower() else "MEDIUM",
                "detail": f"Password management status: {status}",
            })

    print(f"[+] Audited {len(accounts)} accounts")
    return findings, accounts


def audit_platforms(pvwa_url, token):
    """Audit platform configurations."""
    findings = []
    print("[*] Auditing platforms...")
    data = api_call(pvwa_url, token, "/Platforms")
    platforms = data.get("Platforms", data.get("value", []))

    for plat in platforms:
        name = plat.get("Name", plat.get("PlatformID", ""))
        active = plat.get("Active", True)
        if not active:
            continue
        priv_session = plat.get("PrivilegedSessionManagement", {})
        if not priv_session.get("PSMServerId"):
            findings.append({
                "platform": name,
                "check": "No PSM configured",
                "severity": "MEDIUM",
                "detail": "Platform missing privileged session management",
            })

    print(f"[+] Audited {len(platforms)} platforms")
    return findings, platforms


def logoff(pvwa_url, token):
    """End the CyberArk session."""
    try:
        requests.post(f"{pvwa_url}/PasswordVault/API/Auth/Logoff",
                      headers={"Authorization": token},
                      verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=10)  # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments
        print("[+] Session ended")
    except requests.RequestException:
        pass


def format_summary(safe_findings, acct_findings, plat_findings, safes, accounts):
    """Print audit summary."""
    all_findings = safe_findings + acct_findings + plat_findings
    print(f"\n{'='*60}")
    print(f"  CyberArk PAM Audit Report")
    print(f"{'='*60}")
    print(f"  Safes        : {len(safes)}")
    print(f"  Accounts     : {len(accounts)}")
    print(f"  Findings     : {len(all_findings)}")

    severity_counts = {}
    for f in all_findings:
        sev = f.get("severity", "INFO")
        severity_counts[sev] = severity_counts.get(sev, 0) + 1

    print(f"\n  By Severity:")
    for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
        count = severity_counts.get(sev, 0)
        if count:
            print(f"    {sev:10s}: {count}")

    if all_findings:
        print(f"\n  Top Issues:")
        for f in all_findings[:15]:
            if f["severity"] in ("CRITICAL", "HIGH"):
                print(f"    [{f['severity']:8s}] {f['check']}: "
                      f"{f.get('account', f.get('safe', f.get('platform', '')))}")

    return severity_counts


def main():
    parser = argparse.ArgumentParser(description="CyberArk PAM configuration audit agent")
    parser.add_argument("--pvwa-url", help="PVWA URL (or CYBERARK_PVWA_URL env)")
    parser.add_argument("--username", required=True, help="CyberArk username")
    parser.add_argument("--password", required=True, help="CyberArk password")
    parser.add_argument("--auth-type", default="CyberArk",
                        choices=["CyberArk", "LDAP", "RADIUS", "Windows"])
    parser.add_argument("--safe", help="Audit specific safe only")
    parser.add_argument("--output", "-o", help="Output JSON report")
    parser.add_argument("--verbose", "-v", action="store_true")
    args = parser.parse_args()

    if args.pvwa_url:
        os.environ["CYBERARK_PVWA_URL"] = args.pvwa_url
    pvwa_url = get_cyberark_config()

    token = authenticate(pvwa_url, args.username, args.password, args.auth_type)
    try:
        safe_findings, safes = audit_safes(pvwa_url, token)
        acct_findings, accounts = audit_accounts(pvwa_url, token, args.safe)
        plat_findings, platforms = audit_platforms(pvwa_url, token)
    finally:
        logoff(pvwa_url, token)

    severity_counts = format_summary(safe_findings, acct_findings, plat_findings, safes, accounts)

    report = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "tool": "CyberArk PAM Audit",
        "safes_count": len(safes),
        "accounts_count": len(accounts),
        "findings": safe_findings + acct_findings + plat_findings,
        "severity_counts": severity_counts,
        "risk_level": (
            "CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
            else "HIGH" if severity_counts.get("HIGH", 0) > 0
            else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
            else "LOW"
        ),
    }

    if args.output:
        with open(args.output, "w") as f:
            json.dump(report, f, indent=2)
        print(f"\n[+] Report saved to {args.output}")
    elif args.verbose:
        print(json.dumps(report, indent=2))


if __name__ == "__main__":
    main()
