#!/usr/bin/env python3 """Splunk SPL Detection Rule Builder Agent - Generates and validates Splunk detection rules.""" import json import logging import os import argparse from datetime import datetime import requests logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) DETECTION_TEMPLATES = { "brute_force": { "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4625 | stats count by src_ip, user | where count > {threshold}', "description": "Detect brute force login attempts", "mitre": "T1110", "severity": "high", }, "lateral_movement_rdp": { "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4624 Logon_Type=10 | stats count by src_ip, dest, user | where count > 1', "description": "Detect RDP lateral movement", "mitre": "T1021.001", "severity": "high", }, "powershell_encoded": { "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4104 ScriptBlockText="*-EncodedCommand*" OR ScriptBlockText="*FromBase64String*" | table _time, ComputerName, ScriptBlockText', "description": "Detect encoded PowerShell execution", "mitre": "T1059.001", "severity": "critical", }, "suspicious_process": { "spl": 'index=main sourcetype=sysmon EventCode=1 (ParentImage="*\\\\cmd.exe" OR ParentImage="*\\\\powershell.exe") (Image="*\\\\whoami.exe" OR Image="*\\\\net.exe" OR Image="*\\\\nltest.exe") | table _time, Computer, User, ParentImage, Image, CommandLine', "description": "Detect suspicious child process spawning", "mitre": "T1059.003", "severity": "high", }, "scheduled_task_creation": { "spl": 'index=main sourcetype=WinEventLog:Security EventCode=4698 | table _time, SubjectUserName, TaskName, TaskContent', "description": "Detect new scheduled task creation", "mitre": "T1053.005", "severity": "medium", }, "credential_dumping": { "spl": 'index=main sourcetype=sysmon EventCode=10 TargetImage="*\\\\lsass.exe" GrantedAccess IN ("0x1010", "0x1038", "0x1fffff") | table _time, SourceImage, TargetImage, GrantedAccess', "description": "Detect LSASS memory access (credential dumping)", "mitre": "T1003.001", "severity": "critical", }, "data_exfiltration": { "spl": 'index=main sourcetype=proxy | stats sum(bytes_out) as total_bytes by src_ip, dest | where total_bytes > {threshold_bytes} | eval MB=round(total_bytes/1024/1024,2)', "description": "Detect large data transfers (exfiltration)", "mitre": "T1048", "severity": "high", }, } def generate_spl_rule(template_name, params=None): """Generate an SPL detection rule from template.""" if template_name not in DETECTION_TEMPLATES: return {"error": f"Unknown template: {template_name}"} template = DETECTION_TEMPLATES[template_name] spl = template["spl"] if params: for key, value in params.items(): spl = spl.replace(f"{{{key}}}", str(value)) return { "name": template_name, "spl": spl, "description": template["description"], "mitre_technique": template["mitre"], "severity": template["severity"], } def deploy_saved_search(splunk_url, token, rule_name, spl, severity="high"): """Deploy a saved search to Splunk via REST API.""" headers = {"Authorization": f"Bearer {token}"} data = { "name": f"Detection - {rule_name}", "search": spl, "is_scheduled": 1, "cron_schedule": "*/5 * * * *", "dispatch.earliest_time": "-5m", "dispatch.latest_time": "now", "alert.severity": {"info": 1, "low": 2, "medium": 3, "high": 4, "critical": 5}.get(severity, 4), "alert_type": "number of events", "alert_comparator": "greater than", "alert_threshold": "0", "actions": "email", } try: resp = requests.post(f"{splunk_url}/servicesNS/admin/search/saved/searches", headers=headers, data=data, verify=not os.environ.get("SKIP_TLS_VERIFY", "").lower() == "true", timeout=30) # Set SKIP_TLS_VERIFY=true for self-signed certs in lab environments return {"status": resp.status_code, "deployed": resp.status_code in (200, 201)} except requests.RequestException as e: return {"error": str(e)} def generate_report(rules, deployment_results=None): """Generate detection rule report.""" report = { "timestamp": datetime.utcnow().isoformat(), "rules_generated": len(rules), "rules": rules, "deployment_results": deployment_results or [], } print(f"SPL REPORT: {len(rules)} detection rules generated") return report def main(): parser = argparse.ArgumentParser(description="Splunk SPL Detection Rule Builder Agent") parser.add_argument("--templates", nargs="*", choices=list(DETECTION_TEMPLATES.keys()), default=list(DETECTION_TEMPLATES.keys())) parser.add_argument("--threshold", type=int, default=10) parser.add_argument("--threshold-bytes", type=int, default=104857600) parser.add_argument("--splunk-url", help="Splunk URL for deployment") parser.add_argument("--splunk-token", help="Splunk auth token") parser.add_argument("--output", default="splunk_rules.json") args = parser.parse_args() rules = [] for template in args.templates: rule = generate_spl_rule(template, {"threshold": args.threshold, "threshold_bytes": args.threshold_bytes}) rules.append(rule) deployments = [] if args.splunk_url and args.splunk_token: for rule in rules: result = deploy_saved_search(args.splunk_url, args.splunk_token, rule["name"], rule["spl"], rule["severity"]) deployments.append({"rule": rule["name"], "result": result}) report = generate_report(rules, deployments) with open(args.output, "w") as f: json.dump(report, f, indent=2) logger.info("Report saved to %s", args.output) if __name__ == "__main__": main()