mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-10 21:24:56 +03:00
393 lines
14 KiB
Python
393 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Identity Verification Assessment Tool for Zero Trust
|
|
|
|
Analyzes identity configurations, evaluates MFA strength, assesses
|
|
conditional access policies, and generates identity maturity reports.
|
|
"""
|
|
|
|
import json
|
|
import csv
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
from typing import Optional
|
|
|
|
|
|
def assess_mfa_strength(mfa_config: dict) -> dict:
|
|
"""Evaluate MFA configuration against zero trust requirements."""
|
|
findings = []
|
|
score = 100
|
|
|
|
methods = mfa_config.get("enabled_methods", [])
|
|
phishing_resistant = {"fido2", "webauthn", "certificate", "windows_hello", "platform_authenticator"}
|
|
phishable = {"sms", "voice", "email_otp"}
|
|
moderate = {"totp", "push_notification", "authenticator_app"}
|
|
|
|
enabled_phishing_resistant = set(methods) & phishing_resistant
|
|
enabled_phishable = set(methods) & phishable
|
|
|
|
if not enabled_phishing_resistant:
|
|
findings.append({
|
|
"severity": "critical",
|
|
"finding": "No phishing-resistant MFA methods enabled",
|
|
"recommendation": "Deploy FIDO2 security keys or platform authenticators",
|
|
"reference": "NIST SP 800-63B AAL3, CISA ZT Identity Pillar (Advanced)"
|
|
})
|
|
score -= 40
|
|
|
|
if enabled_phishable:
|
|
findings.append({
|
|
"severity": "high",
|
|
"finding": f"Phishable MFA methods still enabled: {', '.join(enabled_phishable)}",
|
|
"recommendation": "Disable SMS, voice, and email OTP methods",
|
|
"reference": "CISA Phishing-Resistant MFA Guidance"
|
|
})
|
|
score -= 20
|
|
|
|
if not mfa_config.get("enforced_for_all_users"):
|
|
findings.append({
|
|
"severity": "critical",
|
|
"finding": "MFA not enforced for all users",
|
|
"recommendation": "Enable MFA requirement for all user accounts",
|
|
"reference": "CISA ZT Identity Pillar (Initial)"
|
|
})
|
|
score -= 30
|
|
|
|
if not mfa_config.get("number_matching_enabled") and "push_notification" in methods:
|
|
findings.append({
|
|
"severity": "high",
|
|
"finding": "Push notification MFA without number matching",
|
|
"recommendation": "Enable number matching to prevent MFA fatigue attacks",
|
|
"reference": "Microsoft MFA fatigue defense guidance"
|
|
})
|
|
score -= 10
|
|
|
|
enrollment_rate = mfa_config.get("enrollment_rate_percent", 0)
|
|
if enrollment_rate < 95:
|
|
findings.append({
|
|
"severity": "warning",
|
|
"finding": f"MFA enrollment rate is {enrollment_rate}% (target: 95%+)",
|
|
"recommendation": "Launch enrollment campaign for remaining users",
|
|
"reference": "CISA ZT Identity Pillar (Advanced)"
|
|
})
|
|
score -= 10
|
|
|
|
return {
|
|
"mfa_score": max(score, 0),
|
|
"phishing_resistant_methods": list(enabled_phishing_resistant),
|
|
"phishable_methods": list(enabled_phishable),
|
|
"findings": findings,
|
|
"maturity_level": (
|
|
"optimal" if score >= 90 else
|
|
"advanced" if score >= 70 else
|
|
"initial" if score >= 50 else
|
|
"traditional"
|
|
)
|
|
}
|
|
|
|
|
|
def assess_conditional_access(policies: list) -> dict:
|
|
"""Evaluate conditional access policies for zero trust alignment."""
|
|
findings = []
|
|
coverage = {
|
|
"legacy_auth_blocked": False,
|
|
"mfa_required_all": False,
|
|
"device_compliance_required": False,
|
|
"location_restrictions": False,
|
|
"session_controls": False,
|
|
"risk_based_policies": False,
|
|
"privileged_access_secured": False,
|
|
}
|
|
|
|
for policy in policies:
|
|
conditions = policy.get("conditions", {})
|
|
grant_controls = policy.get("grant_controls", {})
|
|
session_controls = policy.get("session_controls", {})
|
|
|
|
if policy.get("blocks_legacy_auth"):
|
|
coverage["legacy_auth_blocked"] = True
|
|
|
|
if grant_controls.get("require_mfa") and conditions.get("users") == "all":
|
|
coverage["mfa_required_all"] = True
|
|
|
|
if grant_controls.get("require_compliant_device"):
|
|
coverage["device_compliance_required"] = True
|
|
|
|
if conditions.get("locations") and conditions["locations"].get("excluded_trusted"):
|
|
coverage["location_restrictions"] = True
|
|
|
|
if session_controls.get("sign_in_frequency") or session_controls.get("persistent_browser"):
|
|
coverage["session_controls"] = True
|
|
|
|
if conditions.get("user_risk") or conditions.get("sign_in_risk"):
|
|
coverage["risk_based_policies"] = True
|
|
|
|
if conditions.get("roles") and "privileged" in str(conditions.get("roles", [])).lower():
|
|
coverage["privileged_access_secured"] = True
|
|
|
|
if not coverage["legacy_auth_blocked"]:
|
|
findings.append({
|
|
"severity": "critical",
|
|
"finding": "Legacy authentication protocols not blocked",
|
|
"recommendation": "Create policy blocking basic auth, IMAP, POP3, SMTP AUTH",
|
|
})
|
|
|
|
if not coverage["mfa_required_all"]:
|
|
findings.append({
|
|
"severity": "critical",
|
|
"finding": "MFA not required for all users",
|
|
"recommendation": "Create policy requiring MFA for all user sign-ins",
|
|
})
|
|
|
|
if not coverage["device_compliance_required"]:
|
|
findings.append({
|
|
"severity": "high",
|
|
"finding": "Device compliance not required for access",
|
|
"recommendation": "Require managed and compliant devices for sensitive apps",
|
|
})
|
|
|
|
if not coverage["risk_based_policies"]:
|
|
findings.append({
|
|
"severity": "high",
|
|
"finding": "No risk-based conditional access policies",
|
|
"recommendation": "Enable user risk and sign-in risk based policies",
|
|
})
|
|
|
|
if not coverage["session_controls"]:
|
|
findings.append({
|
|
"severity": "warning",
|
|
"finding": "No session lifetime controls configured",
|
|
"recommendation": "Configure sign-in frequency and browser session persistence",
|
|
})
|
|
|
|
covered = sum(1 for v in coverage.values() if v)
|
|
total = len(coverage)
|
|
|
|
return {
|
|
"coverage": coverage,
|
|
"coverage_score": round((covered / total) * 100),
|
|
"policies_evaluated": len(policies),
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def analyze_sign_in_logs(logs: list) -> dict:
|
|
"""Analyze sign-in logs for identity security insights."""
|
|
stats = {
|
|
"total_sign_ins": len(logs),
|
|
"successful": 0,
|
|
"failed": 0,
|
|
"mfa_challenged": 0,
|
|
"risk_events": [],
|
|
"locations": defaultdict(int),
|
|
"devices": defaultdict(int),
|
|
"applications": defaultdict(int),
|
|
"auth_methods": defaultdict(int),
|
|
"risky_sign_ins": [],
|
|
}
|
|
|
|
for log in logs:
|
|
if log.get("status") == "success":
|
|
stats["successful"] += 1
|
|
else:
|
|
stats["failed"] += 1
|
|
|
|
if log.get("mfa_required"):
|
|
stats["mfa_challenged"] += 1
|
|
|
|
location = log.get("location", {}).get("country", "unknown")
|
|
stats["locations"][location] += 1
|
|
|
|
device = log.get("device", {}).get("os", "unknown")
|
|
stats["devices"][device] += 1
|
|
|
|
app = log.get("application", "unknown")
|
|
stats["applications"][app] += 1
|
|
|
|
method = log.get("auth_method", "password")
|
|
stats["auth_methods"][method] += 1
|
|
|
|
risk_level = log.get("risk_level", "none")
|
|
if risk_level in ("medium", "high"):
|
|
stats["risky_sign_ins"].append({
|
|
"user": log.get("user", "unknown"),
|
|
"risk_level": risk_level,
|
|
"risk_detail": log.get("risk_detail", ""),
|
|
"location": location,
|
|
"timestamp": log.get("timestamp", ""),
|
|
"application": app,
|
|
})
|
|
|
|
stats["locations"] = dict(stats["locations"])
|
|
stats["devices"] = dict(stats["devices"])
|
|
stats["applications"] = dict(stats["applications"])
|
|
stats["auth_methods"] = dict(stats["auth_methods"])
|
|
stats["mfa_coverage_percent"] = round(
|
|
(stats["mfa_challenged"] / max(stats["total_sign_ins"], 1)) * 100, 1
|
|
)
|
|
|
|
return stats
|
|
|
|
|
|
def detect_impossible_travel(sign_ins: list, max_speed_kmh: int = 900) -> list:
|
|
"""Detect impossible travel scenarios from sign-in logs."""
|
|
alerts = []
|
|
user_logins = defaultdict(list)
|
|
|
|
for si in sign_ins:
|
|
user = si.get("user", "")
|
|
if user and si.get("location", {}).get("latitude"):
|
|
user_logins[user].append(si)
|
|
|
|
for user, logins in user_logins.items():
|
|
sorted_logins = sorted(logins, key=lambda x: x.get("timestamp", ""))
|
|
|
|
for i in range(1, len(sorted_logins)):
|
|
prev = sorted_logins[i - 1]
|
|
curr = sorted_logins[i]
|
|
|
|
lat1 = prev["location"].get("latitude", 0)
|
|
lon1 = prev["location"].get("longitude", 0)
|
|
lat2 = curr["location"].get("latitude", 0)
|
|
lon2 = curr["location"].get("longitude", 0)
|
|
|
|
import math
|
|
R = 6371
|
|
dlat = math.radians(lat2 - lat1)
|
|
dlon = math.radians(lon2 - lon1)
|
|
a = (math.sin(dlat / 2) ** 2 +
|
|
math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
|
|
math.sin(dlon / 2) ** 2)
|
|
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
|
|
distance_km = R * c
|
|
|
|
try:
|
|
t1 = datetime.fromisoformat(prev.get("timestamp", "").replace("Z", "+00:00"))
|
|
t2 = datetime.fromisoformat(curr.get("timestamp", "").replace("Z", "+00:00"))
|
|
time_diff_hours = (t2 - t1).total_seconds() / 3600
|
|
except (ValueError, TypeError):
|
|
continue
|
|
|
|
if time_diff_hours > 0:
|
|
required_speed = distance_km / time_diff_hours
|
|
if required_speed > max_speed_kmh and distance_km > 100:
|
|
alerts.append({
|
|
"user": user,
|
|
"severity": "high",
|
|
"type": "impossible_travel",
|
|
"from_location": prev["location"],
|
|
"to_location": curr["location"],
|
|
"distance_km": round(distance_km),
|
|
"time_diff_hours": round(time_diff_hours, 2),
|
|
"required_speed_kmh": round(required_speed),
|
|
"from_time": prev.get("timestamp"),
|
|
"to_time": curr.get("timestamp"),
|
|
})
|
|
|
|
return alerts
|
|
|
|
|
|
def generate_identity_maturity_report(config: dict) -> dict:
|
|
"""Generate a comprehensive identity maturity assessment."""
|
|
report = {
|
|
"generated": datetime.now().isoformat(),
|
|
"overall_maturity": "traditional",
|
|
"pillars": {},
|
|
}
|
|
|
|
mfa_assessment = assess_mfa_strength(config.get("mfa", {}))
|
|
report["pillars"]["mfa"] = mfa_assessment
|
|
|
|
ca_assessment = assess_conditional_access(config.get("conditional_access_policies", []))
|
|
report["pillars"]["conditional_access"] = ca_assessment
|
|
|
|
governance = config.get("governance", {})
|
|
gov_score = 0
|
|
if governance.get("automated_provisioning"): gov_score += 25
|
|
if governance.get("access_reviews_enabled"): gov_score += 25
|
|
if governance.get("jit_access_enabled"): gov_score += 25
|
|
if governance.get("lifecycle_automation"): gov_score += 25
|
|
report["pillars"]["governance"] = {"score": gov_score}
|
|
|
|
monitoring = config.get("monitoring", {})
|
|
mon_score = 0
|
|
if monitoring.get("siem_integration"): mon_score += 25
|
|
if monitoring.get("ueba_enabled"): mon_score += 25
|
|
if monitoring.get("identity_threat_detection"): mon_score += 25
|
|
if monitoring.get("cae_enabled"): mon_score += 25
|
|
report["pillars"]["monitoring"] = {"score": mon_score}
|
|
|
|
avg_score = (
|
|
mfa_assessment["mfa_score"] +
|
|
ca_assessment["coverage_score"] +
|
|
gov_score +
|
|
mon_score
|
|
) / 4
|
|
|
|
report["overall_score"] = round(avg_score)
|
|
report["overall_maturity"] = (
|
|
"optimal" if avg_score >= 90 else
|
|
"advanced" if avg_score >= 70 else
|
|
"initial" if avg_score >= 50 else
|
|
"traditional"
|
|
)
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
parser = argparse.ArgumentParser(description="Identity Verification Assessment Tool")
|
|
parser.add_argument("--config", type=str, help="Path to identity configuration JSON")
|
|
parser.add_argument("--logs", type=str, help="Path to sign-in logs JSON")
|
|
parser.add_argument("--action", choices=["assess", "analyze-logs", "detect-travel", "report"],
|
|
default="report")
|
|
parser.add_argument("--output", type=str, default="identity_report.json")
|
|
args = parser.parse_args()
|
|
|
|
if args.action == "assess" and args.config:
|
|
with open(args.config) as f:
|
|
config = json.load(f)
|
|
mfa_result = assess_mfa_strength(config.get("mfa", {}))
|
|
ca_result = assess_conditional_access(config.get("conditional_access_policies", []))
|
|
result = {"mfa": mfa_result, "conditional_access": ca_result}
|
|
with open(args.output, "w") as f:
|
|
json.dump(result, f, indent=2)
|
|
print(f"MFA Score: {mfa_result['mfa_score']}, CA Coverage: {ca_result['coverage_score']}%")
|
|
|
|
elif args.action == "analyze-logs" and args.logs:
|
|
with open(args.logs) as f:
|
|
logs = json.load(f)
|
|
stats = analyze_sign_in_logs(logs)
|
|
with open(args.output, "w") as f:
|
|
json.dump(stats, f, indent=2)
|
|
print(f"Analyzed {stats['total_sign_ins']} sign-ins, {len(stats['risky_sign_ins'])} risky")
|
|
|
|
elif args.action == "detect-travel" and args.logs:
|
|
with open(args.logs) as f:
|
|
logs = json.load(f)
|
|
alerts = detect_impossible_travel(logs)
|
|
with open(args.output, "w") as f:
|
|
json.dump(alerts, f, indent=2)
|
|
print(f"Detected {len(alerts)} impossible travel events")
|
|
|
|
elif args.action == "report" and args.config:
|
|
with open(args.config) as f:
|
|
config = json.load(f)
|
|
report = generate_identity_maturity_report(config)
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2)
|
|
print(f"Identity Maturity: {report['overall_maturity']} (Score: {report['overall_score']})")
|
|
|
|
else:
|
|
parser.print_help()
|
|
|
|
if args.output and Path(args.output).exists():
|
|
print(f"Output saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|