#!/usr/bin/env python3
"""Agent for auditing CyberArk Zero Standing Privilege (ZSP) configuration via REST API."""

import os
import requests
import json
import argparse
from datetime import datetime, timezone
import urllib3

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def authenticate(base_url, username, password, auth_method="CyberArk"):
    """Authenticate to CyberArk PVWA and obtain session token."""
    url = f"{base_url}/api/auth/{auth_method}/Logon"
    payload = {"username": username, "password": password}
    resp = requests.post(url, json=payload, 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 to CyberArk PVWA as {username}")
    return {"Authorization": token}


def list_safes(base_url, headers):
    """List all safes to audit access policies."""
    url = f"{base_url}/api/Safes"
    resp = requests.get(url, headers=headers, 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()
    safes = resp.json().get("value", [])
    print(f"[*] Found {len(safes)} safes")
    for s in safes[:20]:
        print(f"  {s['safeName']} (retention: {s.get('numberOfDaysRetention', 'N/A')} days)")
    return safes


def audit_safe_members(base_url, headers, safe_name):
    """Audit members and permissions of a specific safe."""
    url = f"{base_url}/api/Safes/{safe_name}/Members"
    resp = requests.get(url, headers=headers, 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()
    members = resp.json().get("value", [])
    findings = []
    for m in members:
        perms = m.get("permissions", {})
        if perms.get("useAccounts") and perms.get("retrieveAccounts"):
            if not m.get("memberType") == "Role":
                findings.append({
                    "safe": safe_name, "member": m.get("memberName"),
                    "issue": "Standing retrieve+use privileges (not JIT)",
                    "severity": "HIGH",
                })
                print(f"  [!] {m.get('memberName')} has standing access to {safe_name}")
    return findings


def list_platforms(base_url, headers):
    """List platforms to verify JIT/ZSP configuration."""
    url = f"{base_url}/api/Platforms"
    resp = requests.get(url, headers=headers, 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()
    platforms = resp.json().get("Platforms", [])
    print(f"[*] Found {len(platforms)} platforms")
    for p in platforms:
        print(f"  {p.get('general', {}).get('name', 'Unknown')} - "
              f"Active: {p.get('general', {}).get('active', False)}")
    return platforms


def check_jit_sessions(base_url, headers, days=7):
    """Check recent privileged sessions for JIT compliance."""
    url = f"{base_url}/api/LiveSessions"
    resp = requests.get(url, headers=headers, 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()
    sessions = resp.json().get("LiveSessions", [])
    print(f"[*] Active privileged sessions: {len(sessions)}")
    long_sessions = []
    for s in sessions:
        duration = s.get("Duration", 0)
        if duration > 3600:
            long_sessions.append({
                "user": s.get("User"), "target": s.get("AccountName"),
                "duration_sec": duration, "severity": "MEDIUM",
            })
            print(f"  [!] Long session: {s.get('User')} -> {s.get('AccountName')} "
                  f"({duration // 60} min)")
    return long_sessions


def audit_accounts_standing_access(base_url, headers):
    """Find privileged accounts with standing (non-JIT) access enabled."""
    url = f"{base_url}/api/Accounts"
    params = {"limit": 100, "offset": 0}
    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()
    accounts = resp.json().get("value", [])
    findings = []
    for a in accounts:
        props = a.get("platformAccountProperties", {})
        if not props.get("JITEnabled", False):
            findings.append({
                "account": a.get("name"), "safe": a.get("safeName"),
                "platform": a.get("platformId"), "issue": "JIT not enabled",
                "severity": "HIGH",
            })
    print(f"[*] Accounts without JIT: {len(findings)}/{len(accounts)}")
    return findings


def generate_report(safe_findings, session_findings, account_findings, output_path):
    """Generate ZSP compliance audit report."""
    report = {
        "audit_date": datetime.now(timezone.utc).isoformat(),
        "summary": {
            "standing_access_findings": len(safe_findings),
            "long_session_findings": len(session_findings),
            "non_jit_accounts": len(account_findings),
        },
        "safe_findings": safe_findings,
        "session_findings": session_findings,
        "account_findings": account_findings,
    }
    with open(output_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n[*] Report saved to {output_path}")


def main():
    parser = argparse.ArgumentParser(description="CyberArk Zero Standing Privilege Audit Agent")
    parser.add_argument("action", choices=["safes", "audit-safe", "platforms",
                                           "sessions", "accounts", "full-audit"])
    parser.add_argument("--url", required=True, help="CyberArk PVWA base URL")
    parser.add_argument("--username", required=True)
    parser.add_argument("--password", required=True)
    parser.add_argument("--safe", help="Specific safe name to audit")
    parser.add_argument("-o", "--output", default="zsp_audit.json")
    args = parser.parse_args()

    headers = authenticate(args.url, args.username, args.password)
    if args.action == "safes":
        list_safes(args.url, headers)
    elif args.action == "audit-safe" and args.safe:
        audit_safe_members(args.url, headers, args.safe)
    elif args.action == "platforms":
        list_platforms(args.url, headers)
    elif args.action == "sessions":
        check_jit_sessions(args.url, headers)
    elif args.action == "accounts":
        audit_accounts_standing_access(args.url, headers)
    elif args.action == "full-audit":
        safes = list_safes(args.url, headers)
        sf = []
        for s in safes:
            sf.extend(audit_safe_members(args.url, headers, s["safeName"]))
        sess = check_jit_sessions(args.url, headers)
        acct = audit_accounts_standing_access(args.url, headers)
        generate_report(sf, sess, acct, args.output)


if __name__ == "__main__":
    main()
