stage 1 of doing new cidrs/ site setup
This commit is contained in:
@@ -46,7 +46,8 @@ class Scan(Base):
|
||||
timestamp = Column(DateTime, nullable=False, index=True, comment="Scan start time (UTC)")
|
||||
duration = Column(Float, nullable=True, comment="Total scan duration in seconds")
|
||||
status = Column(String(20), nullable=False, default='running', comment="running, completed, failed")
|
||||
config_file = Column(Text, nullable=True, comment="Path to YAML config used")
|
||||
config_file = Column(Text, nullable=True, comment="Path to YAML config used (deprecated)")
|
||||
config_id = Column(Integer, ForeignKey('scan_configs.id'), nullable=True, index=True, comment="FK to scan_configs table")
|
||||
title = Column(Text, nullable=True, comment="Scan title from config")
|
||||
json_path = Column(Text, nullable=True, comment="Path to JSON report")
|
||||
html_path = Column(Text, nullable=True, comment="Path to HTML report")
|
||||
@@ -68,6 +69,8 @@ class Scan(Base):
|
||||
tls_versions = relationship('ScanTLSVersion', back_populates='scan', cascade='all, delete-orphan')
|
||||
alerts = relationship('Alert', back_populates='scan', cascade='all, delete-orphan')
|
||||
schedule = relationship('Schedule', back_populates='scans')
|
||||
config = relationship('ScanConfig', back_populates='scans')
|
||||
site_associations = relationship('ScanSiteAssociation', back_populates='scan', cascade='all, delete-orphan')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Scan(id={self.id}, title='{self.title}', status='{self.status}')>"
|
||||
@@ -242,6 +245,178 @@ class ScanTLSVersion(Base):
|
||||
return f"<ScanTLSVersion(id={self.id}, tls_version='{self.tls_version}', supported={self.supported})>"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Reusable Site Definition Tables
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class Site(Base):
|
||||
"""
|
||||
Master site definition (reusable across scans).
|
||||
|
||||
Sites represent logical network segments (e.g., "Production DC", "DMZ",
|
||||
"Branch Office") that can be reused across multiple scans. Each site
|
||||
contains one or more CIDR ranges.
|
||||
"""
|
||||
__tablename__ = 'sites'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(255), nullable=False, unique=True, index=True, comment="Unique site name")
|
||||
description = Column(Text, nullable=True, comment="Site description")
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="Site creation time")
|
||||
updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow, comment="Last modification time")
|
||||
|
||||
# Relationships
|
||||
cidrs = relationship('SiteCIDR', back_populates='site', cascade='all, delete-orphan')
|
||||
scan_associations = relationship('ScanSiteAssociation', back_populates='site')
|
||||
config_associations = relationship('ScanConfigSite', back_populates='site')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Site(id={self.id}, name='{self.name}')>"
|
||||
|
||||
|
||||
class SiteCIDR(Base):
|
||||
"""
|
||||
CIDR ranges associated with a site.
|
||||
|
||||
Each site must have at least one CIDR range. CIDR-level expectations
|
||||
(ping, ports) apply to all IPs in the range unless overridden at the IP level.
|
||||
"""
|
||||
__tablename__ = 'site_cidrs'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
site_id = Column(Integer, ForeignKey('sites.id'), nullable=False, index=True)
|
||||
cidr = Column(String(45), nullable=False, comment="CIDR notation (e.g., 10.0.0.0/24)")
|
||||
expected_ping = Column(Boolean, nullable=True, default=False, comment="Expected ping response for this CIDR")
|
||||
expected_tcp_ports = Column(Text, nullable=True, comment="JSON array of expected TCP ports")
|
||||
expected_udp_ports = Column(Text, nullable=True, comment="JSON array of expected UDP ports")
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="CIDR creation time")
|
||||
|
||||
# Relationships
|
||||
site = relationship('Site', back_populates='cidrs')
|
||||
ips = relationship('SiteIP', back_populates='cidr', cascade='all, delete-orphan')
|
||||
|
||||
# Index for efficient CIDR lookups within a site
|
||||
__table_args__ = (
|
||||
UniqueConstraint('site_id', 'cidr', name='uix_site_cidr'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SiteCIDR(id={self.id}, cidr='{self.cidr}')>"
|
||||
|
||||
|
||||
class SiteIP(Base):
|
||||
"""
|
||||
IP-level expectation overrides within a CIDR range.
|
||||
|
||||
Allows fine-grained control where specific IPs within a CIDR have
|
||||
different expectations than the CIDR-level defaults.
|
||||
"""
|
||||
__tablename__ = 'site_ips'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
site_cidr_id = Column(Integer, ForeignKey('site_cidrs.id'), nullable=False, index=True)
|
||||
ip_address = Column(String(45), nullable=False, comment="IPv4 or IPv6 address")
|
||||
expected_ping = Column(Boolean, nullable=True, comment="Override ping expectation for this IP")
|
||||
expected_tcp_ports = Column(Text, nullable=True, comment="JSON array of expected TCP ports (overrides CIDR)")
|
||||
expected_udp_ports = Column(Text, nullable=True, comment="JSON array of expected UDP ports (overrides CIDR)")
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="IP override creation time")
|
||||
|
||||
# Relationships
|
||||
cidr = relationship('SiteCIDR', back_populates='ips')
|
||||
|
||||
# Index for efficient IP lookups
|
||||
__table_args__ = (
|
||||
UniqueConstraint('site_cidr_id', 'ip_address', name='uix_site_cidr_ip'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<SiteIP(id={self.id}, ip_address='{self.ip_address}')>"
|
||||
|
||||
|
||||
class ScanSiteAssociation(Base):
|
||||
"""
|
||||
Many-to-many relationship between scans and sites.
|
||||
|
||||
Tracks which sites were included in which scans. This allows sites
|
||||
to be reused across multiple scans.
|
||||
"""
|
||||
__tablename__ = 'scan_site_associations'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
scan_id = Column(Integer, ForeignKey('scans.id'), nullable=False, index=True)
|
||||
site_id = Column(Integer, ForeignKey('sites.id'), nullable=False, index=True)
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="Association creation time")
|
||||
|
||||
# Relationships
|
||||
scan = relationship('Scan', back_populates='site_associations')
|
||||
site = relationship('Site', back_populates='scan_associations')
|
||||
|
||||
# Index to prevent duplicate associations
|
||||
__table_args__ = (
|
||||
UniqueConstraint('scan_id', 'site_id', name='uix_scan_site'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ScanSiteAssociation(scan_id={self.scan_id}, site_id={self.site_id})>"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Scan Configuration Tables
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class ScanConfig(Base):
|
||||
"""
|
||||
Scan configurations stored in database (replaces YAML files).
|
||||
|
||||
Stores reusable scan configurations that reference sites from the
|
||||
sites table. Configs define what sites to scan together.
|
||||
"""
|
||||
__tablename__ = 'scan_configs'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
title = Column(String(255), nullable=False, comment="Configuration title")
|
||||
description = Column(Text, nullable=True, comment="Configuration description")
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="Config creation time")
|
||||
updated_at = Column(DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow, comment="Last modification time")
|
||||
|
||||
# Relationships
|
||||
site_associations = relationship('ScanConfigSite', back_populates='config', cascade='all, delete-orphan')
|
||||
scans = relationship('Scan', back_populates='config')
|
||||
schedules = relationship('Schedule', back_populates='config')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ScanConfig(id={self.id}, title='{self.title}')>"
|
||||
|
||||
|
||||
class ScanConfigSite(Base):
|
||||
"""
|
||||
Many-to-many relationship between scan configs and sites.
|
||||
|
||||
Links scan configurations to the sites they should scan. A config
|
||||
can reference multiple sites, and sites can be used in multiple configs.
|
||||
"""
|
||||
__tablename__ = 'scan_config_sites'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
config_id = Column(Integer, ForeignKey('scan_configs.id'), nullable=False, index=True)
|
||||
site_id = Column(Integer, ForeignKey('sites.id'), nullable=False, index=True)
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="Association creation time")
|
||||
|
||||
# Relationships
|
||||
config = relationship('ScanConfig', back_populates='site_associations')
|
||||
site = relationship('Site', back_populates='config_associations')
|
||||
|
||||
# Index to prevent duplicate associations
|
||||
__table_args__ = (
|
||||
UniqueConstraint('config_id', 'site_id', name='uix_config_site'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<ScanConfigSite(config_id={self.config_id}, site_id={self.site_id})>"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Scheduling & Notifications Tables
|
||||
# ============================================================================
|
||||
@@ -258,7 +433,8 @@ class Schedule(Base):
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(255), nullable=False, comment="Schedule name (e.g., 'Daily prod scan')")
|
||||
config_file = Column(Text, nullable=False, comment="Path to YAML config")
|
||||
config_file = Column(Text, nullable=True, comment="Path to YAML config (deprecated)")
|
||||
config_id = Column(Integer, ForeignKey('scan_configs.id'), nullable=True, index=True, comment="FK to scan_configs table")
|
||||
cron_expression = Column(String(100), nullable=False, comment="Cron-like schedule (e.g., '0 2 * * *')")
|
||||
enabled = Column(Boolean, nullable=False, default=True, comment="Is schedule active?")
|
||||
last_run = Column(DateTime, nullable=True, comment="Last execution time")
|
||||
@@ -268,6 +444,7 @@ class Schedule(Base):
|
||||
|
||||
# Relationships
|
||||
scans = relationship('Scan', back_populates='schedule')
|
||||
config = relationship('ScanConfig', back_populates='schedules')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Schedule(id={self.id}, name='{self.name}', enabled={self.enabled})>"
|
||||
|
||||
Reference in New Issue
Block a user