Files
bigbrother/utils/config_loader.py
T
n0mad1k 62a1010ab4 Add Phase 1 utility modules: crypto, networking, logging, stealth, resource, permissions, config_loader, bettercap_api
9 files in utils/ providing the shared infrastructure layer:
- crypto: AES-256-GCM file encryption, argon2id/PBKDF2 key derivation, HKDF network unlock, LUKS container management
- networking: interface detection, MAC/IP helpers, BPF compilation via libpcap ctypes, gratuitous ARP, VLAN creation
- logging: encrypted log writer (BBLogger) with rotation, per-module files, stdout suppression for stealth
- stealth: process rename via prctl, cmdline spoofing, sysctl helpers, timestomping, core dump disable
- resource: hardware tier detection (OPi Zero 3 / Pi Zero / generic) via /proc/device-tree and cpuinfo, resource monitoring
- permissions: root/capability checks via capget ctypes, privilege dropping, directory permission enforcement
- config_loader: YAML merge hierarchy (hardware_tiers -> bigbrother -> modules -> stealth -> CLI), tier auto-detection, SIGHUP reload
- bettercap_api: REST client with per-session random credentials, session/events/command methods
2026-03-18 08:11:25 -04:00

180 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""YAML config loader with merge hierarchy, hardware tier detection, and SIGHUP reload."""
import copy
import os
import signal
import threading
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional
import yaml
from utils.resource import get_hardware_tier
CONFIG_DIR = "config"
MERGE_ORDER = [
"hardware_tiers.yaml",
"bigbrother.yaml",
"modules.yaml",
"stealth.yaml",
]
def deep_merge(base: dict, override: dict) -> dict:
result = copy.deepcopy(base)
for key, value in override.items():
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
result[key] = deep_merge(result[key], value)
else:
result[key] = copy.deepcopy(value)
return result
class ConfigLoader:
def __init__(
self,
config_dir: str = CONFIG_DIR,
cli_overrides: Optional[Dict[str, Any]] = None,
auto_detect_tier: bool = True,
):
self.config_dir = Path(config_dir)
self.cli_overrides = cli_overrides or {}
self.auto_detect_tier = auto_detect_tier
self._config: Dict[str, Any] = {}
self._lock = threading.Lock()
self._reload_callbacks: List[Callable] = []
self._loaded = False
def load(self) -> Dict[str, Any]:
with self._lock:
self._config = self._build_config()
self._loaded = True
return copy.deepcopy(self._config)
def _build_config(self) -> Dict[str, Any]:
merged: Dict[str, Any] = {}
for filename in MERGE_ORDER:
filepath = self.config_dir / filename
if filepath.exists():
try:
with open(filepath, "r") as f:
data = yaml.safe_load(f) or {}
merged = deep_merge(merged, data)
except (yaml.YAMLError, IOError):
continue
# Apply hardware tier defaults
if self.auto_detect_tier:
merged = self._apply_tier_limits(merged)
# Apply CLI overrides last (highest priority)
if self.cli_overrides:
merged = deep_merge(merged, self.cli_overrides)
# Validate
self._validate(merged)
return merged
def _apply_tier_limits(self, config: dict) -> dict:
tier = config.get("device", {}).get("platform", "auto")
if tier == "auto":
tier = get_hardware_tier()
config.setdefault("device", {})["platform"] = tier
# Extract tier-specific limits from hardware_tiers section
tiers = config.pop("hardware_tiers", {})
tier_config = tiers.get(tier, tiers.get("generic", {}))
if tier_config:
config = deep_merge(config, tier_config)
# Apply well-known tier constraints
if tier == "pi_zero":
config.setdefault("capture", {})["pcap_compression_level"] = 3
config.setdefault("security", {})["encryption_key_derive"] = "pbkdf2"
# Limit passive modules
limits = config.setdefault("limits", {})
limits.setdefault("max_passive_modules", 4)
limits.setdefault("max_active_modules", 0)
elif tier == "opi_zero3":
config.setdefault("capture", {})["pcap_compression_level"] = 19
config.setdefault("security", {})["encryption_key_derive"] = "argon2id"
limits = config.setdefault("limits", {})
limits.setdefault("max_passive_modules", 16)
limits.setdefault("max_active_modules", 6)
else:
config.setdefault("capture", {})["pcap_compression_level"] = 19
config.setdefault("security", {})["encryption_key_derive"] = "argon2id"
limits = config.setdefault("limits", {})
limits.setdefault("max_passive_modules", 16)
limits.setdefault("max_active_modules", -1) # unlimited
return config
def _validate(self, config: dict) -> None:
device = config.get("device", {})
platform = device.get("platform", "")
if platform and platform not in ("auto", "opi_zero3", "pi_zero", "generic"):
raise ValueError(f"Invalid platform: {platform}")
capture = config.get("capture", {})
comp_level = capture.get("pcap_compression_level")
if comp_level is not None and not (1 <= comp_level <= 22):
raise ValueError(f"Invalid compression level: {comp_level}")
bettercap = config.get("bettercap", {})
port = bettercap.get("api_port")
if port is not None and not (1 <= port <= 65535):
raise ValueError(f"Invalid bettercap API port: {port}")
security = config.get("security", {})
kdf = security.get("encryption_key_derive")
if kdf and kdf not in ("argon2id", "pbkdf2"):
raise ValueError(f"Invalid key derivation method: {kdf}")
def get(self, key_path: str, default: Any = None) -> Any:
with self._lock:
keys = key_path.split(".")
current = self._config
for k in keys:
if isinstance(current, dict) and k in current:
current = current[k]
else:
return default
return copy.deepcopy(current)
def reload(self) -> Dict[str, Any]:
config = self.load()
for cb in self._reload_callbacks:
try:
cb(config)
except Exception:
pass
return config
def on_reload(self, callback: Callable):
self._reload_callbacks.append(callback)
def register_sighup(self):
def _handler(signum, frame):
self.reload()
signal.signal(signal.SIGHUP, _handler)
@property
def config(self) -> Dict[str, Any]:
with self._lock:
if not self._loaded:
return self.load()
return copy.deepcopy(self._config)
def load_config(
config_dir: str = CONFIG_DIR,
cli_overrides: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
loader = ConfigLoader(config_dir=config_dir, cli_overrides=cli_overrides)
return loader.load()