Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
Overview
Deploy CyberArk Privileged Access Management to discover, vault, rotate, and monitor privileged credentials across enterprise infrastructure. This skill covers vault architecture, session isolation, credential rotation policies, and integration with NIST 800-53 access control requirements.
When to Use
- When deploying or configuring implementing privileged access management with cyberark capabilities in your environment
- When establishing security controls aligned to compliance requirements
- When building or improving security architecture for this domain
- When conducting security assessments that require this implementation
Prerequisites
- Familiarity with identity access management concepts and tools
- Access to a test or lab environment for safe execution
- Python 3.8+ with required dependencies installed
- Appropriate authorization for any testing activities
Objectives
- Design CyberArk vault architecture with high availability
- Implement automated privileged credential discovery and onboarding
- Configure credential rotation policies for different account types
- Deploy Privileged Session Manager (PSM) for session isolation and recording
- Integrate CyberArk with SIEM for privileged access monitoring
- Implement just-in-time (JIT) privileged access workflows
Key Concepts
CyberArk Architecture Components
- Digital Vault: Encrypted credential storage with FIPS 140-2 validated encryption
- Central Policy Manager (CPM): Automated password rotation and verification
- Privileged Session Manager (PSM): Session isolation, recording, and keystroke logging
- Password Vault Web Access (PVWA): Web interface for credential management
- Privileged Threat Analytics (PTA): Behavioral analytics for privileged accounts
- Conjur Secrets Manager: Application identity and secrets management
Vault Security Model
- Master Policy: Global security settings (dual control, exclusive access, one-time passwords)
- Safes: Logical containers for credentials with granular permissions
- Platforms: Configuration profiles defining rotation, verification, and reconciliation
- Account Groups: Link accounts sharing rotation dependencies
Credential Lifecycle
- Discovery: Scan infrastructure for privileged accounts
- Onboarding: Import accounts into vault with platform assignment
- Rotation: Automated password changes per policy schedule
- Verification: Periodic validation that vaulted credentials work
- Reconciliation: Re-sync credentials when vault and target are out of sync
- Decommissioning: Remove accounts no longer needed
Workflow
Step 1: Vault Architecture Design
- Deploy primary vault server in secured network segment
- Configure vault high availability with DR vault
- Harden vault server OS (remove unnecessary services, disable RDP)
- Configure firewall rules (only port 1858 from authorized components)
- Set up vault backup with encryption
Step 2: Safe and Policy Configuration
- Create safe hierarchy aligned with business units
- Define safe members with least-privilege roles:
- Safe Admins: manage safe membership
- Credential Managers: add/modify accounts
- Auditors: view audit logs only
- Users: retrieve/use credentials
- Configure Master Policy settings:
- Require dual control for credential retrieval
- Enable exclusive access (one user per credential at a time)
- Set one-time password mode for sensitive accounts
Step 3: Platform Configuration
- Windows Domain Admin: Rotate every 24 hours, verify every 4 hours
- Linux Root: Rotate every 72 hours with SSH key rotation
- Database Admin (Oracle, SQL Server): Rotate every 24 hours
- Network Devices: Rotate every 7 days
- Service Accounts: Rotate on schedule with dependency management
- Cloud IAM Keys: Rotate every 90 days with dual-key strategy
Step 4: Privileged Session Management
- Deploy PSM servers behind load balancer
- Configure session recording (video, keystroke, command logs)
- Set up session isolation (users connect through PSM, never directly)
- Define connection components for RDP, SSH, databases, web apps
- Configure live session monitoring and termination capabilities
- Set session recording retention (minimum 1 year for compliance)
Step 5: Integration and Monitoring
- Forward CyberArk audit logs to SIEM (CEF/Syslog format)
- Configure PTA for behavioral analytics:
- Detect credential theft indicators
- Alert on suspicious privileged session activity
- Monitor unmanaged privileged account usage
- Integrate with ticketing system for access request workflows
- Set up alerts for failed rotation, verification failures, policy violations
Security Controls
| Control | NIST 800-53 | Description |
|---|---|---|
| Privileged Access | AC-6(7) | Privileged account controls |
| Credential Management | IA-5 | Automated credential rotation |
| Session Recording | AU-14 | Session audit capability |
| Access Enforcement | AC-3 | Vault-enforced access policies |
| Separation of Duties | AC-5 | Dual control for sensitive operations |
Common Pitfalls
- Not configuring reconciliation accounts leading to lockouts after rotation
- Setting rotation schedules too aggressive for service accounts with dependencies
- Failing to test PSM connection components before production deployment
- Not establishing break-glass procedures for vault unavailability
- Overlooking network device credential management
Verification
- Vault accessible only from authorized components
- Credential rotation succeeds for all onboarded accounts
- PSM sessions recorded and searchable
- Dual control enforced for sensitive credential checkout
- SIEM receives CyberArk audit events
- Break-glass procedure tested and documented
- DR vault failover tested successfully
Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md4.5 KB
API Reference: CyberArk Privileged Access Management
Libraries Used
| Library | Purpose |
|---|---|
requests |
HTTP client for CyberArk PVWA REST API |
json |
Parse CyberArk JSON responses |
os |
Read environment variables for credentials |
urllib.parse |
URL-encode safe and account query parameters |
Installation
pip install requestsAuthentication
CyberArk PVWA REST API requires session token authentication:
import requests
import os
PVWA_URL = os.environ.get("CYBERARK_URL", "https://pvwa.example.com")
# CyberArk credential authentication
resp = requests.post(
f"{PVWA_URL}/PasswordVault/api/auth/cyberark/logon",
json={
"username": os.environ["CYBERARK_USER"],
"password": os.environ["CYBERARK_PASS"],
},
timeout=30,
verify=True,
)
session_token = resp.json() # Returns session token string
headers = {"Authorization": session_token}LDAP Authentication
resp = requests.post(
f"{PVWA_URL}/PasswordVault/api/auth/ldap/logon",
json={"username": user, "password": password},
timeout=30,
verify=True,
)RADIUS Authentication
resp = requests.post(
f"{PVWA_URL}/PasswordVault/api/auth/radius/logon",
json={"username": user, "password": otp_code},
timeout=30,
verify=True,
)REST API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/{method}/logon |
Authenticate (cyberark, ldap, radius) |
| POST | /api/auth/logoff |
End session |
| GET | /api/Accounts |
List privileged accounts |
| GET | /api/Accounts/{id} |
Get account details |
| POST | /api/Accounts |
Add a new privileged account |
| PATCH | /api/Accounts/{id} |
Update account properties |
| DELETE | /api/Accounts/{id} |
Delete an account |
| POST | /api/Accounts/{id}/Password/Retrieve |
Retrieve account password |
| POST | /api/Accounts/{id}/Change |
Trigger password change |
| POST | /api/Accounts/{id}/Reconcile |
Reconcile password |
| POST | /api/Accounts/{id}/Verify |
Verify password on target |
| GET | /api/Safes |
List safes |
| GET | /api/Safes/{name} |
Get safe details |
| POST | /api/Safes |
Create a safe |
| GET | /api/Safes/{name}/Members |
List safe members |
| POST | /api/Safes/{name}/Members |
Add safe member |
| GET | /api/Platforms |
List platforms |
| GET | /api/ComponentsMonitoringDetails/{component} |
System health |
Core Operations
List Privileged Accounts
resp = requests.get(
f"{PVWA_URL}/PasswordVault/api/Accounts",
headers=headers,
params={"search": "Linux", "limit": 100},
timeout=30,
verify=True,
)
accounts = resp.json()
for acct in accounts.get("value", []):
print(f"{acct['name']} — platform: {acct['platformId']}, safe: {acct['safeName']}")Retrieve a Password (Check-Out)
resp = requests.post(
f"{PVWA_URL}/PasswordVault/api/Accounts/{account_id}/Password/Retrieve",
headers=headers,
json={"reason": "Automated security audit"},
timeout=30,
verify=True,
)
password = resp.text # Returns the password as plain textList Safes and Audit Permissions
resp = requests.get(
f"{PVWA_URL}/PasswordVault/api/Safes",
headers=headers,
params={"limit": 200},
timeout=30,
verify=True,
)
for safe in resp.json().get("value", []):
members_resp = requests.get(
f"{PVWA_URL}/PasswordVault/api/Safes/{safe['safeName']}/Members",
headers=headers,
timeout=30,
verify=True,
)
members = members_resp.json().get("value", [])
print(f"Safe: {safe['safeName']} — {len(members)} members")Trigger Password Rotation
resp = requests.post(
f"{PVWA_URL}/PasswordVault/api/Accounts/{account_id}/Change",
headers=headers,
json={"ChangeEntireGroup": False},
timeout=60,
verify=True,
)Logoff
requests.post(
f"{PVWA_URL}/PasswordVault/api/auth/logoff",
headers=headers,
timeout=10,
verify=True,
)Output Format
{
"value": [
{
"id": "42_8",
"name": "root-linux-prod01",
"address": "10.0.1.50",
"userName": "root",
"platformId": "UnixSSH",
"safeName": "LinuxRoot",
"secretType": "password",
"platformAccountProperties": {
"LogonDomain": "",
"Port": "22"
},
"secretManagement": {
"automaticManagementEnabled": true,
"lastModifiedTime": 1705334400
}
}
],
"count": 1
}standards.md1.5 KB
Standards and References - Privileged Access Management with CyberArk
NIST Standards
- NIST SP 800-53 Rev 5: Security and Privacy Controls
- AC-2: Account Management
- AC-5: Separation of Duties
- AC-6: Least Privilege
- AC-6(7): Review of User Privileges (Privileged Accounts)
- AU-14: Session Audit
- IA-5: Authenticator Management
- NIST SP 800-171: Protecting CUI - 3.1.5 Least Privilege, 3.1.7 Privileged Functions
- NIST SP 800-63B: Digital Identity Guidelines - Authentication
- NIST Cybersecurity Framework: PR.AC (Identity Management, Authentication, Access Control)
CyberArk Documentation
- CyberArk NIST 800-53 Rev 5 Whitepaper: https://www.cyberark.com/resources/white-papers/nist-sp-800-53-revision-5-implementing-essential-security-controls-with-cyberark-solutions
- CyberArk Privilege Cloud: SaaS PAM platform
- CyberArk Conjur: Application secrets management
- CyberArk EPM: Endpoint privilege management
Industry Standards
- CIS Controls v8: Control 5 - Account Management, Control 6 - Access Control Management
- MITRE ATT&CK: T1078 (Valid Accounts), T1003 (OS Credential Dumping)
- PCI DSS 4.0: Requirement 7 (Restrict Access), Requirement 8 (Identify and Authenticate)
- SOX: Section 404 - Internal controls for privileged access
- ISO 27001: A.9 Access Control
Compliance Frameworks
- FISMA: Federal compliance requiring NIST 800-53 controls
- HIPAA: Access controls for PHI systems
- GDPR: Article 32 - Security of processing
workflows.md3.0 KB
Privileged Access Management Workflows
Workflow 1: Privileged Credential Checkout and Use
User -> PVWA -> Request Credential -> Dual Control Approval -> Vault Release -> PSM Session -> Target SystemSteps:
- User authenticates to PVWA with MFA
- User requests access to privileged account
- If dual control enabled, request routed to approver
- Approver reviews and approves/denies request
- Vault releases credential through PSM
- User connects to target via PSM (never sees password)
- Session recorded (video, keystrokes, commands)
- On disconnect, credential checked back in
- If one-time password mode, CPM rotates credential immediately
Workflow 2: Automated Credential Rotation
Steps:
- CPM checks rotation schedule for each platform
- CPM connects to target system using reconciliation account
- CPM generates new password meeting complexity requirements
- CPM changes password on target system
- CPM updates password in vault
- CPM verifies new credential works on target
- If verification fails, CPM triggers reconciliation
- Rotation event logged to audit trail
- SIEM alert triggered on rotation failure
Workflow 3: Privileged Account Discovery
Steps:
- Configure account discovery scan targets (IP ranges, domains)
- Discovery scanner connects to targets using scanning credentials
- Scanner identifies privileged accounts:
- Windows: Local admins, domain admins, service accounts
- Linux: root, sudoers, service accounts
- Database: DBA accounts, application accounts
- Network: admin/enable accounts on switches/routers
- Discovered accounts compared against vault inventory
- Unmanaged accounts flagged for review
- Security team reviews and prioritizes onboarding
- Approved accounts onboarded to appropriate safes
- CPM begins credential rotation per platform policy
Workflow 4: Break-Glass Emergency Access
Steps:
- Normal vault access unavailable (outage, disaster)
- Authorized personnel retrieve break-glass media (sealed envelope, USB)
- Break-glass credentials used to access critical systems directly
- All actions taken with break-glass credentials manually documented
- When vault service restored, all break-glass credentials rotated immediately
- Break-glass media re-sealed with new credentials
- Incident report created documenting break-glass usage
- All actions performed during break-glass reviewed by security team
Workflow 5: Incident Response - Compromised Privileged Account
Steps:
- PTA detects anomalous privileged account behavior
- Alert generated with risk score and indicators
- Security analyst reviews alert in PVWA/SIEM
- If confirmed compromise: a. Immediately rotate compromised credential via CPM b. Terminate any active PSM sessions using that account c. Review session recordings for malicious activity d. Check for lateral movement using audit logs e. Assess blast radius of compromised privilege level
- Forensic analysis of session recordings
- Post-incident review and policy updates
Scripts 2
agent.py9.6 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()
process.py16.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
CyberArk PAM Health Monitor and Audit Script
Monitors CyberArk vault health, checks credential rotation status,
audits safe permissions, and generates compliance reports for
privileged access management.
"""
import json
import datetime
import hashlib
from typing import Dict, List, Optional
from dataclasses import dataclass, field
@dataclass
class PrivilegedAccount:
"""Represents a privileged account in the vault."""
account_id: str
username: str
address: str # target system
platform_id: str
safe_name: str
account_type: str # domain_admin, local_admin, service_account, dba, network
last_rotation: Optional[str] = None
last_verification: Optional[str] = None
last_access: Optional[str] = None
rotation_interval_days: int = 30
status: str = "active" # active, locked, failed_rotation, disabled
@dataclass
class SafeConfig:
"""Represents a CyberArk safe configuration."""
safe_name: str
description: str
members: List[Dict] = field(default_factory=list) # {"user": "...", "role": "..."}
retention_days: int = 365
dual_control: bool = False
require_reason: bool = True
account_count: int = 0
@dataclass
class PAMAuditFinding:
"""PAM audit finding."""
finding_id: str
severity: str
category: str
title: str
details: str
affected_accounts: List[str] = field(default_factory=list)
remediation: str = ""
nist_control: str = ""
class CyberArkPAMAuditor:
"""Audits CyberArk PAM configuration and compliance."""
ROTATION_POLICIES = {
"domain_admin": 1, # days
"local_admin": 3,
"service_account": 30,
"dba": 1,
"network": 7,
"cloud_iam": 90,
"root": 3,
}
def __init__(self):
self.accounts: List[PrivilegedAccount] = []
self.safes: List[SafeConfig] = []
self.findings: List[PAMAuditFinding] = []
self.finding_counter = 0
def load_accounts(self, accounts: List[Dict]):
"""Load privileged accounts for audit."""
for acct in accounts:
self.accounts.append(PrivilegedAccount(**acct))
def load_safes(self, safes: List[Dict]):
"""Load safe configurations for audit."""
for safe in safes:
self.safes.append(SafeConfig(**safe))
def _next_finding_id(self) -> str:
self.finding_counter += 1
return f"PAM-{self.finding_counter:04d}"
def audit_all(self) -> List[PAMAuditFinding]:
"""Run all PAM audit checks."""
self.findings = []
self.finding_counter = 0
self._audit_rotation_compliance()
self._audit_verification_status()
self._audit_stale_accounts()
self._audit_safe_permissions()
self._audit_dual_control()
self._audit_service_account_dependencies()
self._audit_account_coverage()
return self.findings
def _audit_rotation_compliance(self):
"""Check if credentials are being rotated per policy."""
now = datetime.datetime.now()
overdue_accounts = []
for acct in self.accounts:
if acct.status == "disabled":
continue
max_days = self.ROTATION_POLICIES.get(acct.account_type, 30)
if acct.last_rotation:
try:
last_rot = datetime.datetime.fromisoformat(acct.last_rotation)
days_since = (now - last_rot).days
if days_since > max_days:
overdue_accounts.append(
f"{acct.username}@{acct.address} ({acct.account_type}): "
f"{days_since} days since rotation, policy: {max_days} days"
)
except ValueError:
pass
else:
overdue_accounts.append(
f"{acct.username}@{acct.address}: Never rotated"
)
if overdue_accounts:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="critical",
category="Credential Rotation",
title=f"{len(overdue_accounts)} accounts overdue for rotation",
details="The following accounts exceed their rotation policy:\n" +
"\n".join(f" - {a}" for a in overdue_accounts),
affected_accounts=[a.split("@")[0] for a in overdue_accounts],
remediation="Investigate CPM rotation failures. Check reconciliation accounts. Run manual rotation.",
nist_control="IA-5(1)"
))
else:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="info",
category="Credential Rotation",
title="All accounts within rotation policy",
details="All active accounts have been rotated within their policy window."
))
def _audit_verification_status(self):
"""Check credential verification status."""
unverified = []
now = datetime.datetime.now()
for acct in self.accounts:
if acct.status == "disabled":
continue
if not acct.last_verification:
unverified.append(f"{acct.username}@{acct.address}: Never verified")
else:
try:
last_ver = datetime.datetime.fromisoformat(acct.last_verification)
days_since = (now - last_ver).days
if days_since > 7:
unverified.append(
f"{acct.username}@{acct.address}: {days_since} days since verification"
)
except ValueError:
pass
if unverified:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="high",
category="Credential Verification",
title=f"{len(unverified)} accounts have stale verification",
details="Vault credentials may not match target systems:\n" +
"\n".join(f" - {u}" for u in unverified),
remediation="Run CPM verification task. Check target system connectivity.",
nist_control="IA-5(2)"
))
def _audit_stale_accounts(self):
"""Identify accounts that haven't been accessed."""
stale = []
now = datetime.datetime.now()
for acct in self.accounts:
if acct.status == "disabled":
continue
if not acct.last_access:
stale.append(f"{acct.username}@{acct.address}: Never accessed from vault")
else:
try:
last_acc = datetime.datetime.fromisoformat(acct.last_access)
days_since = (now - last_acc).days
if days_since > 90:
stale.append(
f"{acct.username}@{acct.address}: {days_since} days since last access"
)
except ValueError:
pass
if stale:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="medium",
category="Stale Accounts",
title=f"{len(stale)} accounts unused for 90+ days",
details="These accounts may be candidates for decommissioning:\n" +
"\n".join(f" - {s}" for s in stale),
remediation="Review with account owners. Disable or remove if no longer needed.",
nist_control="AC-2(3)"
))
def _audit_safe_permissions(self):
"""Check safe member permissions for least privilege."""
for safe in self.safes:
admin_count = sum(1 for m in safe.members if m.get("role") == "admin")
if admin_count > 3:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="high",
category="Safe Permissions",
title=f"Safe '{safe.safe_name}' has {admin_count} admins",
details=f"Excessive admin access to safe '{safe.safe_name}'. "
"Admin role allows full control including member management.",
remediation="Review safe admins. Reduce to minimum required (typically 2).",
nist_control="AC-6"
))
for member in safe.members:
if member.get("role") == "full_access":
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="medium",
category="Safe Permissions",
title=f"Full access granted in safe '{safe.safe_name}'",
details=f"User '{member.get('user')}' has full access to safe '{safe.safe_name}'.",
remediation="Assign minimum required permissions (retrieve, list) instead of full access.",
nist_control="AC-6(1)"
))
def _audit_dual_control(self):
"""Check dual control enforcement for sensitive safes."""
for safe in self.safes:
if not safe.dual_control:
high_priv_types = any(
acct.account_type in ("domain_admin", "root", "dba")
for acct in self.accounts if acct.safe_name == safe.safe_name
)
if high_priv_types:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="high",
category="Dual Control",
title=f"Dual control not enabled for high-privilege safe '{safe.safe_name}'",
details=f"Safe '{safe.safe_name}' contains high-privilege accounts but "
"does not require dual control for credential checkout.",
remediation="Enable dual control requiring approval before credential release.",
nist_control="AC-5"
))
def _audit_service_account_dependencies(self):
"""Check for service accounts that may have rotation dependencies."""
service_accounts = [a for a in self.accounts if a.account_type == "service_account"]
if service_accounts:
rapid_rotation = [
a for a in service_accounts
if a.rotation_interval_days < 7
]
if rapid_rotation:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="medium",
category="Service Account Rotation",
title=f"{len(rapid_rotation)} service accounts with aggressive rotation",
details="Service accounts with < 7-day rotation may cause application disruptions "
"if dependencies are not properly managed.",
remediation="Verify application dependency management before aggressive rotation. "
"Use account groups for coordinated rotation.",
nist_control="IA-5(1)"
))
def _audit_account_coverage(self):
"""Check for potential gaps in privileged account coverage."""
account_types = set(a.account_type for a in self.accounts)
expected_types = {"domain_admin", "local_admin", "service_account", "dba", "network", "root"}
missing = expected_types - account_types
if missing:
self.findings.append(PAMAuditFinding(
finding_id=self._next_finding_id(),
severity="medium",
category="Coverage Gap",
title=f"No {', '.join(missing)} accounts in vault",
details=f"Account types not found in vault: {', '.join(missing)}. "
"These may be unmanaged privileged accounts.",
remediation="Run privileged account discovery scan. Onboard missing account types.",
nist_control="AC-2"
))
def generate_report(self) -> str:
"""Generate comprehensive PAM audit report."""
if not self.findings:
self.audit_all()
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3, "info": 4}
sorted_findings = sorted(self.findings, key=lambda f: severity_order.get(f.severity, 5))
lines = [
"=" * 70,
"CYBERARK PAM AUDIT REPORT",
"=" * 70,
f"Report Date: {datetime.datetime.now().isoformat()}",
f"Total Accounts Audited: {len(self.accounts)}",
f"Total Safes Audited: {len(self.safes)}",
f"Total Findings: {len(self.findings)}",
"-" * 70,
""
]
by_severity = {}
for f in sorted_findings:
by_severity.setdefault(f.severity, []).append(f)
for sev in ["critical", "high", "medium", "low", "info"]:
count = len(by_severity.get(sev, []))
lines.append(f" {sev.upper()}: {count}")
lines.append("")
for f in sorted_findings:
icon = {"critical": "[!!!]", "high": "[!!]", "medium": "[!]", "low": "[~]", "info": "[i]"}.get(f.severity, "")
lines.append(f"{icon} {f.finding_id} [{f.severity.upper()}] {f.title}")
lines.append(f" Category: {f.category}")
lines.append(f" {f.details}")
if f.remediation:
lines.append(f" Remediation: {f.remediation}")
if f.nist_control:
lines.append(f" NIST Control: {f.nist_control}")
lines.append("")
lines.append("=" * 70)
critical_count = len(by_severity.get("critical", []))
overall = "FAIL" if critical_count > 0 else "PASS WITH FINDINGS" if len(self.findings) > len(by_severity.get("info", [])) else "PASS"
lines.append(f"OVERALL: {overall}")
lines.append("=" * 70)
return "\n".join(lines)
def main():
"""Run PAM audit with sample data."""
auditor = CyberArkPAMAuditor()
sample_accounts = [
{"account_id": "1", "username": "domain_admin01", "address": "dc01.corp.local",
"platform_id": "WinDomain", "safe_name": "DomainAdmins", "account_type": "domain_admin",
"last_rotation": "2026-02-20", "last_verification": "2026-02-22", "last_access": "2026-02-21"},
{"account_id": "2", "username": "root", "address": "webserver01.corp.local",
"platform_id": "UnixSSH", "safe_name": "LinuxRoot", "account_type": "root",
"last_rotation": "2026-01-15", "last_verification": "2026-01-16"},
{"account_id": "3", "username": "sa", "address": "sqlserver01.corp.local",
"platform_id": "MSSQL", "safe_name": "DatabaseAdmins", "account_type": "dba",
"last_rotation": "2026-02-22", "last_verification": "2026-02-22", "last_access": "2026-02-23"},
{"account_id": "4", "username": "svc_backup", "address": "backup01.corp.local",
"platform_id": "WinService", "safe_name": "ServiceAccounts", "account_type": "service_account",
"last_rotation": "2026-02-01", "rotation_interval_days": 5},
{"account_id": "5", "username": "admin", "address": "switch01.corp.local",
"platform_id": "CiscoIOS", "safe_name": "NetworkDevices", "account_type": "network",
"last_rotation": "2026-02-10", "last_verification": "2026-02-10"},
]
sample_safes = [
{"safe_name": "DomainAdmins", "description": "Domain admin accounts", "dual_control": True,
"members": [{"user": "admin1", "role": "admin"}, {"user": "admin2", "role": "admin"}], "account_count": 5},
{"safe_name": "LinuxRoot", "description": "Linux root accounts", "dual_control": False,
"members": [{"user": "admin1", "role": "admin"}, {"user": "admin2", "role": "admin"},
{"user": "admin3", "role": "admin"}, {"user": "admin4", "role": "admin"}], "account_count": 20},
{"safe_name": "ServiceAccounts", "description": "Service accounts", "dual_control": False,
"members": [{"user": "svc_team", "role": "full_access"}], "account_count": 50},
]
auditor.load_accounts(sample_accounts)
auditor.load_safes(sample_safes)
report = auditor.generate_report()
print(report)
if __name__ == "__main__":
main()
Assets 1
template.mdtext/markdown · 2.5 KBKeep exploring