170 lines
4.7 KiB
Python
170 lines
4.7 KiB
Python
"""
|
|
Base scanner interface and common utilities.
|
|
|
|
All scanner modules inherit from BaseScanner and implement
|
|
the common interface for running scans and returning results.
|
|
"""
|
|
|
|
import logging
|
|
from abc import ABC, abstractmethod
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Optional
|
|
from enum import Enum
|
|
|
|
logger = logging.getLogger('scanner')
|
|
|
|
|
|
class ScannerStatus(Enum):
|
|
"""Status of a scanner execution."""
|
|
SUCCESS = 'success'
|
|
PARTIAL = 'partial'
|
|
FAILED = 'failed'
|
|
SKIPPED = 'skipped'
|
|
|
|
|
|
@dataclass
|
|
class ScannerResult:
|
|
"""
|
|
Standardized result from any scanner.
|
|
|
|
Attributes:
|
|
status: The execution status of the scanner
|
|
scanner_name: Name of the scanner that produced this result
|
|
scores: Dictionary of score values (0-100)
|
|
metrics: List of metric dictionaries with name, value, unit
|
|
issues: List of issue dictionaries with category, severity, title, etc.
|
|
raw_data: Original response from the scanner tool
|
|
error_message: Error message if the scan failed
|
|
execution_time_seconds: How long the scan took
|
|
"""
|
|
status: ScannerStatus
|
|
scanner_name: str
|
|
scores: dict = field(default_factory=dict)
|
|
metrics: list = field(default_factory=list)
|
|
issues: list = field(default_factory=list)
|
|
raw_data: Optional[dict] = None
|
|
error_message: Optional[str] = None
|
|
execution_time_seconds: float = 0.0
|
|
|
|
def to_dict(self) -> dict:
|
|
"""Convert result to dictionary for serialization."""
|
|
return {
|
|
'status': self.status.value,
|
|
'scanner_name': self.scanner_name,
|
|
'scores': self.scores,
|
|
'metrics': self.metrics,
|
|
'issues': self.issues,
|
|
'raw_data': self.raw_data,
|
|
'error_message': self.error_message,
|
|
'execution_time_seconds': self.execution_time_seconds,
|
|
}
|
|
|
|
|
|
class BaseScanner(ABC):
|
|
"""
|
|
Abstract base class for all scanners.
|
|
|
|
Each scanner must implement the `run` method which takes a URL
|
|
and returns a ScannerResult.
|
|
"""
|
|
|
|
name: str = "base_scanner"
|
|
|
|
def __init__(self, config: dict = None):
|
|
"""
|
|
Initialize the scanner with optional configuration.
|
|
|
|
Args:
|
|
config: Dictionary of configuration options
|
|
"""
|
|
self.config = config or {}
|
|
self.logger = logging.getLogger(f'scanner.{self.name}')
|
|
|
|
@abstractmethod
|
|
def run(self, url: str) -> ScannerResult:
|
|
"""
|
|
Run the scanner on the given URL.
|
|
|
|
Args:
|
|
url: The URL to scan
|
|
|
|
Returns:
|
|
ScannerResult with scan data
|
|
"""
|
|
pass
|
|
|
|
def is_available(self) -> bool:
|
|
"""
|
|
Check if the scanner is available and properly configured.
|
|
|
|
Returns:
|
|
True if the scanner can run, False otherwise
|
|
"""
|
|
return True
|
|
|
|
def _create_issue(
|
|
self,
|
|
category: str,
|
|
severity: str,
|
|
title: str,
|
|
description: str,
|
|
affected_url: str = None,
|
|
remediation: str = None,
|
|
raw_data: dict = None
|
|
) -> dict:
|
|
"""
|
|
Helper to create a standardized issue dictionary.
|
|
|
|
Args:
|
|
category: Issue category (security, performance, etc.)
|
|
severity: Severity level (critical, high, medium, low, info)
|
|
title: Brief issue title
|
|
description: Detailed description
|
|
affected_url: Specific URL affected
|
|
remediation: Suggested fix
|
|
raw_data: Original scanner data
|
|
|
|
Returns:
|
|
Issue dictionary
|
|
"""
|
|
return {
|
|
'category': category,
|
|
'severity': severity,
|
|
'title': title,
|
|
'description': description,
|
|
'affected_url': affected_url,
|
|
'remediation': remediation,
|
|
'tool': self.name,
|
|
'raw_data': raw_data,
|
|
}
|
|
|
|
def _create_metric(
|
|
self,
|
|
name: str,
|
|
display_name: str,
|
|
value: float,
|
|
unit: str,
|
|
score: float = None
|
|
) -> dict:
|
|
"""
|
|
Helper to create a standardized metric dictionary.
|
|
|
|
Args:
|
|
name: Machine-readable metric name
|
|
display_name: Human-readable name
|
|
value: Numeric value
|
|
unit: Unit of measurement
|
|
score: Optional score (0-1)
|
|
|
|
Returns:
|
|
Metric dictionary
|
|
"""
|
|
return {
|
|
'name': name,
|
|
'display_name': display_name,
|
|
'value': value,
|
|
'unit': unit,
|
|
'source': self.name,
|
|
'score': score,
|
|
}
|