mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-13 14:44:58 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
202 lines
7.8 KiB
Python
202 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for auditing SailPoint IdentityNow/IIQ identity governance."""
|
|
|
|
import json
|
|
import argparse
|
|
from datetime import datetime
|
|
from collections import Counter
|
|
|
|
try:
|
|
import requests
|
|
except ImportError:
|
|
requests = None
|
|
|
|
|
|
def sailpoint_api(base_url, token, endpoint, method="GET", params=None):
|
|
"""Call SailPoint IdentityNow API."""
|
|
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
|
|
resp = requests.request(method, f"{base_url}{endpoint}",
|
|
headers=headers, params=params, timeout=60)
|
|
resp.raise_for_status()
|
|
return resp.json()
|
|
|
|
|
|
def list_identities(base_url, token, limit=250):
|
|
"""List identities from SailPoint."""
|
|
return sailpoint_api(base_url, token, "/v3/search/identities",
|
|
params={"limit": limit})
|
|
|
|
|
|
def list_access_profiles(base_url, token):
|
|
"""List access profiles."""
|
|
return sailpoint_api(base_url, token, "/v3/access-profiles", params={"limit": 250})
|
|
|
|
|
|
def list_certifications(base_url, token):
|
|
"""List active certification campaigns."""
|
|
return sailpoint_api(base_url, token, "/v3/campaigns", params={"limit": 50})
|
|
|
|
|
|
def audit_certification_campaigns(campaigns_data):
|
|
"""Audit certification campaign completion and compliance."""
|
|
campaigns = campaigns_data if isinstance(campaigns_data, list) else \
|
|
campaigns_data.get("campaigns", campaigns_data.get("items", []))
|
|
findings = []
|
|
for campaign in campaigns:
|
|
status = campaign.get("status", "").lower()
|
|
completion = campaign.get("completionPercentage", campaign.get("completion", 0))
|
|
deadline = campaign.get("deadline", campaign.get("due_date", ""))
|
|
if status == "active" and completion < 50:
|
|
findings.append({
|
|
"campaign": campaign.get("name", ""),
|
|
"issue": f"Low completion: {completion}%",
|
|
"severity": "HIGH",
|
|
"deadline": deadline,
|
|
})
|
|
if status == "overdue":
|
|
findings.append({
|
|
"campaign": campaign.get("name", ""),
|
|
"issue": "Campaign overdue",
|
|
"severity": "CRITICAL",
|
|
})
|
|
return findings
|
|
|
|
|
|
def audit_sod_violations(sod_data):
|
|
"""Audit Separation of Duties policy violations."""
|
|
violations = sod_data if isinstance(sod_data, list) else \
|
|
sod_data.get("violations", [])
|
|
by_policy = Counter(v.get("policy_name", "unknown") for v in violations)
|
|
by_identity = Counter(v.get("identity", "unknown") for v in violations)
|
|
critical = [v for v in violations if v.get("severity", "").lower() == "critical"]
|
|
return {
|
|
"total_violations": len(violations),
|
|
"by_policy": dict(by_policy),
|
|
"top_violators": dict(by_identity.most_common(10)),
|
|
"critical_violations": len(critical),
|
|
"details": critical[:20],
|
|
}
|
|
|
|
|
|
def audit_orphan_accounts(accounts_data):
|
|
"""Find orphan accounts (no identity correlation)."""
|
|
accounts = accounts_data if isinstance(accounts_data, list) else \
|
|
accounts_data.get("accounts", [])
|
|
orphans = []
|
|
for acct in accounts:
|
|
if not acct.get("identityId") and not acct.get("identity"):
|
|
orphans.append({
|
|
"account": acct.get("name", acct.get("nativeIdentity", "")),
|
|
"source": acct.get("sourceName", acct.get("source", "")),
|
|
"created": acct.get("created", ""),
|
|
"last_login": acct.get("lastLogin", acct.get("last_login", "")),
|
|
"severity": "HIGH",
|
|
})
|
|
return {
|
|
"total_accounts": len(accounts),
|
|
"orphan_count": len(orphans),
|
|
"orphans": orphans[:50],
|
|
}
|
|
|
|
|
|
def audit_access_reviews(reviews_path):
|
|
"""Audit access review data for over-provisioned users."""
|
|
with open(reviews_path) as f:
|
|
reviews = json.load(f)
|
|
items = reviews if isinstance(reviews, list) else reviews.get("reviews", [])
|
|
over_provisioned = []
|
|
for review in items:
|
|
entitlements = review.get("entitlements", review.get("access", []))
|
|
unused = [e for e in entitlements if not e.get("used_last_90_days",
|
|
e.get("last_used", "") != "")]
|
|
if len(unused) > 3:
|
|
over_provisioned.append({
|
|
"identity": review.get("identity", review.get("user", "")),
|
|
"total_entitlements": len(entitlements),
|
|
"unused_entitlements": len(unused),
|
|
"severity": "HIGH" if len(unused) > 10 else "MEDIUM",
|
|
})
|
|
return {
|
|
"total_reviewed": len(items),
|
|
"over_provisioned": len(over_provisioned),
|
|
"details": over_provisioned[:20],
|
|
}
|
|
|
|
|
|
def generate_lifecycle_policy():
|
|
"""Generate identity lifecycle policy recommendations."""
|
|
return {
|
|
"joiner": {
|
|
"trigger": "HR system new hire event",
|
|
"actions": ["Create identity", "Provision birthright access",
|
|
"Assign department role", "Send welcome notification"],
|
|
"sla": "Within 24 hours of start date",
|
|
},
|
|
"mover": {
|
|
"trigger": "Department or role change in HR system",
|
|
"actions": ["Recalculate role entitlements", "Revoke old department access",
|
|
"Provision new role access", "Trigger manager certification"],
|
|
"sla": "Within 48 hours of change",
|
|
},
|
|
"leaver": {
|
|
"trigger": "Termination event from HR system",
|
|
"actions": ["Disable all accounts immediately", "Revoke all access",
|
|
"Transfer ownership of shared resources",
|
|
"Archive mailbox", "Remove from all groups"],
|
|
"sla": "Within 1 hour of termination (immediate for involuntary)",
|
|
},
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="SailPoint Identity Governance Agent")
|
|
parser.add_argument("--campaigns", help="Certification campaigns JSON")
|
|
parser.add_argument("--sod", help="SOD violations JSON")
|
|
parser.add_argument("--accounts", help="Accounts data JSON for orphan detection")
|
|
parser.add_argument("--reviews", help="Access reviews JSON")
|
|
parser.add_argument("--action", choices=["campaigns", "sod", "orphans", "reviews",
|
|
"lifecycle", "full"], default="full")
|
|
parser.add_argument("--output", default="sailpoint_governance_report.json")
|
|
args = parser.parse_args()
|
|
|
|
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
|
|
|
|
if args.action in ("campaigns", "full") and args.campaigns:
|
|
with open(args.campaigns) as f:
|
|
data = json.load(f)
|
|
findings = audit_certification_campaigns(data)
|
|
report["results"]["campaigns"] = findings
|
|
print(f"[+] Campaign issues: {len(findings)}")
|
|
|
|
if args.action in ("sod", "full") and args.sod:
|
|
with open(args.sod) as f:
|
|
data = json.load(f)
|
|
result = audit_sod_violations(data)
|
|
report["results"]["sod"] = result
|
|
print(f"[+] SOD violations: {result['total_violations']}")
|
|
|
|
if args.action in ("orphans", "full") and args.accounts:
|
|
with open(args.accounts) as f:
|
|
data = json.load(f)
|
|
result = audit_orphan_accounts(data)
|
|
report["results"]["orphans"] = result
|
|
print(f"[+] Orphan accounts: {result['orphan_count']}")
|
|
|
|
if args.action in ("reviews", "full") and args.reviews:
|
|
result = audit_access_reviews(args.reviews)
|
|
report["results"]["reviews"] = result
|
|
print(f"[+] Over-provisioned: {result['over_provisioned']}")
|
|
|
|
if args.action in ("lifecycle", "full"):
|
|
policy = generate_lifecycle_policy()
|
|
report["results"]["lifecycle"] = policy
|
|
print("[+] Lifecycle policy generated")
|
|
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|