Initial commit: DECNET honeypot/deception network framework
Core CLI, service plugins (SSH/SMB/FTP/HTTP/RDP), Docker Compose orchestration, MACVLAN networking, and Logstash log forwarding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
87
decnet/composer.py
Normal file
87
decnet/composer.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Generates a docker-compose.yml from a DecnetConfig.
|
||||
|
||||
Network model:
|
||||
Each decky gets ONE "base" container that holds the MACVLAN IP.
|
||||
All service containers for that decky share the base's network namespace
|
||||
via `network_mode: "service:<base>"`. From the outside, every service on
|
||||
a given decky appears to come from the same IP — exactly like a real host.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from decnet.config import DecnetConfig
|
||||
from decnet.network import MACVLAN_NETWORK_NAME
|
||||
from decnet.services.registry import get_service
|
||||
|
||||
_LOG_NETWORK = "decnet_logs"
|
||||
|
||||
# Minimal image for the base container — just needs to stay alive.
|
||||
_BASE_IMAGE = "debian:bookworm-slim"
|
||||
|
||||
|
||||
def generate_compose(config: DecnetConfig) -> dict:
|
||||
"""Build and return the full docker-compose data structure."""
|
||||
services: dict = {}
|
||||
|
||||
for decky in config.deckies:
|
||||
base_key = decky.name # e.g. "decky-01"
|
||||
|
||||
# --- Base container: owns the MACVLAN IP, runs nothing but sleep ---
|
||||
base: dict = {
|
||||
"image": _BASE_IMAGE,
|
||||
"container_name": base_key,
|
||||
"hostname": decky.hostname,
|
||||
"command": ["sleep", "infinity"],
|
||||
"restart": "unless-stopped",
|
||||
"networks": {
|
||||
MACVLAN_NETWORK_NAME: {
|
||||
"ipv4_address": decky.ip,
|
||||
}
|
||||
},
|
||||
}
|
||||
if config.log_target:
|
||||
base["networks"][_LOG_NETWORK] = {}
|
||||
services[base_key] = base
|
||||
|
||||
# --- Service containers: share base network namespace ---
|
||||
for svc_name in decky.services:
|
||||
svc = get_service(svc_name)
|
||||
fragment = svc.compose_fragment(decky.name, log_target=config.log_target)
|
||||
|
||||
fragment.setdefault("environment", {})
|
||||
fragment["environment"]["HOSTNAME"] = decky.hostname
|
||||
|
||||
# Share the base container's network — no own IP needed
|
||||
fragment["network_mode"] = f"service:{base_key}"
|
||||
fragment["depends_on"] = [base_key]
|
||||
|
||||
# hostname must not be set when using network_mode
|
||||
fragment.pop("hostname", None)
|
||||
fragment.pop("networks", None)
|
||||
|
||||
services[f"{decky.name}-{svc_name}"] = fragment
|
||||
|
||||
# Network definitions
|
||||
networks: dict = {
|
||||
MACVLAN_NETWORK_NAME: {
|
||||
"external": True, # created by network.py before compose up
|
||||
}
|
||||
}
|
||||
if config.log_target:
|
||||
networks[_LOG_NETWORK] = {"driver": "bridge", "internal": True}
|
||||
|
||||
return {
|
||||
"version": "3.8",
|
||||
"services": services,
|
||||
"networks": networks,
|
||||
}
|
||||
|
||||
|
||||
def write_compose(config: DecnetConfig, output_path: Path) -> Path:
|
||||
"""Write the docker-compose.yml to output_path and return it."""
|
||||
data = generate_compose(config)
|
||||
output_path.write_text(yaml.dump(data, default_flow_style=False, sort_keys=False))
|
||||
return output_path
|
||||
Reference in New Issue
Block a user