Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-SkillsFramework mappings
MITRE ATT&CK
Overview
Deploy SailPoint IdentityNow or IdentityIQ for identity governance and administration. Covers identity lifecycle management, access request workflows, certification campaigns, role mining, SOD policy enforcement, and compliance reporting for enterprise IAM.
When to Use
- When deploying or configuring implementing identity governance with sailpoint 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
- Implement comprehensive implementing identity governance with sailpoint capability
- Establish automated discovery and monitoring processes
- Integrate with enterprise IAM and security tools
- Generate compliance-ready documentation and reports
- Align with NIST 800-53 access control requirements
Security Controls
| Control | NIST 800-53 | Description |
|---|---|---|
| Account Management | AC-2 | Lifecycle management |
| Access Enforcement | AC-3 | Policy-based access control |
| Least Privilege | AC-6 | Minimum necessary permissions |
| Audit Logging | AU-3 | Authentication and access events |
| Identification | IA-2 | User and service identification |
Verification
- Implementation tested in non-production environment
- Security policies configured and enforced
- Audit logging enabled and forwarding to SIEM
- Documentation and runbooks complete
- Compliance evidence generated
Source materials
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md1.6 KB
API Reference: Implementing Identity Governance with SailPoint
SailPoint IdentityNow V3 API
import requests
headers = {"Authorization": "Bearer <token>"}
base = "https://TENANT.api.identitynow.com"
identities = requests.get(f"{base}/v3/search/identities", headers=headers).json()
profiles = requests.get(f"{base}/v3/access-profiles", headers=headers).json()
campaigns = requests.get(f"{base}/v3/campaigns", headers=headers).json()Key API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/v3/search/identities |
GET | Search identities |
/v3/access-profiles |
GET | List access profiles |
/v3/campaigns |
GET | Certification campaigns |
/v3/roles |
GET | List roles |
/v3/sources |
GET | List identity sources |
/v3/accounts |
GET | List accounts |
Identity Lifecycle Events
| Event | Trigger | SLA |
|---|---|---|
| Joiner | HR new hire | 24 hours |
| Mover | Department/role change | 48 hours |
| Leaver | Termination | 1 hour |
SOD Policy Types
| Type | Example | Risk |
|---|---|---|
| Toxic combination | AP + AR | HIGH |
| Privileged conflict | Admin + Auditor | CRITICAL |
| Regulatory | Trade execution + Compliance | CRITICAL |
Certification Campaign Status
| Status | Action Needed |
|---|---|
| STAGED | Not yet started |
| ACTIVE | In progress |
| COMPLETED | All decisions made |
| OVERDUE | Past deadline - escalate |
References
- SailPoint IdentityNow API: https://developer.sailpoint.com/docs/api/v3
- SailPoint IIQ: https://community.sailpoint.com/
- NIST 800-53 AC-2: Account Management
Scripts 1
agent.py7.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()
Keep exploring