npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Access recertification (also called access certification or access review) is a periodic process where designated reviewers validate that users have appropriate access to systems and data. Saviynt Enterprise Identity Cloud (EIC) automates this process through certification campaigns that present reviewers with current access assignments and collect approve/revoke/conditionally-certify decisions. Campaigns can be triggered on schedule (quarterly, semi-annually), event-driven (department transfer, role change), or on-demand. Saviynt provides intelligence features including risk scoring, usage analytics, and peer-group analysis to help reviewers make informed decisions.
When to Use
- When conducting security assessments that involve performing access recertification with saviynt
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Saviynt Enterprise Identity Cloud (EIC) tenant with admin access
- Identity data synchronized from authoritative sources (HR, AD, cloud)
- Entitlement data imported from target applications
- Certifier roles assigned (managers, application owners, data owners)
- Campaign templates defined for each certification type
Core Concepts
Campaign Types
| Type | Scope | Trigger | Certifier |
|---|---|---|---|
| User Manager | All access for users under a manager | Scheduled (quarterly) | Direct manager |
| Entitlement Owner | All users with a specific entitlement | Scheduled (semi-annually) | Entitlement/app owner |
| Application | All access to a specific application | Scheduled | Application owner |
| Role-Based | All users assigned to a specific role | Scheduled | Role owner |
| Event-Based | Users whose attributes changed | Attribute change trigger | New manager |
| Micro-Certification | Single user, single entitlement | On-demand | Manager or owner |
Certification Decisions
| Decision | Effect | Use Case |
|---|---|---|
| Certify (Approve) | Access maintained | Access is still required |
| Revoke | Access removal ticket created | Access no longer needed |
| Conditionally Certify | Access maintained with conditions | Access needed temporarily, review again |
| Delegate | Reassign to another certifier | Certifier lacks knowledge to decide |
| Abstain | No decision recorded | Conflict of interest |
Campaign Lifecycle
CONFIGURATION → PREVIEW → ACTIVE → IN PROGRESS → COMPLETED → REMEDIATION
│ │ │ │ │ │
│ │ │ │ │ └── Revoke tickets
│ │ │ │ │ executed
│ │ │ │ │
│ │ │ │ └── All decisions
│ │ │ │ collected
│ │ │ │
│ │ │ └── Certifiers reviewing
│ │ │ and making decisions
│ │ │
│ │ └── Campaign launched,
│ │ notifications sent
│ │
│ └── Read-only preview for validation
│
└── Campaign parameters definedWorkflow
Step 1: Configure Campaign Template
In Saviynt Admin Console:
- Navigate to Certifications > Campaign > Create New Campaign
- Define campaign parameters:
| Parameter | Value |
|---|---|
| Campaign Name | Q1 2025 Manager Access Review |
| Campaign Type | User Manager |
| Description | Quarterly review of all user access |
| Certifier Type | Manager (dynamic - user's direct manager) |
| Secondary Certifier | Application Owner (fallback if manager unavailable) |
| Due Date | 14 days from launch |
| Reminder Schedule | Day 7, Day 10, Day 13 |
| Escalation | Auto-revoke on Day 15 if no decision |
-
Configure scope filters:
- Include: All active users
- Exclude: Service accounts, break-glass accounts
- Application filter: All connected applications
-
Configure intelligence features:
- Enable risk scoring (high-risk entitlements highlighted)
- Enable usage data (last access date shown)
- Enable peer analysis (compare access to peer group)
- Enable SoD violation flagging
Step 2: Configure Certifier Experience
Customize what certifiers see during the review:
Columns Displayed:
- User name and title
- Application name
- Entitlement/role name
- Risk score (1-10)
- Last access date
- Peer group comparison (% of peers with same access)
- SoD violation flag
Decision Options:
- Certify with justification (free text)
- Revoke with reason (dropdown: no longer needed, SoD conflict, role change)
- Conditionally certify with expiry date
Bulk Actions:
- Certify all low-risk items
- Revoke all items not accessed in 90+ days
- Filter by application, risk level, or SoD status
Step 3: Launch Campaign via API
import requests
SAVIYNT_URL = "https://tenant.saviyntcloud.com"
SAVIYNT_TOKEN = "your-api-token"
def create_certification_campaign(campaign_config):
"""Create and launch a Saviynt certification campaign."""
headers = {
"Authorization": f"Bearer {SAVIYNT_TOKEN}",
"Content-Type": "application/json"
}
# Create campaign
response = requests.post(
f"{SAVIYNT_URL}/ECM/api/v5/createCampaign",
headers=headers,
json={
"campaignname": campaign_config["name"],
"campaigntype": campaign_config["type"],
"description": campaign_config["description"],
"certifier": campaign_config["certifier_type"],
"duedate": campaign_config["due_date"],
"reminderdays": campaign_config["reminder_days"],
"autorevoke": campaign_config.get("auto_revoke", True),
"autorevokedays": campaign_config.get("auto_revoke_days", 15),
"scope": campaign_config.get("scope", {}),
}
)
response.raise_for_status()
campaign_id = response.json().get("campaignId")
# Launch campaign
launch_response = requests.post(
f"{SAVIYNT_URL}/ECM/api/v5/launchCampaign",
headers=headers,
json={"campaignId": campaign_id}
)
launch_response.raise_for_status()
return {
"campaign_id": campaign_id,
"status": "launched",
"certifications_created": launch_response.json().get("certificationCount", 0)
}
def get_campaign_status(campaign_id):
"""Get current status and progress of a campaign."""
headers = {"Authorization": f"Bearer {SAVIYNT_TOKEN}"}
response = requests.get(
f"{SAVIYNT_URL}/ECM/api/v5/getCampaignDetails",
headers=headers,
params={"campaignId": campaign_id}
)
response.raise_for_status()
data = response.json()
return {
"campaign_id": campaign_id,
"status": data.get("status"),
"total_items": data.get("totalLineItems", 0),
"certified": data.get("certifiedCount", 0),
"revoked": data.get("revokedCount", 0),
"pending": data.get("pendingCount", 0),
"completion_rate": data.get("completionPercentage", 0),
}Step 4: Monitor Campaign Progress
Track certification progress and send escalations:
- Dashboard: Saviynt provides real-time campaign dashboard with completion rates
- Reminders: Automatic email reminders at configured intervals
- Escalation: If certifier does not respond by due date, escalate to manager's manager or auto-revoke
- Delegation: Allow certifiers to delegate specific items to application owners
Step 5: Execute Remediation
After campaign closes:
- Auto-Remediation: Saviynt automatically creates provisioning tasks to revoke denied access
- Ticket Integration: Revocation tasks create tickets in ServiceNow/Jira for tracking
- Grace Period: Configure a grace period (e.g., 5 business days) before access is actually removed
- Verification: After revocation, verify access is removed from target systems
- Audit Trail: All decisions, revocations, and remediations logged for compliance evidence
Validation Checklist
- Campaign templates configured for each certification type
- Certifier roles assigned (managers, app owners, data owners)
- Risk scoring and usage analytics enabled
- SoD violation detection configured
- Reminder and escalation schedules defined
- Auto-revoke policy for non-responsive certifiers configured
- Campaign launched and certifiers notified
- Campaign completion rate > 95% before close
- Revocation tasks created for all denied entitlements
- Remediation completed within SLA
- Campaign report generated for compliance audit
- Evidence archived for regulatory retention period
References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.7 KB
API Reference: Saviynt Access Recertification
Saviynt EIC REST API v5
Authentication
POST /ECM/api/login
Body: {"username": "admin", "password": "pass"}
Returns: {"access_token": "...", "token_type": "Bearer"}Certification Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /ECM/api/v5/listCertification |
List campaigns |
| POST | /ECM/api/v5/getCertificationDetails |
Campaign statistics |
| POST | /ECM/api/v5/getCertificationItems |
Get review items |
| POST | /ECM/api/v5/certifyItems |
Certify/revoke items |
listCertification Payload
| Field | Description |
|---|---|
certificationstatus |
active, completed, expired |
max |
Maximum results per page |
offset |
Pagination offset |
Certification Item Fields
| Field | Description |
|---|---|
username |
Identity under review |
entitlement_value |
Access being reviewed |
risk_score |
Computed risk (0-10) |
last_used_date |
Last access usage date |
peer_group_match |
Whether peers have same access |
certifyItems Actions
| Action | Description |
|---|---|
certify |
Approve continued access |
revoke |
Remove access |
consult |
Request additional reviewer input |
Campaign Types
| Type | Trigger |
|---|---|
| User Manager | Manager reviews direct reports |
| Application Owner | App owner reviews all users |
| Entitlement Owner | Entitlement owner reviews holders |
| Event-Based | Triggered by role/department change |
References
- Saviynt REST API: https://docs.saviyntcloud.com/
- Saviynt Certification: https://docs.saviyntcloud.com/bundle/EIC-Admin-v24x/
standards.md1.9 KB
Access Recertification with Saviynt - Standards Reference
Compliance Requirements for Access Reviews
SOX Section 404 (Sarbanes-Oxley)
- Quarterly access reviews for financially significant applications
- Evidence of review decisions with justification
- Remediation of revoked access within defined SLA
- Separation of duties validation during certification
SOC 2 Type II
- CC6.1: Logical access controls
- CC6.2: User registration and authorization
- CC6.3: Access modification and removal
- Semi-annual certification campaigns required for trust service criteria
PCI DSS v4.0
- 7.2.4: User accounts and access reviewed at least every 6 months
- 7.2.5: Application and system accounts reviewed every 6 months
- Evidence of review decisions required
HIPAA Security Rule
- 164.312(a)(1): Access control standard
- 164.308(a)(3)(ii)(A): Workforce clearance procedure
- 164.308(a)(4): Information access management
- Annual access reviews for PHI-accessing systems
GDPR Article 5(1)(f)
- Appropriate security of personal data
- Regular access reviews ensure only authorized personnel access PII
- Documentation of access review decisions
Saviynt Campaign Configuration Standards
Campaign Frequency by Compliance
| Framework | Minimum Frequency | Scope |
|---|---|---|
| SOX | Quarterly | Financial applications |
| SOC 2 | Semi-annually | All in-scope systems |
| PCI DSS | Semi-annually | Cardholder data systems |
| HIPAA | Annually | PHI-accessing systems |
| ISO 27001 | Annually | All systems |
| NIST CSF | Per risk assessment | Risk-based |
Risk-Based Certification
| Risk Level | Review Frequency | Certifier | Auto-Revoke |
|---|---|---|---|
| Critical | Monthly | CISO + App Owner | 7 days |
| High | Quarterly | Manager + App Owner | 14 days |
| Medium | Semi-annually | Manager | 21 days |
| Low | Annually | Manager | 30 days |
workflows.md3.9 KB
Access Recertification with Saviynt - Workflows
Campaign Execution Workflow
WEEK 1: PREPARATION
├── Review and update certifier assignments
├── Verify identity data freshness (HR sync)
├── Validate entitlement data accuracy
├── Configure campaign template
└── Schedule campaign launch
WEEK 2: LAUNCH AND REVIEW
├── Launch campaign (auto-notifications sent)
├── Certifiers receive email with review link
├── Certifiers review each line item:
│ ├── Check user's current role
│ ├── Review risk score
│ ├── Check last access date
│ ├── Compare with peer group
│ └── Make certify/revoke decision
└── Day 7: First reminder sent
WEEK 3: FOLLOW-UP
├── Day 10: Second reminder sent
├── Day 13: Final reminder (escalation warning)
├── Security team contacts non-responsive certifiers
└── Campaign manager reviews progress dashboard
WEEK 4: CLOSE AND REMEDIATE
├── Day 14: Campaign due date
├── Day 15: Auto-revoke for non-certified items (if configured)
├── Revocation tasks created automatically
├── Remediation tickets sent to provisioning team
├── Access removed from target systems
└── Campaign report generated for complianceCertifier Decision Workflow
Certifier opens Saviynt certification inbox
│
├── For each user-entitlement pair:
│
│ ├── Review Context:
│ │ ├── User's name, title, department
│ │ ├── Entitlement name and application
│ │ ├── Risk score (1-10)
│ │ ├── Last access: 3 days ago / 180 days ago / Never
│ │ ├── Peer analysis: 85% of peers have this access
│ │ └── SoD violation: None / Conflict detected
│ │
│ ├── Decision Logic:
│ │ ├── Active user + Used recently + Peers have it → CERTIFY
│ │ ├── Active user + Not used in 90+ days → INVESTIGATE
│ │ ├── User changed department → LIKELY REVOKE
│ │ ├── SoD violation detected → REVOKE or ESCALATE
│ │ └── Cannot determine → DELEGATE to app owner
│ │
│ └── Record decision with justification
│
└── Submit all decisionsEvent-Based Certification Workflow
User attribute changes in HR system (e.g., department transfer)
│
├── Saviynt detects change via HR connector sync
│
├── User update rule triggers micro-certification:
│ ├── Scope: All entitlements for this user
│ ├── Certifier: New manager
│ └── Due date: 7 days
│
├── New manager reviews all access:
│ ├── Certify access relevant to new role
│ ├── Revoke access specific to old role
│ └── Request new access if needed
│
└── Remediation executes for revoked itemsRemediation Tracking Workflow
Campaign completes with revoked items
│
├── Saviynt creates provisioning tasks for each revocation
│
├── For each revoked entitlement:
│ ├── Create deprovisioning request
│ ├── Route to target system connector
│ ├── Execute removal (API/connector)
│ ├── Verify removal succeeded
│ └── Update audit log
│
├── If automated removal fails:
│ ├── Create manual remediation ticket (ServiceNow)
│ ├── Assign to application admin
│ ├── Track SLA compliance
│ └── Escalate if overdue
│
└── Post-remediation verification:
├── Re-scan target systems
├── Confirm revoked access no longer present
└── Archive compliance evidenceScripts 2
agent.py6.7 KB
#!/usr/bin/env python3
"""Agent for managing Saviynt access recertification campaigns 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):
"""Authenticate to Saviynt EIC and get OAuth token."""
url = f"{base_url}/ECM/api/login"
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().get("access_token")
print(f"[*] Authenticated to Saviynt EIC")
return {"Authorization": f"Bearer {token}"}
def list_campaigns(base_url, headers, status="active"):
"""List certification campaigns."""
url = f"{base_url}/ECM/api/v5/listCertification"
payload = {"certificationstatus": status, "max": 50, "offset": 0}
resp = requests.post(url, headers=headers, 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()
campaigns = resp.json().get("certifications", [])
print(f"\n[*] Campaigns ({status}): {len(campaigns)}")
for c in campaigns:
print(f" {c.get('certificationname')} - certifier: {c.get('certifier', 'N/A')} "
f"| due: {c.get('duedate', 'N/A')}")
return campaigns
def get_campaign_details(base_url, headers, cert_key):
"""Get detailed campaign status including item counts."""
url = f"{base_url}/ECM/api/v5/getCertificationDetails"
payload = {"certkey": cert_key}
resp = requests.post(url, headers=headers, 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()
details = resp.json()
total = details.get("totalitems", 0)
certified = details.get("certifieditems", 0)
revoked = details.get("revokeditems", 0)
pending = total - certified - revoked
print(f"\n[*] Campaign {cert_key}: total={total}, certified={certified}, "
f"revoked={revoked}, pending={pending}")
return details
def get_pending_items(base_url, headers, cert_key, max_items=100):
"""Get items pending review in a certification campaign."""
url = f"{base_url}/ECM/api/v5/getCertificationItems"
payload = {"certkey": cert_key, "status": "pending", "max": max_items, "offset": 0}
resp = requests.post(url, headers=headers, 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()
items = resp.json().get("certificationitems", [])
print(f"\n[*] Pending items: {len(items)}")
high_risk = [i for i in items if i.get("risk_score", 0) > 7]
print(f" High-risk items (score > 7): {len(high_risk)}")
for i in high_risk[:10]:
print(f" [!] {i.get('username')} - {i.get('entitlement_value')} "
f"(risk: {i.get('risk_score')})")
return items
def certify_items(base_url, headers, cert_key, item_ids, action="certify"):
"""Certify or revoke items in a campaign."""
url = f"{base_url}/ECM/api/v5/certifyItems"
payload = {"certkey": cert_key, "itemids": item_ids, "action": action,
"comments": f"Auto-{action} by recertification agent"}
resp = requests.post(url, headers=headers, 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()
print(f"[*] {action.capitalize()}d {len(item_ids)} items in campaign {cert_key}")
return resp.json()
def check_overdue_campaigns(base_url, headers):
"""Find campaigns past their due date."""
url = f"{base_url}/ECM/api/v5/listCertification"
payload = {"certificationstatus": "active", "max": 200, "offset": 0}
resp = requests.post(url, headers=headers, 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()
campaigns = resp.json().get("certifications", [])
now = datetime.now(timezone.utc)
overdue = []
for c in campaigns:
due = c.get("duedate", "")
if due:
try:
due_dt = datetime.fromisoformat(due.replace("Z", "+00:00"))
if due_dt < now:
overdue.append({"name": c.get("certificationname"),
"due": due, "certifier": c.get("certifier")})
except ValueError:
pass
print(f"\n[*] Overdue campaigns: {len(overdue)}")
for o in overdue:
print(f" [!] {o['name']} (due: {o['due']}, certifier: {o['certifier']})")
return overdue
def generate_report(campaigns, overdue, output_path):
"""Generate recertification status report."""
report = {"report_date": datetime.now(timezone.utc).isoformat(),
"active_campaigns": len(campaigns), "overdue_campaigns": len(overdue),
"campaigns": campaigns[:50], "overdue": overdue}
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="Saviynt Access Recertification Agent")
parser.add_argument("action", choices=["list", "details", "pending", "overdue", "full-audit"])
parser.add_argument("--url", required=True, help="Saviynt EIC base URL")
parser.add_argument("--username", required=True)
parser.add_argument("--password", required=True)
parser.add_argument("--cert-key", help="Certification campaign key")
parser.add_argument("-o", "--output", default="recert_report.json")
args = parser.parse_args()
headers = authenticate(args.url, args.username, args.password)
if args.action == "list":
list_campaigns(args.url, headers)
elif args.action == "details" and args.cert_key:
get_campaign_details(args.url, headers, args.cert_key)
elif args.action == "pending" and args.cert_key:
get_pending_items(args.url, headers, args.cert_key)
elif args.action == "overdue":
check_overdue_campaigns(args.url, headers)
elif args.action == "full-audit":
campaigns = list_campaigns(args.url, headers)
overdue = check_overdue_campaigns(args.url, headers)
generate_report(campaigns, overdue, args.output)
if __name__ == "__main__":
main()
process.py8.5 KB
#!/usr/bin/env python3
"""
Saviynt Access Recertification Campaign Manager
Manages access recertification campaigns via Saviynt REST API,
tracks campaign progress, and generates compliance reports.
Requirements:
pip install requests pandas
"""
import json
import logging
import sys
from datetime import datetime, timedelta, timezone
try:
import requests
except ImportError:
print("[ERROR] requests required: pip install requests")
sys.exit(1)
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("saviynt_recert")
class SaviyntCampaignManager:
"""Manage Saviynt access recertification campaigns."""
def __init__(self, base_url, username, password):
self.base_url = base_url.rstrip("/")
self.token = self._authenticate(username, password)
def _authenticate(self, username, password):
"""Authenticate and retrieve API token."""
resp = requests.post(
f"{self.base_url}/ECM/api/login",
json={"username": username, "password": password},
)
resp.raise_for_status()
token = resp.json().get("access_token")
if not token:
raise Exception("Authentication failed: no token returned")
logger.info("Authenticated to Saviynt EIC")
return token
def _api_call(self, method, endpoint, json_data=None):
headers = {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
}
url = f"{self.base_url}{endpoint}"
resp = requests.request(method, url, headers=headers, json=json_data)
resp.raise_for_status()
return resp.json()
def create_campaign(self, name, campaign_type, certifier_type,
due_days=14, scope=None):
"""Create a new certification campaign."""
due_date = (datetime.now(timezone.utc) + timedelta(days=due_days)).strftime(
"%Y-%m-%d"
)
payload = {
"campaignname": name,
"campaigntype": campaign_type, # UserManager, EntitlementOwner, Application
"certifier": certifier_type,
"duedate": due_date,
"reminderdays": "7,10,13",
"autorevoke": True,
"autorevokedays": due_days + 1,
"status": "New",
}
if scope:
payload["scope"] = scope
result = self._api_call("POST", "/ECM/api/v5/createCampaign", payload)
campaign_id = result.get("campaignId")
logger.info(f"Campaign created: {name} (ID: {campaign_id})")
return result
def launch_campaign(self, campaign_id):
"""Launch an existing campaign, sending notifications to certifiers."""
result = self._api_call(
"POST", "/ECM/api/v5/launchCampaign",
{"campaignId": campaign_id}
)
logger.info(f"Campaign {campaign_id} launched")
return result
def get_campaign_status(self, campaign_id):
"""Get campaign progress and statistics."""
result = self._api_call(
"GET", f"/ECM/api/v5/getCampaignDetails?campaignId={campaign_id}"
)
return {
"campaign_id": campaign_id,
"name": result.get("campaignname"),
"status": result.get("status"),
"total_items": result.get("totalLineItems", 0),
"certified": result.get("certifiedCount", 0),
"revoked": result.get("revokedCount", 0),
"pending": result.get("pendingCount", 0),
"completion_pct": result.get("completionPercentage", 0),
"due_date": result.get("duedate"),
}
def get_all_campaigns(self, status=None):
"""List all certification campaigns."""
params = {}
if status:
params["status"] = status
result = self._api_call("GET", "/ECM/api/v5/getCampaigns")
campaigns = result.get("campaigns", [])
if status:
campaigns = [c for c in campaigns if c.get("status") == status]
return campaigns
def get_certification_items(self, campaign_id, status_filter=None):
"""Get individual certification line items for a campaign."""
result = self._api_call(
"POST", "/ECM/api/v5/getCertificationDetails",
{"campaignId": campaign_id, "max": 1000}
)
items = result.get("certifications", [])
if status_filter:
items = [i for i in items if i.get("status") == status_filter]
return items
def generate_compliance_report(self, campaign_id):
"""Generate a compliance-ready report for a completed campaign."""
status = self.get_campaign_status(campaign_id)
items = self.get_certification_items(campaign_id)
certified_items = [i for i in items if i.get("decision") == "Certify"]
revoked_items = [i for i in items if i.get("decision") == "Revoke"]
pending_items = [i for i in items if not i.get("decision")]
report = {
"report_title": "Access Recertification Compliance Report",
"campaign_name": status.get("name"),
"campaign_id": campaign_id,
"generated_at": datetime.now(timezone.utc).isoformat(),
"metrics": {
"total_items_reviewed": status["total_items"],
"certified": status["certified"],
"revoked": status["revoked"],
"pending": status["pending"],
"completion_rate": f"{status['completion_pct']}%",
"certification_rate": (
f"{(status['certified'] / status['total_items'] * 100):.1f}%"
if status["total_items"] else "N/A"
),
"revocation_rate": (
f"{(status['revoked'] / status['total_items'] * 100):.1f}%"
if status["total_items"] else "N/A"
),
},
"findings": [],
}
if pending_items:
report["findings"].append({
"severity": "High",
"finding": f"{len(pending_items)} items not reviewed by due date",
"action": "Auto-revoke or manual follow-up required",
})
revoke_rate = status["revoked"] / status["total_items"] if status["total_items"] else 0
if revoke_rate > 0.20:
report["findings"].append({
"severity": "Medium",
"finding": f"High revocation rate ({revoke_rate:.0%}) suggests over-provisioning",
"action": "Review provisioning policies and role definitions",
})
return report
class RecertificationScheduler:
"""Schedule recurring certification campaigns based on compliance needs."""
def __init__(self):
self.schedules = []
def add_schedule(self, name, frequency_days, campaign_type,
certifier_type, scope=None):
"""Add a recurring certification schedule."""
self.schedules.append({
"name": name,
"frequency_days": frequency_days,
"campaign_type": campaign_type,
"certifier_type": certifier_type,
"scope": scope,
"last_run": None,
})
def get_due_campaigns(self):
"""Check which campaigns are due to run."""
now = datetime.now(timezone.utc)
due = []
for schedule in self.schedules:
if schedule["last_run"] is None:
due.append(schedule)
else:
next_run = schedule["last_run"] + timedelta(days=schedule["frequency_days"])
if now >= next_run:
due.append(schedule)
return due
def export_schedule(self, output_path):
"""Export certification schedule for documentation."""
with open(output_path, "w") as f:
json.dump({
"schedules": self.schedules,
"exported_at": datetime.now(timezone.utc).isoformat(),
}, f, indent=2, default=str)
logger.info(f"Schedule exported to {output_path}")
if __name__ == "__main__":
print("=" * 60)
print("Saviynt Access Recertification Campaign Manager")
print("=" * 60)
print()
print("Usage:")
print(" mgr = SaviyntCampaignManager('https://tenant.saviyntcloud.com',")
print(" 'admin', 'password')")
print(" mgr.create_campaign('Q1 Review', 'UserManager', 'Manager')")
print(" mgr.launch_campaign(campaign_id)")
print(" report = mgr.generate_compliance_report(campaign_id)")