mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 22:24:56 +03:00
c21af3347e
- Add scripts/agent.py and references/api-reference.md to all remaining skills - Update all 648 LICENSE files: copyright now reads 'Mahipal' - Add implementing-security-monitoring-with-datadog (new skill with full anatomy) - All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
222 lines
8.2 KiB
Python
222 lines
8.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for implementing and auditing Just-In-Time access provisioning."""
|
|
|
|
import json
|
|
import argparse
|
|
from datetime import datetime, timedelta
|
|
from collections import Counter
|
|
|
|
|
|
def audit_jit_requests(requests_path):
|
|
"""Audit JIT access requests for compliance and anomalies."""
|
|
with open(requests_path) as f:
|
|
data = json.load(f)
|
|
requests_list = data if isinstance(data, list) else data.get("requests", [])
|
|
findings = []
|
|
by_status = Counter()
|
|
by_resource = Counter()
|
|
|
|
for req in requests_list:
|
|
status = req.get("status", "unknown").lower()
|
|
by_status[status] += 1
|
|
by_resource[req.get("resource", "unknown")] += 1
|
|
|
|
duration_hours = req.get("duration_hours", req.get("duration", 0))
|
|
if duration_hours > 8:
|
|
findings.append({
|
|
"request_id": req.get("id", ""),
|
|
"issue": f"Long access duration: {duration_hours}h",
|
|
"severity": "MEDIUM",
|
|
"user": req.get("requestor", req.get("user", "")),
|
|
})
|
|
if duration_hours > 24:
|
|
findings[-1]["severity"] = "HIGH"
|
|
|
|
if status == "approved" and not req.get("approver"):
|
|
findings.append({
|
|
"request_id": req.get("id", ""),
|
|
"issue": "Auto-approved without approver record",
|
|
"severity": "HIGH",
|
|
})
|
|
|
|
granted = req.get("granted_at", "")
|
|
expired = req.get("expired_at", req.get("revoked_at", ""))
|
|
if granted and not expired and status == "active":
|
|
granted_dt = datetime.fromisoformat(granted.replace("Z", ""))
|
|
if (datetime.utcnow() - granted_dt).total_seconds() > duration_hours * 3600:
|
|
findings.append({
|
|
"request_id": req.get("id", ""),
|
|
"issue": "Access not revoked after expiration",
|
|
"severity": "CRITICAL",
|
|
"user": req.get("requestor", ""),
|
|
})
|
|
|
|
return {
|
|
"total_requests": len(requests_list),
|
|
"by_status": dict(by_status),
|
|
"by_resource": dict(by_resource.most_common(10)),
|
|
"findings": findings,
|
|
"anomaly_count": len(findings),
|
|
}
|
|
|
|
|
|
def audit_standing_privileges(privileges_path):
|
|
"""Identify standing privileges that should be converted to JIT."""
|
|
with open(privileges_path) as f:
|
|
data = json.load(f)
|
|
privs = data if isinstance(data, list) else data.get("privileges", [])
|
|
candidates = []
|
|
|
|
for priv in privs:
|
|
role = priv.get("role", priv.get("permission", "")).lower()
|
|
usage = priv.get("last_used", priv.get("last_access", ""))
|
|
is_privileged = any(k in role for k in [
|
|
"admin", "root", "owner", "superuser", "dba", "operator"])
|
|
|
|
days_unused = 0
|
|
if usage:
|
|
try:
|
|
usage_dt = datetime.fromisoformat(usage.replace("Z", ""))
|
|
days_unused = (datetime.utcnow() - usage_dt).days
|
|
except ValueError:
|
|
pass
|
|
|
|
if is_privileged and days_unused > 30:
|
|
candidates.append({
|
|
"user": priv.get("user", priv.get("identity", "")),
|
|
"role": priv.get("role", ""),
|
|
"resource": priv.get("resource", priv.get("target", "")),
|
|
"days_unused": days_unused,
|
|
"severity": "CRITICAL" if days_unused > 90 else "HIGH",
|
|
"recommendation": "Convert to JIT access",
|
|
})
|
|
elif is_privileged:
|
|
candidates.append({
|
|
"user": priv.get("user", ""),
|
|
"role": priv.get("role", ""),
|
|
"days_unused": days_unused,
|
|
"severity": "MEDIUM",
|
|
"recommendation": "Evaluate for JIT conversion",
|
|
})
|
|
|
|
return {
|
|
"total_privileges": len(privs),
|
|
"jit_candidates": len(candidates),
|
|
"critical_standing": sum(1 for c in candidates if c["severity"] == "CRITICAL"),
|
|
"details": candidates[:30],
|
|
}
|
|
|
|
|
|
def calculate_jit_metrics(requests_path):
|
|
"""Calculate JIT program operational metrics."""
|
|
with open(requests_path) as f:
|
|
data = json.load(f)
|
|
requests_list = data if isinstance(data, list) else data.get("requests", [])
|
|
|
|
approval_times = []
|
|
durations = []
|
|
auto_approved = 0
|
|
total = len(requests_list)
|
|
|
|
for req in requests_list:
|
|
if req.get("auto_approved", False):
|
|
auto_approved += 1
|
|
|
|
requested = req.get("requested_at", "")
|
|
approved = req.get("approved_at", "")
|
|
if requested and approved:
|
|
try:
|
|
r_dt = datetime.fromisoformat(requested.replace("Z", ""))
|
|
a_dt = datetime.fromisoformat(approved.replace("Z", ""))
|
|
approval_times.append((a_dt - r_dt).total_seconds() / 60)
|
|
except ValueError:
|
|
pass
|
|
|
|
duration = req.get("duration_hours", 0)
|
|
if duration:
|
|
durations.append(duration)
|
|
|
|
return {
|
|
"total_requests": total,
|
|
"auto_approved_rate": round(auto_approved / total * 100, 1) if total else 0,
|
|
"avg_approval_time_min": round(sum(approval_times) / len(approval_times), 1) if approval_times else 0,
|
|
"avg_duration_hours": round(sum(durations) / len(durations), 1) if durations else 0,
|
|
"max_duration_hours": max(durations) if durations else 0,
|
|
"p95_approval_time_min": sorted(approval_times)[int(len(approval_times) * 0.95)] if approval_times else 0,
|
|
}
|
|
|
|
|
|
def generate_jit_policy():
|
|
"""Generate JIT access provisioning policy."""
|
|
return {
|
|
"risk_levels": {
|
|
"low": {
|
|
"approval": "auto-approve",
|
|
"max_duration": "4h",
|
|
"examples": ["Read-only database access", "Log viewer"],
|
|
},
|
|
"medium": {
|
|
"approval": "manager-approve",
|
|
"max_duration": "8h",
|
|
"examples": ["Application admin", "Deploy permissions"],
|
|
},
|
|
"high": {
|
|
"approval": "manager + security team",
|
|
"max_duration": "4h",
|
|
"examples": ["Production database write", "IAM admin"],
|
|
},
|
|
"critical": {
|
|
"approval": "CISO + manager + security team",
|
|
"max_duration": "2h",
|
|
"examples": ["Domain admin", "Root access", "Key management"],
|
|
},
|
|
},
|
|
"controls": {
|
|
"session_recording": True,
|
|
"break_glass_procedure": True,
|
|
"automatic_revocation": True,
|
|
"audit_logging": True,
|
|
"notification_on_grant": True,
|
|
},
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="JIT Access Provisioning Agent")
|
|
parser.add_argument("--requests", help="JIT requests log JSON")
|
|
parser.add_argument("--privileges", help="Standing privileges JSON")
|
|
parser.add_argument("--action", choices=["audit", "standing", "metrics", "policy", "full"],
|
|
default="full")
|
|
parser.add_argument("--output", default="jit_access_report.json")
|
|
args = parser.parse_args()
|
|
|
|
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
|
|
|
|
if args.action in ("audit", "full") and args.requests:
|
|
result = audit_jit_requests(args.requests)
|
|
report["results"]["audit"] = result
|
|
print(f"[+] JIT audit: {result['total_requests']} requests, {result['anomaly_count']} issues")
|
|
|
|
if args.action in ("standing", "full") and args.privileges:
|
|
result = audit_standing_privileges(args.privileges)
|
|
report["results"]["standing"] = result
|
|
print(f"[+] Standing privs: {result['jit_candidates']} JIT candidates")
|
|
|
|
if args.action in ("metrics", "full") and args.requests:
|
|
metrics = calculate_jit_metrics(args.requests)
|
|
report["results"]["metrics"] = metrics
|
|
print(f"[+] Avg approval: {metrics['avg_approval_time_min']}min")
|
|
|
|
if args.action in ("policy", "full"):
|
|
policy = generate_jit_policy()
|
|
report["results"]["policy"] = policy
|
|
print("[+] JIT 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()
|