forked from JoasASantos/NeuroSploit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscheduler.py
More file actions
executable file
·219 lines (191 loc) · 8.25 KB
/
Copy pathscheduler.py
File metadata and controls
executable file
·219 lines (191 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/usr/bin/env python3
"""
Scan Scheduler - Recurring task orchestration for NeuroSploit.
Supports cron expressions and interval-based scheduling for:
- Reconnaissance scans
- Vulnerability validation
- Re-analysis of previous findings
Uses APScheduler with SQLite persistence so jobs survive restarts.
"""
import json
import logging
from datetime import datetime
from typing import Dict, List, Optional
from pathlib import Path
logger = logging.getLogger(__name__)
try:
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
HAS_APSCHEDULER = True
except ImportError:
HAS_APSCHEDULER = False
logger.warning("APScheduler not installed. Scheduler disabled. Install with: pip install apscheduler>=3.10.0")
class ScanScheduler:
"""Manages recurring scan jobs via APScheduler."""
def __init__(self, config: Dict, database_url: str = "sqlite:///./data/neurosploit_scheduler.db"):
self.config = config
self.scheduler_config = config.get('scheduler', {})
self.enabled = self.scheduler_config.get('enabled', False)
self.jobs_meta: Dict[str, Dict] = {} # job_id -> metadata
self._scan_callback = None
if not HAS_APSCHEDULER:
self.enabled = False
self.scheduler = None
return
jobstores = {
'default': SQLAlchemyJobStore(url=database_url)
}
self.scheduler = AsyncIOScheduler(jobstores=jobstores)
# Load pre-configured jobs from config
for job_config in self.scheduler_config.get('jobs', []):
try:
self.add_job(
job_id=job_config['id'],
target=job_config['target'],
scan_type=job_config.get('scan_type', 'quick'),
cron_expression=job_config.get('cron'),
interval_minutes=job_config.get('interval_minutes'),
agent_role=job_config.get('agent_role'),
llm_profile=job_config.get('llm_profile')
)
except Exception as e:
logger.error(f"Failed to load scheduled job '{job_config.get('id', '?')}': {e}")
def set_scan_callback(self, callback):
"""Set the callback function that executes scans.
The callback signature should be:
async def callback(target: str, scan_type: str,
agent_role: Optional[str], llm_profile: Optional[str]) -> Dict
"""
self._scan_callback = callback
def add_job(self, job_id: str, target: str, scan_type: str = "quick",
cron_expression: Optional[str] = None,
interval_minutes: Optional[int] = None,
agent_role: Optional[str] = None,
llm_profile: Optional[str] = None) -> Dict:
"""Schedule a recurring scan job.
Args:
job_id: Unique identifier for the job
target: Target URL or IP
scan_type: 'quick', 'full', 'recon', or 'analysis'
cron_expression: Cron schedule (e.g., '0 */6 * * *' for every 6 hours)
interval_minutes: Alternative to cron - run every N minutes
agent_role: Optional agent role for AI analysis
llm_profile: Optional LLM profile override
"""
if not self.scheduler:
return {"error": "Scheduler not available (APScheduler not installed)"}
if cron_expression:
trigger = CronTrigger.from_crontab(cron_expression)
schedule_desc = f"cron: {cron_expression}"
elif interval_minutes:
trigger = IntervalTrigger(minutes=interval_minutes)
schedule_desc = f"every {interval_minutes} minutes"
else:
return {"error": "Provide either cron_expression or interval_minutes"}
self.scheduler.add_job(
self._execute_scheduled_scan,
trigger=trigger,
id=job_id,
args=[target, scan_type, agent_role, llm_profile],
replace_existing=True,
name=f"scan_{target}_{scan_type}"
)
meta = {
"id": job_id,
"target": target,
"scan_type": scan_type,
"schedule": schedule_desc,
"agent_role": agent_role,
"llm_profile": llm_profile,
"created_at": datetime.now().isoformat(),
"last_run": None,
"run_count": 0,
"status": "active"
}
self.jobs_meta[job_id] = meta
logger.info(f"Scheduled job '{job_id}': {target} ({scan_type}) - {schedule_desc}")
return meta
def remove_job(self, job_id: str) -> bool:
"""Remove a scheduled job."""
if not self.scheduler:
return False
try:
self.scheduler.remove_job(job_id)
self.jobs_meta.pop(job_id, None)
logger.info(f"Removed scheduled job: {job_id}")
return True
except Exception as e:
logger.error(f"Failed to remove job '{job_id}': {e}")
return False
def pause_job(self, job_id: str) -> bool:
"""Pause a scheduled job."""
if not self.scheduler:
return False
try:
self.scheduler.pause_job(job_id)
if job_id in self.jobs_meta:
self.jobs_meta[job_id]["status"] = "paused"
return True
except Exception as e:
logger.error(f"Failed to pause job '{job_id}': {e}")
return False
def resume_job(self, job_id: str) -> bool:
"""Resume a paused job."""
if not self.scheduler:
return False
try:
self.scheduler.resume_job(job_id)
if job_id in self.jobs_meta:
self.jobs_meta[job_id]["status"] = "active"
return True
except Exception as e:
logger.error(f"Failed to resume job '{job_id}': {e}")
return False
def list_jobs(self) -> List[Dict]:
"""List all scheduled jobs with metadata."""
jobs = []
if self.scheduler:
for job in self.scheduler.get_jobs():
meta = self.jobs_meta.get(job.id, {})
jobs.append({
"id": job.id,
"name": job.name,
"next_run": str(job.next_run_time) if job.next_run_time else None,
"target": meta.get("target", "unknown"),
"scan_type": meta.get("scan_type", "unknown"),
"schedule": meta.get("schedule", "unknown"),
"status": meta.get("status", "active"),
"last_run": meta.get("last_run"),
"run_count": meta.get("run_count", 0)
})
return jobs
async def _execute_scheduled_scan(self, target: str, scan_type: str,
agent_role: Optional[str],
llm_profile: Optional[str]):
"""Execute a scheduled scan. Called by APScheduler."""
job_id = f"scan_{target}_{scan_type}"
logger.info(f"Executing scheduled scan: {target} ({scan_type})")
if job_id in self.jobs_meta:
self.jobs_meta[job_id]["last_run"] = datetime.now().isoformat()
self.jobs_meta[job_id]["run_count"] += 1
if self._scan_callback:
try:
result = await self._scan_callback(target, scan_type, agent_role, llm_profile)
logger.info(f"Scheduled scan completed: {target} ({scan_type})")
return result
except Exception as e:
logger.error(f"Scheduled scan failed for {target}: {e}")
else:
logger.warning("No scan callback registered. Scheduled scan skipped.")
def start(self):
"""Start the scheduler."""
if self.scheduler and self.enabled:
self.scheduler.start()
logger.info(f"Scheduler started with {len(self.list_jobs())} jobs")
def stop(self):
"""Stop the scheduler gracefully."""
if self.scheduler and self.scheduler.running:
self.scheduler.shutdown(wait=False)
logger.info("Scheduler stopped")