Migrate from file-based configs to database with per-IP site configuration
Major architectural changes: - Replace YAML config files with database-stored ScanConfig model - Remove CIDR block support in favor of individual IP addresses per site - Each IP now has its own expected_ping, expected_tcp_ports, expected_udp_ports - AlertRule now uses config_id FK instead of config_file string API changes: - POST /api/scans now requires config_id instead of config_file - Alert rules API uses config_id with validation - All config dropdowns fetch from /api/configs dynamically Template updates: - scans.html, dashboard.html, alert_rules.html load configs via API - Display format: Config Title (X sites) in dropdowns - Removed Jinja2 config_files loops Migrations: - 008: Expand CIDRs to individual IPs with per-IP port configs - 009: Remove CIDR-related columns - 010: Add config_id to alert_rules, remove config_file
This commit is contained in:
@@ -267,7 +267,7 @@ class Site(Base):
|
||||
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')
|
||||
ips = relationship('SiteIP', back_populates='site', cascade='all, delete-orphan')
|
||||
scan_associations = relationship('ScanSiteAssociation', back_populates='site')
|
||||
config_associations = relationship('ScanConfigSite', back_populates='site')
|
||||
|
||||
@@ -275,59 +275,29 @@ class Site(Base):
|
||||
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.
|
||||
Individual IP addresses with their own settings.
|
||||
|
||||
Allows fine-grained control where specific IPs within a CIDR have
|
||||
different expectations than the CIDR-level defaults.
|
||||
Each IP is directly associated with a site and has its own port and ping settings.
|
||||
IPs are standalone entities - CIDRs are only used as a convenience for bulk creation.
|
||||
"""
|
||||
__tablename__ = 'site_ips'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
site_cidr_id = Column(Integer, ForeignKey('site_cidrs.id'), nullable=False, index=True)
|
||||
site_id = Column(Integer, ForeignKey('sites.id'), nullable=False, index=True, comment="FK to sites")
|
||||
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")
|
||||
expected_ping = Column(Boolean, nullable=True, comment="Expected ping response for this IP")
|
||||
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="IP creation time")
|
||||
|
||||
# Relationships
|
||||
cidr = relationship('SiteCIDR', back_populates='ips')
|
||||
site = relationship('Site', back_populates='ips')
|
||||
|
||||
# Index for efficient IP lookups
|
||||
# Index for efficient IP lookups - prevent duplicate IPs within a site
|
||||
__table_args__ = (
|
||||
UniqueConstraint('site_cidr_id', 'ip_address', name='uix_site_cidr_ip'),
|
||||
UniqueConstraint('site_id', 'ip_address', name='uix_site_ip_address'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -507,12 +477,13 @@ class AlertRule(Base):
|
||||
webhook_enabled = Column(Boolean, nullable=False, default=False, comment="Send webhook for this rule?")
|
||||
severity = Column(String(20), nullable=True, comment="Alert severity: critical, warning, info")
|
||||
filter_conditions = Column(Text, nullable=True, comment="JSON filter conditions for the rule")
|
||||
config_file = Column(String(255), nullable=True, comment="Optional: specific config file this rule applies to")
|
||||
config_id = Column(Integer, ForeignKey('scan_configs.id'), nullable=True, index=True, comment="Optional: specific config this rule applies to")
|
||||
created_at = Column(DateTime, nullable=False, default=datetime.utcnow, comment="Rule creation time")
|
||||
updated_at = Column(DateTime, nullable=True, comment="Last update time")
|
||||
|
||||
# Relationships
|
||||
alerts = relationship("Alert", back_populates="rule", cascade="all, delete-orphan")
|
||||
config = relationship("ScanConfig", backref="alert_rules")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<AlertRule(id={self.id}, name='{self.name}', rule_type='{self.rule_type}', enabled={self.enabled})>"
|
||||
|
||||
Reference in New Issue
Block a user