refactor to remove config_files in favor of db

This commit is contained in:
2025-11-19 20:29:14 -06:00
parent b2e6efb4b3
commit 41ba4c47b5
34 changed files with 463 additions and 536 deletions

View File

@@ -19,7 +19,7 @@ from web.models import (
ScanCertificate, ScanTLSVersion, Site, ScanSiteAssociation
)
from web.utils.pagination import paginate, PaginatedResult
from web.utils.validators import validate_config_file, validate_scan_status
from web.utils.validators import validate_scan_status
logger = logging.getLogger(__name__)
@@ -41,7 +41,7 @@ class ScanService:
"""
self.db = db_session
def trigger_scan(self, config_file: str = None, config_id: int = None,
def trigger_scan(self, config_id: int,
triggered_by: str = 'manual', schedule_id: Optional[int] = None,
scheduler=None) -> int:
"""
@@ -51,8 +51,7 @@ class ScanService:
queues the scan for background execution.
Args:
config_file: Path to YAML configuration file (legacy, optional)
config_id: Database config ID (preferred, optional)
config_id: Database config ID
triggered_by: Source that triggered scan (manual, scheduled, api)
schedule_id: Optional schedule ID if triggered by schedule
scheduler: Optional SchedulerService instance for queuing background jobs
@@ -61,106 +60,48 @@ class ScanService:
Scan ID of the created scan
Raises:
ValueError: If config is invalid or both/neither config_file and config_id provided
ValueError: If config is invalid
"""
# Validate that exactly one config source is provided
if not (bool(config_file) ^ bool(config_id)):
raise ValueError("Must provide exactly one of config_file or config_id")
from web.models import ScanConfig
# Handle database config
if config_id:
from web.models import ScanConfig
# Validate config exists
db_config = self.db.query(ScanConfig).filter_by(id=config_id).first()
if not db_config:
raise ValueError(f"Config with ID {config_id} not found")
# Validate config exists
db_config = self.db.query(ScanConfig).filter_by(id=config_id).first()
if not db_config:
raise ValueError(f"Config with ID {config_id} not found")
# Create scan record with config_id
scan = Scan(
timestamp=datetime.utcnow(),
status='running',
config_id=config_id,
title=db_config.title,
triggered_by=triggered_by,
schedule_id=schedule_id,
created_at=datetime.utcnow()
)
# Create scan record with config_id
scan = Scan(
timestamp=datetime.utcnow(),
status='running',
config_id=config_id,
title=db_config.title,
triggered_by=triggered_by,
schedule_id=schedule_id,
created_at=datetime.utcnow()
)
self.db.add(scan)
self.db.commit()
self.db.refresh(scan)
self.db.add(scan)
self.db.commit()
self.db.refresh(scan)
logger.info(f"Scan {scan.id} triggered via {triggered_by} with config_id={config_id}")
logger.info(f"Scan {scan.id} triggered via {triggered_by} with config_id={config_id}")
# Queue background job if scheduler provided
if scheduler:
try:
job_id = scheduler.queue_scan(scan.id, config_id=config_id)
logger.info(f"Scan {scan.id} queued for background execution (job_id={job_id})")
except Exception as e:
logger.error(f"Failed to queue scan {scan.id}: {str(e)}")
# Mark scan as failed if job queuing fails
scan.status = 'failed'
scan.error_message = f"Failed to queue background job: {str(e)}"
self.db.commit()
raise
else:
logger.warning(f"Scan {scan.id} created but not queued (no scheduler provided)")
return scan.id
# Handle legacy YAML config file
# Queue background job if scheduler provided
if scheduler:
try:
job_id = scheduler.queue_scan(scan.id, config_id=config_id)
logger.info(f"Scan {scan.id} queued for background execution (job_id={job_id})")
except Exception as e:
logger.error(f"Failed to queue scan {scan.id}: {str(e)}")
# Mark scan as failed if job queuing fails
scan.status = 'failed'
scan.error_message = f"Failed to queue background job: {str(e)}"
self.db.commit()
raise
else:
# Validate config file
is_valid, error_msg = validate_config_file(config_file)
if not is_valid:
raise ValueError(f"Invalid config file: {error_msg}")
logger.warning(f"Scan {scan.id} created but not queued (no scheduler provided)")
# Convert config_file to full path if it's just a filename
if not config_file.startswith('/'):
config_path = f'/app/configs/{config_file}'
else:
config_path = config_file
# Load config to get title
import yaml
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
# Create scan record
scan = Scan(
timestamp=datetime.utcnow(),
status='running',
config_file=config_file,
title=config.get('title', 'Untitled Scan'),
triggered_by=triggered_by,
schedule_id=schedule_id,
created_at=datetime.utcnow()
)
self.db.add(scan)
self.db.commit()
self.db.refresh(scan)
logger.info(f"Scan {scan.id} triggered via {triggered_by}")
# Queue background job if scheduler provided
if scheduler:
try:
job_id = scheduler.queue_scan(scan.id, config_file=config_file)
logger.info(f"Scan {scan.id} queued for background execution (job_id={job_id})")
except Exception as e:
logger.error(f"Failed to queue scan {scan.id}: {str(e)}")
# Mark scan as failed if job queuing fails
scan.status = 'failed'
scan.error_message = f"Failed to queue background job: {str(e)}"
self.db.commit()
raise
else:
logger.warning(f"Scan {scan.id} created but not queued (no scheduler provided)")
return scan.id
return scan.id
def get_scan(self, scan_id: int) -> Optional[Dict[str, Any]]:
"""
@@ -614,7 +555,7 @@ class ScanService:
'duration': scan.duration,
'status': scan.status,
'title': scan.title,
'config_file': scan.config_file,
'config_id': scan.config_id,
'json_path': scan.json_path,
'html_path': scan.html_path,
'zip_path': scan.zip_path,
@@ -640,7 +581,7 @@ class ScanService:
'duration': scan.duration,
'status': scan.status,
'title': scan.title,
'config_file': scan.config_file,
'config_id': scan.config_id,
'triggered_by': scan.triggered_by,
'created_at': scan.created_at.isoformat() if scan.created_at else None
}
@@ -783,17 +724,17 @@ class ScanService:
return None
# Check if scans use the same configuration
config1 = scan1.get('config_file', '')
config2 = scan2.get('config_file', '')
same_config = (config1 == config2) and (config1 != '')
config1 = scan1.get('config_id')
config2 = scan2.get('config_id')
same_config = (config1 == config2) and (config1 is not None)
# Generate warning message if configs differ
config_warning = None
if not same_config:
config_warning = (
f"These scans use different configurations. "
f"Scan #{scan1_id} used '{config1 or 'unknown'}' and "
f"Scan #{scan2_id} used '{config2 or 'unknown'}'. "
f"Scan #{scan1_id} used config_id={config1 or 'unknown'} and "
f"Scan #{scan2_id} used config_id={config2 or 'unknown'}. "
f"The comparison may show all changes as additions/removals if the scans "
f"cover different IP ranges or infrastructure."
)
@@ -832,14 +773,14 @@ class ScanService:
'timestamp': scan1['timestamp'],
'title': scan1['title'],
'status': scan1['status'],
'config_file': config1
'config_id': config1
},
'scan2': {
'id': scan2['id'],
'timestamp': scan2['timestamp'],
'title': scan2['title'],
'status': scan2['status'],
'config_file': config2
'config_id': config2
},
'same_config': same_config,
'config_warning': config_warning,