mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 13:44:56 +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
163 lines
6.9 KiB
Python
163 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for auditing and configuring Google Workspace SAML SSO."""
|
|
|
|
import json
|
|
import argparse
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from cryptography import x509
|
|
except ImportError:
|
|
x509 = None
|
|
|
|
|
|
def parse_saml_certificate(cert_path):
|
|
"""Parse and validate an IdP SAML signing certificate."""
|
|
if not x509:
|
|
return {"error": "cryptography library not available"}
|
|
with open(cert_path, "rb") as f:
|
|
pem_data = f.read()
|
|
cert = x509.load_pem_x509_certificate(pem_data)
|
|
now = datetime.utcnow()
|
|
return {
|
|
"subject": cert.subject.rfc4514_string(),
|
|
"issuer": cert.issuer.rfc4514_string(),
|
|
"not_before": str(cert.not_valid_before_utc),
|
|
"not_after": str(cert.not_valid_after_utc),
|
|
"serial": str(cert.serial_number),
|
|
"key_size": cert.public_key().key_size if hasattr(cert.public_key(), "key_size") else None,
|
|
"expired": cert.not_valid_after_utc.replace(tzinfo=None) < now,
|
|
"days_until_expiry": (cert.not_valid_after_utc.replace(tzinfo=None) - now).days,
|
|
"signature_algorithm": cert.signature_algorithm_oid.dotted_string,
|
|
}
|
|
|
|
|
|
def audit_sso_config(config_path):
|
|
"""Audit Google Workspace SSO configuration."""
|
|
with open(config_path) as f:
|
|
config = json.load(f)
|
|
findings = []
|
|
sso = config.get("sso", config)
|
|
|
|
if not sso.get("sso_enabled", sso.get("enabled", False)):
|
|
findings.append({"issue": "SSO not enabled", "severity": "HIGH"})
|
|
|
|
sign_in_url = sso.get("sign_in_page_url", sso.get("sso_url", ""))
|
|
if sign_in_url and not sign_in_url.startswith("https://"):
|
|
findings.append({"issue": "SSO sign-in URL not HTTPS", "severity": "CRITICAL"})
|
|
|
|
sign_out_url = sso.get("sign_out_page_url", sso.get("logout_url", ""))
|
|
if not sign_out_url:
|
|
findings.append({"issue": "Sign-out URL not configured", "severity": "MEDIUM"})
|
|
|
|
cert_path = sso.get("verification_certificate", sso.get("certificate_path", ""))
|
|
if cert_path and Path(cert_path).exists():
|
|
cert_info = parse_saml_certificate(cert_path)
|
|
if cert_info.get("expired"):
|
|
findings.append({"issue": "IdP certificate expired", "severity": "CRITICAL",
|
|
"detail": cert_info})
|
|
elif cert_info.get("days_until_expiry", 999) < 30:
|
|
findings.append({"issue": f"IdP cert expires in {cert_info['days_until_expiry']} days",
|
|
"severity": "HIGH", "detail": cert_info})
|
|
key_size = cert_info.get("key_size", 0)
|
|
if key_size and key_size < 2048:
|
|
findings.append({"issue": f"Weak IdP cert key size: {key_size}", "severity": "HIGH"})
|
|
elif not cert_path:
|
|
findings.append({"issue": "No verification certificate configured", "severity": "CRITICAL"})
|
|
|
|
if not sso.get("use_domain_specific_issuer", True):
|
|
findings.append({"issue": "Domain-specific issuer not enabled", "severity": "MEDIUM"})
|
|
|
|
if sso.get("allow_password_auth_when_sso_enabled", True):
|
|
findings.append({"issue": "Password auth still allowed alongside SSO", "severity": "MEDIUM",
|
|
"recommendation": "Disable direct password login for SSO users"})
|
|
|
|
if not findings:
|
|
findings.append({"issue": "No issues found", "severity": "INFO"})
|
|
return findings
|
|
|
|
|
|
def generate_saml_config(idp_entity_id, sso_url, slo_url, cert_path, domain):
|
|
"""Generate Google Workspace SAML SSO configuration."""
|
|
return {
|
|
"sso_enabled": True,
|
|
"sign_in_page_url": sso_url,
|
|
"sign_out_page_url": slo_url,
|
|
"change_password_url": f"https://{idp_entity_id}/change-password",
|
|
"verification_certificate": cert_path,
|
|
"use_domain_specific_issuer": True,
|
|
"domain": domain,
|
|
"saml_settings": {
|
|
"idp_entity_id": idp_entity_id,
|
|
"sp_entity_id": f"google.com/a/{domain}",
|
|
"acs_url": f"https://accounts.google.com/samlrp/acs?rpid=RPID",
|
|
"name_id_format": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
|
|
},
|
|
}
|
|
|
|
|
|
def audit_sso_login_activity(log_path):
|
|
"""Audit SSO login activity for anomalies."""
|
|
with open(log_path) as f:
|
|
events = json.load(f)
|
|
items = events if isinstance(events, list) else events.get("events", [])
|
|
total = len(items)
|
|
failures = [e for e in items if e.get("status") in ("failed", "error", "FAILED")]
|
|
sso_logins = [e for e in items if e.get("auth_method") == "saml_sso"]
|
|
password_logins = [e for e in items if e.get("auth_method") == "password"]
|
|
return {
|
|
"total_logins": total,
|
|
"sso_logins": len(sso_logins),
|
|
"password_logins": len(password_logins),
|
|
"sso_adoption_rate": round(len(sso_logins) / total * 100, 1) if total else 0,
|
|
"failures": len(failures),
|
|
"failure_rate": round(len(failures) / total * 100, 1) if total else 0,
|
|
"recent_failures": failures[:10],
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Google Workspace SSO Agent")
|
|
parser.add_argument("--config", help="SSO config JSON to audit")
|
|
parser.add_argument("--cert", help="IdP SAML certificate PEM file")
|
|
parser.add_argument("--login-log", help="Login activity log JSON")
|
|
parser.add_argument("--action", choices=["audit", "cert", "activity", "generate", "full"],
|
|
default="full")
|
|
parser.add_argument("--idp-url", help="IdP SSO URL")
|
|
parser.add_argument("--domain", help="Google Workspace domain")
|
|
parser.add_argument("--output", default="gws_sso_report.json")
|
|
args = parser.parse_args()
|
|
|
|
report = {"generated_at": datetime.utcnow().isoformat(), "results": {}}
|
|
|
|
if args.action in ("audit", "full") and args.config:
|
|
findings = audit_sso_config(args.config)
|
|
report["results"]["audit"] = findings
|
|
issues = sum(1 for f in findings if f["severity"] not in ("INFO",))
|
|
print(f"[+] SSO audit: {issues} issues found")
|
|
|
|
if args.action in ("cert", "full") and args.cert:
|
|
cert_info = parse_saml_certificate(args.cert)
|
|
report["results"]["certificate"] = cert_info
|
|
status = "EXPIRED" if cert_info.get("expired") else "VALID"
|
|
print(f"[+] Certificate: {status}, expires in {cert_info.get('days_until_expiry')} days")
|
|
|
|
if args.action in ("activity", "full") and args.login_log:
|
|
activity = audit_sso_login_activity(args.login_log)
|
|
report["results"]["activity"] = activity
|
|
print(f"[+] SSO adoption: {activity['sso_adoption_rate']}%")
|
|
|
|
if args.action == "generate" and args.idp_url and args.domain:
|
|
config = generate_saml_config("idp", args.idp_url, "", args.cert or "", args.domain)
|
|
report["results"]["generated_config"] = config
|
|
print("[+] SAML config 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()
|