Wire all 25 services into --randomize-services and add heterogeneous OS fingerprints
- Replace hardcoded ALL_SERVICE_NAMES=[5 services] in cli.py with
_all_service_names() pulling dynamically from the plugin registry;
randomize-services now draws from all 25 registered honeypots
- Add build_base field to DistroProfile: apt-compatible image for service
Dockerfiles (ubuntu22/ubuntu20/kali get their own; others fall back to
debian:bookworm-slim since Dockerfiles use apt-get)
- Add build_base to DeckyConfig; propagate from distro in _build_deckies
and _build_deckies_from_ini
- Inject BASE_IMAGE build arg in composer.py for every build-based service
so each decky's containers reflect its assigned distro
- Update all 21 service Dockerfiles: FROM debian:bookworm-slim →
ARG BASE_IMAGE=debian:bookworm-slim / FROM ${BASE_IMAGE}
- Add tests/test_cli_service_pool.py and tests/test_composer.py (306 total)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,7 +32,9 @@ app = typer.Typer(
|
||||
)
|
||||
console = Console()
|
||||
|
||||
ALL_SERVICE_NAMES = ["ssh", "smb", "rdp", "http", "ftp"]
|
||||
def _all_service_names() -> list[str]:
|
||||
"""Return all registered service names from the live plugin registry."""
|
||||
return sorted(all_services().keys())
|
||||
|
||||
|
||||
def _resolve_distros(
|
||||
@@ -71,11 +73,12 @@ def _build_deckies(
|
||||
if services_explicit:
|
||||
svc_list = services_explicit
|
||||
elif randomize_services:
|
||||
# Pick 1-3 random services, try to avoid exact duplicates
|
||||
# Pick 1-3 random services from the full registry, avoid exact duplicates
|
||||
svc_pool = _all_service_names()
|
||||
attempts = 0
|
||||
while True:
|
||||
count = random.randint(1, min(3, len(ALL_SERVICE_NAMES)))
|
||||
chosen = frozenset(random.sample(ALL_SERVICE_NAMES, count))
|
||||
count = random.randint(1, min(3, len(svc_pool)))
|
||||
chosen = frozenset(random.sample(svc_pool, count))
|
||||
attempts += 1
|
||||
if chosen not in used_combos or attempts > 20:
|
||||
break
|
||||
@@ -92,6 +95,7 @@ def _build_deckies(
|
||||
services=svc_list,
|
||||
distro=distro.slug,
|
||||
base_image=distro.image,
|
||||
build_base=distro.build_base,
|
||||
hostname=hostname,
|
||||
)
|
||||
)
|
||||
@@ -136,18 +140,19 @@ def _build_deckies_from_ini(
|
||||
)
|
||||
|
||||
if spec.services:
|
||||
known = set(ALL_SERVICE_NAMES)
|
||||
known = set(_all_service_names())
|
||||
unknown = [s for s in spec.services if s not in known]
|
||||
if unknown:
|
||||
console.print(
|
||||
f"[red]Unknown service(s) in [{spec.name}]: {unknown}. "
|
||||
f"Available: {ALL_SERVICE_NAMES}[/]"
|
||||
f"Available: {_all_service_names()}[/]"
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
svc_list = spec.services
|
||||
elif randomize:
|
||||
count = random.randint(1, min(3, len(ALL_SERVICE_NAMES)))
|
||||
svc_list = random.sample(ALL_SERVICE_NAMES, count)
|
||||
svc_pool = _all_service_names()
|
||||
count = random.randint(1, min(3, len(svc_pool)))
|
||||
svc_list = random.sample(svc_pool, count)
|
||||
else:
|
||||
console.print(
|
||||
f"[red]Decky '[{spec.name}]' has no services= in config. "
|
||||
@@ -161,6 +166,7 @@ def _build_deckies_from_ini(
|
||||
services=svc_list,
|
||||
distro=distro.slug,
|
||||
base_image=distro.image,
|
||||
build_base=distro.build_base,
|
||||
hostname=hostname,
|
||||
))
|
||||
return deckies
|
||||
@@ -225,10 +231,10 @@ def deploy(
|
||||
|
||||
services_list = [s.strip() for s in services.split(",")] if services else None
|
||||
if services_list:
|
||||
known = set(ALL_SERVICE_NAMES)
|
||||
known = set(_all_service_names())
|
||||
unknown = [s for s in services_list if s not in known]
|
||||
if unknown:
|
||||
console.print(f"[red]Unknown service(s): {unknown}. Available: {ALL_SERVICE_NAMES}[/]")
|
||||
console.print(f"[red]Unknown service(s): {unknown}. Available: {_all_service_names()}[/]")
|
||||
raise typer.Exit(1)
|
||||
|
||||
if not services_list and not randomize_services:
|
||||
|
||||
@@ -48,6 +48,11 @@ def generate_compose(config: DecnetConfig) -> dict:
|
||||
svc = get_service(svc_name)
|
||||
fragment = svc.compose_fragment(decky.name, log_target=config.log_target)
|
||||
|
||||
# Inject the per-decky base image into build services so containers
|
||||
# vary by distro and don't all fingerprint as debian:bookworm-slim.
|
||||
if "build" in fragment:
|
||||
fragment["build"].setdefault("args", {})["BASE_IMAGE"] = decky.build_base
|
||||
|
||||
fragment.setdefault("environment", {})
|
||||
fragment["environment"]["HOSTNAME"] = decky.hostname
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@ class DeckyConfig(BaseModel):
|
||||
ip: str
|
||||
services: list[str]
|
||||
distro: str # slug from distros.DISTROS, e.g. "debian", "ubuntu22"
|
||||
base_image: str # resolved Docker image tag
|
||||
base_image: str # Docker image for the base/IP-holder container
|
||||
build_base: str = "debian:bookworm-slim" # apt-compatible image for service Dockerfiles
|
||||
hostname: str
|
||||
|
||||
@field_validator("services")
|
||||
|
||||
@@ -12,9 +12,10 @@ from dataclasses import dataclass
|
||||
@dataclass(frozen=True)
|
||||
class DistroProfile:
|
||||
slug: str # CLI-facing identifier, e.g. "debian", "rocky9"
|
||||
image: str # Docker image tag
|
||||
image: str # Docker image tag (used for the base/IP-holder container)
|
||||
display_name: str # Human-readable label shown in tables
|
||||
hostname_style: str # "generic" | "rhel" | "minimal" | "rolling"
|
||||
build_base: str # apt-compatible image for service Dockerfiles (FROM ${BASE_IMAGE})
|
||||
|
||||
|
||||
DISTROS: dict[str, DistroProfile] = {
|
||||
@@ -23,54 +24,63 @@ DISTROS: dict[str, DistroProfile] = {
|
||||
image="debian:bookworm-slim",
|
||||
display_name="Debian 12 (Bookworm)",
|
||||
hostname_style="generic",
|
||||
build_base="debian:bookworm-slim",
|
||||
),
|
||||
"ubuntu22": DistroProfile(
|
||||
slug="ubuntu22",
|
||||
image="ubuntu:22.04",
|
||||
display_name="Ubuntu 22.04 LTS (Jammy)",
|
||||
hostname_style="generic",
|
||||
build_base="ubuntu:22.04",
|
||||
),
|
||||
"ubuntu20": DistroProfile(
|
||||
slug="ubuntu20",
|
||||
image="ubuntu:20.04",
|
||||
display_name="Ubuntu 20.04 LTS (Focal)",
|
||||
hostname_style="generic",
|
||||
build_base="ubuntu:20.04",
|
||||
),
|
||||
"rocky9": DistroProfile(
|
||||
slug="rocky9",
|
||||
image="rockylinux:9-minimal",
|
||||
display_name="Rocky Linux 9",
|
||||
hostname_style="rhel",
|
||||
build_base="debian:bookworm-slim", # Dockerfiles use apt-get; fall back to debian
|
||||
),
|
||||
"centos7": DistroProfile(
|
||||
slug="centos7",
|
||||
image="centos:7",
|
||||
display_name="CentOS 7",
|
||||
hostname_style="rhel",
|
||||
build_base="debian:bookworm-slim", # Dockerfiles use apt-get; fall back to debian
|
||||
),
|
||||
"alpine": DistroProfile(
|
||||
slug="alpine",
|
||||
image="alpine:3.19",
|
||||
display_name="Alpine Linux 3.19",
|
||||
hostname_style="minimal",
|
||||
build_base="debian:bookworm-slim", # Dockerfiles use apt-get; fall back to debian
|
||||
),
|
||||
"fedora": DistroProfile(
|
||||
slug="fedora",
|
||||
image="fedora:39",
|
||||
display_name="Fedora 39",
|
||||
hostname_style="rhel",
|
||||
build_base="debian:bookworm-slim", # Dockerfiles use apt-get; fall back to debian
|
||||
),
|
||||
"kali": DistroProfile(
|
||||
slug="kali",
|
||||
image="kalilinux/kali-rolling",
|
||||
display_name="Kali Linux (Rolling)",
|
||||
hostname_style="rolling",
|
||||
build_base="kalilinux/kali-rolling", # Debian-based, apt-get compatible
|
||||
),
|
||||
"arch": DistroProfile(
|
||||
slug="arch",
|
||||
image="archlinux:latest",
|
||||
display_name="Arch Linux",
|
||||
hostname_style="rolling",
|
||||
build_base="debian:bookworm-slim", # Dockerfiles use apt-get; fall back to debian
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user