291 lines
14 KiB
Python
291 lines
14 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
|
|
|
|
def _bool(name: str, default: bool = False) -> bool:
|
|
value = os.getenv(name)
|
|
if value is None:
|
|
return default
|
|
return value.strip().lower() in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def _int(name: str, default: int) -> int:
|
|
value = os.getenv(name)
|
|
if value is None or not value.strip():
|
|
return default
|
|
return int(value)
|
|
|
|
|
|
|
|
def _choice(name: str, default: str, allowed: set[str]) -> str:
|
|
value = (os.getenv(name, default) or default).strip().lower()
|
|
return value if value in allowed else default
|
|
|
|
|
|
def _float(name: str, default: float) -> float:
|
|
value = os.getenv(name)
|
|
if value is None or not value.strip():
|
|
return default
|
|
return float(value)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Config:
|
|
tzsp_bind: str
|
|
tzsp_port: int
|
|
tap_name: str
|
|
tap_mtu: int
|
|
tzsp_receiver_bin: str
|
|
tzsp_telemetry_socket: str
|
|
tzsp_rcvbuf_bytes: int
|
|
tzsp_batch_size: int
|
|
tzsp_queue_mb: int
|
|
tzsp_datagram_bytes: int
|
|
suricata_config: str
|
|
suricata_output_config: str
|
|
suricata_home_net: str
|
|
suricata_local_rules: str
|
|
suricata_extra_rules_glob: str
|
|
suricata_custom_rules: str
|
|
suricata_threshold_config: str
|
|
suricata_persist_lib_dir: str
|
|
update_rules_on_start: bool
|
|
rule_update_interval_hours: int
|
|
web_bind: str
|
|
web_port: int
|
|
metrics_allowed_ips: str
|
|
metrics_basic_auth_username: str
|
|
metrics_basic_auth_password: str
|
|
db_path: str
|
|
eve_path: str
|
|
suricata_log_max_mb: int
|
|
forensic_pcap_mode: str
|
|
forensic_pcap_window_seconds: int
|
|
forensic_pcap_memory_mb: int
|
|
forensic_pcap_max_files: int
|
|
forensic_pcap_max_total_mb: int
|
|
alert_retention_days: int
|
|
alert_max_severity: int
|
|
alert_dedup_window_seconds: int
|
|
alert_ignore_sids: str
|
|
alert_ignore_categories: str
|
|
auto_block: bool
|
|
auto_block_max_severity: int
|
|
monitored_networks: str
|
|
never_block: str
|
|
block_timeout: str
|
|
routeros_url: str
|
|
routeros_user: str
|
|
routeros_password: str
|
|
routeros_verify_tls: bool
|
|
routeros_address_list: str
|
|
routeros_http_timeout: int
|
|
admin_token: str
|
|
admin_username: str
|
|
admin_password: str
|
|
session_hours: int
|
|
session_cookie_secure: bool
|
|
analytics_snapshot_interval_seconds: int
|
|
traffic_archive_interval_seconds: int
|
|
traffic_archive_lag_seconds: int
|
|
traffic_archive_batch_size: int
|
|
redis_url: str
|
|
redis_managed: bool
|
|
redis_data_dir: str
|
|
redis_port: int
|
|
redis_maxmemory_mb: int
|
|
redis_snapshot_seconds: int
|
|
redis_aof: bool
|
|
traffic_retention_hours: int
|
|
traffic_max_events: int
|
|
traffic_memory_events: int
|
|
websocket_queue_size: int
|
|
live_flow_update_seconds: float
|
|
ndr_enabled: bool
|
|
ndr_correlation_window_seconds: int
|
|
behavior_min_observations: int
|
|
ndr_auto_block: bool
|
|
ndr_auto_block_risk: int
|
|
routeros_inventory_interval_seconds: int
|
|
notify_webhook_url: str
|
|
notify_min_risk: int
|
|
notify_timeout_seconds: int
|
|
|
|
@classmethod
|
|
def from_env(cls) -> "Config":
|
|
return cls(
|
|
tzsp_bind=os.getenv("TZSP_BIND", "0.0.0.0"),
|
|
tzsp_port=_int("TZSP_PORT", 37008),
|
|
tap_name=os.getenv("TAP_NAME", "suritap0"),
|
|
tap_mtu=_int("TAP_MTU", 9000),
|
|
tzsp_receiver_bin=os.getenv("TZSP_RECEIVER_BIN", "/usr/local/bin/mikrosuricata-tzsp"),
|
|
tzsp_telemetry_socket=os.getenv("TZSP_TELEMETRY_SOCKET", "/run/mikrosuricata/tzsp-telemetry.sock"),
|
|
tzsp_rcvbuf_bytes=max(1024 * 1024, _int("TZSP_RCVBUF_BYTES", 32 * 1024 * 1024)),
|
|
tzsp_batch_size=max(1, min(1024, _int("TZSP_BATCH_SIZE", 256))),
|
|
tzsp_queue_mb=max(8, min(512, _int("TZSP_QUEUE_MB", 64))),
|
|
tzsp_datagram_bytes=max(2048, min(65535, _int("TZSP_DATAGRAM_BYTES", 12288))),
|
|
suricata_config=os.getenv("SURICATA_CONFIG", "/etc/suricata/suricata.yaml"),
|
|
suricata_output_config=os.getenv(
|
|
"SURICATA_OUTPUT_CONFIG", "/opt/ids/suricata/ids-output.yaml"
|
|
),
|
|
suricata_home_net=os.getenv(
|
|
"SURICATA_HOME_NET",
|
|
"[192.168.0.0/16,10.0.0.0/8,172.16.0.0/12]",
|
|
),
|
|
suricata_local_rules=os.getenv(
|
|
"SURICATA_LOCAL_RULES", "/data/suricata/local.rules"
|
|
),
|
|
suricata_extra_rules_glob=os.getenv(
|
|
"SURICATA_EXTRA_RULES_GLOB", "/data/suricata/*.rules"
|
|
),
|
|
suricata_custom_rules=os.getenv(
|
|
"SURICATA_CUSTOM_RULES", "/data/suricata/custom.rules"
|
|
),
|
|
suricata_threshold_config=os.getenv(
|
|
"SURICATA_THRESHOLD_CONFIG", "/data/suricata/threshold.config"
|
|
),
|
|
suricata_persist_lib_dir=os.getenv(
|
|
"SURICATA_PERSIST_LIB_DIR", "/data/lib/suricata"
|
|
),
|
|
update_rules_on_start=_bool("UPDATE_RULES_ON_START", False),
|
|
rule_update_interval_hours=max(0, _int("RULE_UPDATE_INTERVAL_HOURS", 24)),
|
|
web_bind=os.getenv("WEB_BIND", "0.0.0.0"),
|
|
web_port=_int("WEB_PORT", 8080),
|
|
metrics_allowed_ips=os.getenv(
|
|
"METRICS_ALLOWED_IPS", "127.0.0.1/32,::1/128"
|
|
).strip(),
|
|
metrics_basic_auth_username=os.getenv(
|
|
"METRICS_BASIC_AUTH_USERNAME", ""
|
|
).strip(),
|
|
metrics_basic_auth_password=os.getenv(
|
|
"METRICS_BASIC_AUTH_PASSWORD", ""
|
|
),
|
|
db_path=os.getenv("DB_PATH", "/data/ids.db"),
|
|
eve_path=os.getenv("EVE_PATH", "/data/logs/suricata/eve.json"),
|
|
suricata_log_max_mb=_int("SURICATA_LOG_MAX_MB", 512),
|
|
forensic_pcap_mode=_choice("FORENSIC_PCAP_MODE", "alerts", {"blocks", "alerts", "all", "off"}),
|
|
forensic_pcap_window_seconds=max(5, _int("FORENSIC_PCAP_WINDOW_SECONDS", 60)),
|
|
forensic_pcap_memory_mb=max(1, _int("FORENSIC_PCAP_MEMORY_MB", 64)),
|
|
forensic_pcap_max_files=max(1, _int("FORENSIC_PCAP_MAX_FILES", 32)),
|
|
forensic_pcap_max_total_mb=max(1, _int("FORENSIC_PCAP_MAX_TOTAL_MB", 512)),
|
|
alert_retention_days=_int("ALERT_RETENTION_DAYS", 14),
|
|
# Suricata severity uses 1 as the most important value. Keeping
|
|
# 1-2 by default removes low-priority informational noise from the
|
|
# incident database while raw EVE remains available on disk.
|
|
alert_max_severity=_int("ALERT_MAX_SEVERITY", 2),
|
|
alert_dedup_window_seconds=_int("ALERT_DEDUP_WINDOW_SECONDS", 300),
|
|
alert_ignore_sids=os.getenv("ALERT_IGNORE_SIDS", "1000001"),
|
|
alert_ignore_categories=os.getenv("ALERT_IGNORE_CATEGORIES", ""),
|
|
auto_block=_bool("AUTO_BLOCK", False),
|
|
auto_block_max_severity=_int("AUTO_BLOCK_MAX_SEVERITY", 1),
|
|
monitored_networks=os.getenv("MONITORED_NETWORKS", "192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"),
|
|
never_block=os.getenv("NEVER_BLOCK", ""),
|
|
block_timeout=os.getenv("BLOCK_TIMEOUT", "1h"),
|
|
routeros_url=os.getenv("ROUTEROS_URL", "https://172.31.255.1").rstrip("/"),
|
|
routeros_user=os.getenv("ROUTEROS_USER", "suricata-api"),
|
|
routeros_password=os.getenv("ROUTEROS_PASSWORD", "CHANGE_ME"),
|
|
routeros_verify_tls=_bool("ROUTEROS_VERIFY_TLS", False),
|
|
routeros_address_list=os.getenv("ROUTEROS_ADDRESS_LIST", "IDS-BLOCK"),
|
|
routeros_http_timeout=_int("ROUTEROS_HTTP_TIMEOUT", 5),
|
|
admin_token=os.getenv("ADMIN_TOKEN", ""),
|
|
admin_username=os.getenv("ADMIN_USERNAME", "admin").strip() or "admin",
|
|
admin_password=os.getenv("ADMIN_PASSWORD", ""),
|
|
session_hours=max(1, _int("SESSION_HOURS", 168)),
|
|
session_cookie_secure=_bool("SESSION_COOKIE_SECURE", False),
|
|
analytics_snapshot_interval_seconds=max(
|
|
15, _int("ANALYTICS_SNAPSHOT_INTERVAL_SECONDS", 120)
|
|
),
|
|
traffic_archive_interval_seconds=max(1, _int("TRAFFIC_ARCHIVE_INTERVAL_SECONDS", 10)),
|
|
traffic_archive_lag_seconds=max(2, _int("TRAFFIC_ARCHIVE_LAG_SECONDS", 10)),
|
|
traffic_archive_batch_size=max(100, min(5000, _int("TRAFFIC_ARCHIVE_BATCH_SIZE", 1000))),
|
|
redis_url=os.getenv("REDIS_URL", "redis://127.0.0.1:6379/0"),
|
|
redis_managed=_bool("REDIS_MANAGED", True),
|
|
redis_data_dir=os.getenv("REDIS_DATA_DIR", "/data/redis"),
|
|
redis_port=_int("REDIS_PORT", 6379),
|
|
# Redis is only a short-lived ingestion buffer. Keep a hard memory
|
|
# ceiling so a stalled archive worker cannot trigger host OOMK.
|
|
redis_maxmemory_mb=max(32, _int("REDIS_MAXMEMORY_MB", 128) or 128),
|
|
# Durable history lives in SQLite, so Redis persistence is optional
|
|
# and disabled by default to avoid RDB/AOF fork memory spikes.
|
|
redis_snapshot_seconds=max(0, _int("REDIS_SNAPSHOT_SECONDS", 0)),
|
|
redis_aof=_bool("REDIS_AOF", False),
|
|
traffic_retention_hours=_int("TRAFFIC_RETENTION_HOURS", 24),
|
|
# Redis is only a transient queue. Bound the number of normalized
|
|
# EVE records as well as bytes so a stalled SQLite archive cannot
|
|
# accumulate an arbitrarily large sorted set.
|
|
traffic_max_events=max(0, _int("TRAFFIC_MAX_EVENTS", 50000)),
|
|
traffic_memory_events=max(0, _int("TRAFFIC_MEMORY_EVENTS", 0)),
|
|
websocket_queue_size=_int("WEBSOCKET_QUEUE_SIZE", 512),
|
|
live_flow_update_seconds=_float("LIVE_FLOW_UPDATE_SECONDS", 2.0),
|
|
ndr_enabled=_bool("NDR_ENABLED", True),
|
|
ndr_correlation_window_seconds=max(300, _int("NDR_CORRELATION_WINDOW_SECONDS", 1800)),
|
|
behavior_min_observations=max(10, _int("BEHAVIOR_MIN_OBSERVATIONS", 50)),
|
|
ndr_auto_block=_bool("NDR_AUTO_BLOCK", False),
|
|
ndr_auto_block_risk=max(70, min(100, _int("NDR_AUTO_BLOCK_RISK", 92))),
|
|
routeros_inventory_interval_seconds=max(60, _int("ROUTEROS_INVENTORY_INTERVAL_SECONDS", 300)),
|
|
notify_webhook_url=os.getenv("NOTIFY_WEBHOOK_URL", "").strip(),
|
|
notify_min_risk=max(1, min(100, _int("NOTIFY_MIN_RISK", 80))),
|
|
notify_timeout_seconds=max(1, min(30, _int("NOTIFY_TIMEOUT_SECONDS", 5))),
|
|
)
|
|
|
|
def public_dict(self) -> dict:
|
|
return {
|
|
"tzsp_bind": self.tzsp_bind,
|
|
"tzsp_port": self.tzsp_port,
|
|
"tap_name": self.tap_name,
|
|
"tap_mtu": self.tap_mtu,
|
|
"tzsp_receiver_engine": "rust",
|
|
"tzsp_rcvbuf_bytes": self.tzsp_rcvbuf_bytes,
|
|
"tzsp_batch_size": self.tzsp_batch_size,
|
|
"tzsp_queue_mb": self.tzsp_queue_mb,
|
|
"tzsp_datagram_bytes": self.tzsp_datagram_bytes,
|
|
"suricata_home_net": self.suricata_home_net,
|
|
"suricata_extra_rules_glob": self.suricata_extra_rules_glob,
|
|
"web_port": self.web_port,
|
|
"rule_update_interval_hours": self.rule_update_interval_hours,
|
|
"alert_retention_days": self.alert_retention_days,
|
|
"suricata_log_max_mb": self.suricata_log_max_mb,
|
|
"forensic_pcap_mode": self.forensic_pcap_mode,
|
|
"forensic_pcap_window_seconds": self.forensic_pcap_window_seconds,
|
|
"forensic_pcap_max_files": self.forensic_pcap_max_files,
|
|
"forensic_pcap_max_total_mb": self.forensic_pcap_max_total_mb,
|
|
"alert_max_severity": self.alert_max_severity,
|
|
"alert_dedup_window_seconds": self.alert_dedup_window_seconds,
|
|
"alert_ignore_sids": self.alert_ignore_sids,
|
|
"alert_ignore_categories": self.alert_ignore_categories,
|
|
"auto_block": self.auto_block,
|
|
"auto_block_max_severity": self.auto_block_max_severity,
|
|
"monitored_networks": self.monitored_networks,
|
|
"never_block": self.never_block,
|
|
"block_timeout": self.block_timeout,
|
|
"routeros_url": self.routeros_url,
|
|
"routeros_user": self.routeros_user,
|
|
"routeros_verify_tls": self.routeros_verify_tls,
|
|
"routeros_address_list": self.routeros_address_list,
|
|
"auth_enabled": bool(self.admin_password or self.admin_token),
|
|
"admin_username": self.admin_username,
|
|
"session_hours": self.session_hours,
|
|
"analytics_snapshot_interval_seconds": self.analytics_snapshot_interval_seconds,
|
|
"traffic_archive_interval_seconds": self.traffic_archive_interval_seconds,
|
|
"traffic_archive_lag_seconds": self.traffic_archive_lag_seconds,
|
|
"traffic_archive_batch_size": self.traffic_archive_batch_size,
|
|
"traffic_retention_hours": self.traffic_retention_hours,
|
|
"redis_managed": self.redis_managed,
|
|
"redis_maxmemory_mb": self.redis_maxmemory_mb,
|
|
"redis_snapshot_seconds": self.redis_snapshot_seconds,
|
|
"redis_aof": self.redis_aof,
|
|
"traffic_max_events": self.traffic_max_events,
|
|
"traffic_memory_events": self.traffic_memory_events,
|
|
"live_flow_update_seconds": self.live_flow_update_seconds,
|
|
"ndr_enabled": self.ndr_enabled,
|
|
"ndr_correlation_window_seconds": self.ndr_correlation_window_seconds,
|
|
"behavior_min_observations": self.behavior_min_observations,
|
|
"ndr_auto_block": self.ndr_auto_block,
|
|
"ndr_auto_block_risk": self.ndr_auto_block_risk,
|
|
"routeros_inventory_interval_seconds": self.routeros_inventory_interval_seconds,
|
|
"notify_webhook_enabled": bool(self.notify_webhook_url),
|
|
"notify_min_risk": self.notify_min_risk,
|
|
}
|