Add per-service customization, stealth hardening, and BYOS support

- HTTP: configurable server_header, response_code, fake_app presets
  (apache/nginx/wordpress/phpmyadmin/iis), extra_headers, custom_body,
  static files directory mount
- SSH/Cowrie: configurable kernel_version, hardware_platform, ssh_banner,
  and users/passwords via COWRIE_USERDB_ENTRIES; switched to build mode
  so cowrie.cfg.j2 persona fields and userdb.txt generation work
- SMTP: configurable banner and MTA hostname
- MySQL: configurable version string in protocol greeting
- Redis: configurable redis_version and os string in INFO response
- BYOS: [custom-*] INI sections define bring-your-own Docker services
- Stealth: rename all *_honeypot.py → server.py; replace HONEYPOT_NAME
  env var with NODE_NAME across all 22+ service templates and plugins;
  strip "honeypot" from all in-container file content
- Config: DeckyConfig.service_config dict; INI [decky-N.svc] subsections;
  composer passes service_cfg to compose_fragment
- 350 tests passing (100%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 04:08:27 -03:00
parent 07c06e3c0a
commit cf1e00af28
102 changed files with 974 additions and 309 deletions

View File

@@ -168,6 +168,7 @@ def _build_deckies_from_ini(
base_image=distro.image,
build_base=distro.build_base,
hostname=hostname,
service_config=spec.service_config,
))
return deckies
@@ -217,6 +218,20 @@ def deploy(
f"[dim]Subnet:[/] {subnet_cidr} [dim]Gateway:[/] {effective_gateway} "
f"[dim]Host IP:[/] {host_ip}")
# Register bring-your-own services from INI before validation
if ini.custom_services:
from decnet.custom_service import CustomService
from decnet.services.registry import register_custom_service
for cs in ini.custom_services:
register_custom_service(
CustomService(
name=cs.name,
image=cs.image,
exec_cmd=cs.exec_cmd,
ports=cs.ports,
)
)
effective_log_target = log_target or ini.log_target
decky_configs = _build_deckies_from_ini(
ini, subnet_cidr, effective_gateway, host_ip, randomize_services

View File

@@ -46,7 +46,10 @@ def generate_compose(config: DecnetConfig) -> dict:
# --- 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)
svc_cfg = decky.service_config.get(svc_name, {})
fragment = svc.compose_fragment(
decky.name, log_target=config.log_target, service_cfg=svc_cfg
)
# Inject the per-decky base image into build services so containers
# vary by distro and don't all fingerprint as debian:bookworm-slim.

View File

@@ -26,6 +26,7 @@ class DeckyConfig(BaseModel):
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
service_config: dict[str, dict] = {} # optional per-service persona config
@field_validator("services")
@classmethod

41
decnet/custom_service.py Normal file
View File

@@ -0,0 +1,41 @@
"""
Bring-your-own-service (BYOS) support.
CustomService wraps a user-defined service from an INI [custom-*] section.
It is instantiated dynamically and registered via register_custom_service(),
not through the auto-discovery mechanism in the registry.
"""
from decnet.services.base import BaseService
class CustomService(BaseService):
"""A user-defined service that runs an arbitrary Docker image."""
def __init__(self, name: str, image: str, exec_cmd: str, ports: list[int] | None = None):
self.name = name
self.default_image = image
self.ports = ports or []
self._exec_cmd = exec_cmd
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
slug = self.name.replace("_", "-")
fragment: dict = {
"image": self.default_image,
"container_name": f"{decky_name}-{slug}",
"restart": "unless-stopped",
"environment": {"NODE_NAME": decky_name},
}
if self._exec_cmd:
fragment["command"] = self._exec_cmd.split()
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target
return fragment
def dockerfile_context(self):
return None

View File

@@ -12,11 +12,26 @@ Format:
ip=192.168.1.82 # optional
services=ssh,smb # optional; falls back to --randomize-services
[hostname-1.ssh] # optional per-service persona config
kernel_version=5.15.0-76-generic
users=root:toor,admin:admin123
[hostname-1.http]
server_header=nginx/1.18.0
fake_app=wordpress
[hostname-2]
services=ssh
[hostname-3]
ip=192.168.1.32
# Custom (bring-your-own) service definitions:
[custom-myservice]
binary=my-docker-image:latest
exec=/usr/bin/myservice -p 8080
ports=8080
"""
import configparser
@@ -29,6 +44,16 @@ class DeckySpec:
name: str
ip: str | None = None
services: list[str] | None = None
service_config: dict[str, dict] = field(default_factory=dict)
@dataclass
class CustomServiceSpec:
"""Spec for a user-defined (bring-your-own) service."""
name: str # service slug, e.g. "myservice" (section is "custom-myservice")
image: str # Docker image to use
exec_cmd: str # command to run inside the container
ports: list[int] = field(default_factory=list)
@dataclass
@@ -38,6 +63,7 @@ class IniConfig:
interface: str | None = None
log_target: str | None = None
deckies: list[DeckySpec] = field(default_factory=list)
custom_services: list[CustomServiceSpec] = field(default_factory=list)
def load_ini(path: str | Path) -> IniConfig:
@@ -56,13 +82,40 @@ def load_ini(path: str | Path) -> IniConfig:
cfg.interface = g.get("interface")
cfg.log_target = g.get("log_target") or g.get("log-target")
# First pass: collect decky sections and custom service definitions
for section in cp.sections():
if section == "general":
continue
if "." in section:
continue # subsections handled in second pass
if section.startswith("custom-"):
# Bring-your-own service definition
s = cp[section]
svc_name = section[len("custom-"):]
image = s.get("binary", "")
exec_cmd = s.get("exec", "")
ports_raw = s.get("ports", "")
ports = [int(p.strip()) for p in ports_raw.split(",") if p.strip().isdigit()]
cfg.custom_services.append(
CustomServiceSpec(name=svc_name, image=image, exec_cmd=exec_cmd, ports=ports)
)
continue
s = cp[section]
ip = s.get("ip")
svc_raw = s.get("services")
services = [sv.strip() for sv in svc_raw.split(",")] if svc_raw else None
cfg.deckies.append(DeckySpec(name=section, ip=ip, services=services))
# Second pass: collect per-service subsections [decky-name.service]
decky_names = {d.name for d in cfg.deckies}
decky_map = {d.name: d for d in cfg.deckies}
for section in cp.sections():
if "." not in section:
continue
decky_name, _, svc_name = section.partition(".")
if decky_name not in decky_names:
continue # orphaned subsection — ignore
svc_cfg = {k: v for k, v in cp[section].items()}
decky_map[decky_name].service_config[svc_name] = svc_cfg
return cfg

View File

@@ -15,7 +15,12 @@ class BaseService(ABC):
default_image: str # Docker image tag, or "build" if a Dockerfile is needed
@abstractmethod
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
"""
Return the docker-compose service dict for this service on a given decky.
@@ -26,6 +31,7 @@ class BaseService(ABC):
Args:
decky_name: unique identifier for the decky (e.g. "decky-01")
log_target: "ip:port" string if log forwarding is enabled, else None
service_cfg: optional per-service persona config from INI subsection
"""
def dockerfile_context(self) -> Path | None:

View File

@@ -12,7 +12,7 @@ class ConpotService(BaseService):
ports = [502, 161, 80]
default_image = "honeynet/conpot"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
return {
"image": "honeynet/conpot",
"container_name": f"{decky_name}-conpot",

View File

@@ -9,12 +9,12 @@ class DockerAPIService(BaseService):
ports = [2375, 2376]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-docker-api",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -10,13 +10,13 @@ class ElasticsearchService(BaseService):
ports = [9200]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-elasticsearch",
"restart": "unless-stopped",
"environment": {
"HONEYPOT_NAME": decky_name,
"NODE_NAME": decky_name,
},
}
if log_target:

View File

@@ -9,13 +9,13 @@ class FTPService(BaseService):
ports = [21]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-ftp",
"restart": "unless-stopped",
"environment": {
"HONEYPOT_NAME": decky_name,
"NODE_NAME": decky_name,
},
}
if log_target:

View File

@@ -1,3 +1,4 @@
import json
from pathlib import Path
from decnet.services.base import BaseService
@@ -9,17 +10,43 @@ class HTTPService(BaseService):
ports = [80, 443]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-http",
"restart": "unless-stopped",
"environment": {
"HONEYPOT_NAME": decky_name,
"NODE_NAME": decky_name,
},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target
# Optional persona overrides — only injected when explicitly set
if "server_header" in cfg:
fragment["environment"]["SERVER_HEADER"] = cfg["server_header"]
if "response_code" in cfg:
fragment["environment"]["RESPONSE_CODE"] = str(cfg["response_code"])
if "fake_app" in cfg:
fragment["environment"]["FAKE_APP"] = cfg["fake_app"]
if "extra_headers" in cfg:
val = cfg["extra_headers"]
fragment["environment"]["EXTRA_HEADERS"] = (
json.dumps(val) if isinstance(val, dict) else val
)
if "custom_body" in cfg:
fragment["environment"]["CUSTOM_BODY"] = cfg["custom_body"]
if "files" in cfg:
files_path = str(Path(cfg["files"]).resolve())
fragment["environment"]["FILES_DIR"] = "/opt/html_files"
fragment.setdefault("volumes", []).append(f"{files_path}:/opt/html_files:ro")
return fragment
def dockerfile_context(self) -> Path | None:

View File

@@ -9,12 +9,12 @@ class IMAPService(BaseService):
ports = [143, 993]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-imap",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,12 +9,12 @@ class KubernetesAPIService(BaseService):
ports = [6443, 8080]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-k8s",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,13 +9,13 @@ class LDAPService(BaseService):
ports = [389, 636]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-ldap",
"restart": "unless-stopped",
"cap_add": ["NET_BIND_SERVICE"],
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -16,12 +16,12 @@ class LLMNRService(BaseService):
ports = [5355, 5353]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-llmnr",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,12 +9,12 @@ class MongoDBService(BaseService):
ports = [27017]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-mongodb",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,12 +9,12 @@ class MQTTService(BaseService):
ports = [1883]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-mqtt",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,12 +9,12 @@ class MSSQLService(BaseService):
ports = [1433]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-mssql",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,15 +9,23 @@ class MySQLService(BaseService):
ports = [3306]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-mysql",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target
if "version" in cfg:
fragment["environment"]["MYSQL_VERSION"] = cfg["version"]
return fragment
def dockerfile_context(self) -> Path | None:

View File

@@ -9,12 +9,12 @@ class POP3Service(BaseService):
ports = [110, 995]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-pop3",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,12 +9,12 @@ class PostgresService(BaseService):
ports = [5432]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-postgres",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,13 +9,13 @@ class RDPService(BaseService):
ports = [3389]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-rdp",
"restart": "unless-stopped",
"environment": {
"HONEYPOT_NAME": decky_name,
"NODE_NAME": decky_name,
},
}
if log_target:

View File

@@ -9,15 +9,25 @@ class RedisService(BaseService):
ports = [6379]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-redis",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target
if "version" in cfg:
fragment["environment"]["REDIS_VERSION"] = cfg["version"]
if "os_string" in cfg:
fragment["environment"]["REDIS_OS"] = cfg["os_string"]
return fragment
def dockerfile_context(self) -> Path | None:

View File

@@ -31,6 +31,12 @@ def _load_plugins() -> None:
_loaded = True
def register_custom_service(instance: BaseService) -> None:
"""Register a dynamically created service (e.g. BYOS from INI)."""
_load_plugins()
_registry[instance.name] = instance
def get_service(name: str) -> BaseService:
_load_plugins()
if name not in _registry:

View File

@@ -9,12 +9,12 @@ class SIPService(BaseService):
ports = [5060]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-sip",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,14 +9,14 @@ class SMBService(BaseService):
ports = [445, 139]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-smb",
"restart": "unless-stopped",
"cap_add": ["NET_BIND_SERVICE"],
"environment": {
"HONEYPOT_NAME": decky_name,
"NODE_NAME": decky_name,
},
}
if log_target:

View File

@@ -10,18 +10,28 @@ class SMTPService(BaseService):
ports = [25, 587]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-smtp",
"restart": "unless-stopped",
"cap_add": ["NET_BIND_SERVICE"],
"environment": {
"HONEYPOT_NAME": decky_name,
"NODE_NAME": decky_name,
},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target
if "banner" in cfg:
fragment["environment"]["SMTP_BANNER"] = cfg["banner"]
if "mta" in cfg:
fragment["environment"]["SMTP_MTA"] = cfg["mta"]
return fragment
def dockerfile_context(self) -> Path:

View File

@@ -9,12 +9,12 @@ class SNMPService(BaseService):
ports = [161]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-snmp",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -1,15 +1,24 @@
from pathlib import Path
from decnet.services.base import BaseService
TEMPLATES_DIR = Path(__file__).parent.parent.parent / "templates" / "cowrie"
class SSHService(BaseService):
name = "ssh"
ports = [22, 2222]
default_image = "cowrie/cowrie"
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(
self,
decky_name: str,
log_target: str | None = None,
service_cfg: dict | None = None,
) -> dict:
cfg = service_cfg or {}
env: dict = {
# Override [honeypot] and [ssh] listen_endpoints to also bind port 22
"COWRIE_HONEYPOT_HOSTNAME": decky_name,
"NODE_NAME": decky_name,
"COWRIE_HOSTNAME": decky_name,
"COWRIE_HONEYPOT_LISTEN_ENDPOINTS": "tcp:22:interface=0.0.0.0 tcp:2222:interface=0.0.0.0",
"COWRIE_SSH_LISTEN_ENDPOINTS": "tcp:22:interface=0.0.0.0 tcp:2222:interface=0.0.0.0",
}
@@ -18,13 +27,26 @@ class SSHService(BaseService):
env["COWRIE_OUTPUT_TCP_ENABLED"] = "true"
env["COWRIE_OUTPUT_TCP_HOST"] = host
env["COWRIE_OUTPUT_TCP_PORT"] = port
# Optional persona overrides
if "kernel_version" in cfg:
env["COWRIE_HONEYPOT_KERNEL_VERSION"] = cfg["kernel_version"]
if "kernel_build_string" in cfg:
env["COWRIE_HONEYPOT_KERNEL_BUILD_STRING"] = cfg["kernel_build_string"]
if "hardware_platform" in cfg:
env["COWRIE_HONEYPOT_HARDWARE_PLATFORM"] = cfg["hardware_platform"]
if "ssh_banner" in cfg:
env["COWRIE_SSH_VERSION"] = cfg["ssh_banner"]
if "users" in cfg:
env["COWRIE_USERDB_ENTRIES"] = cfg["users"]
return {
"image": "cowrie/cowrie",
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-ssh",
"restart": "unless-stopped",
"cap_add": ["NET_BIND_SERVICE"],
"environment": env,
}
def dockerfile_context(self):
return None
def dockerfile_context(self) -> Path:
return TEMPLATES_DIR

View File

@@ -6,7 +6,7 @@ class TelnetService(BaseService):
ports = [23]
default_image = "cowrie/cowrie"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
env: dict = {
"COWRIE_HONEYPOT_HOSTNAME": decky_name,
"COWRIE_TELNET_ENABLED": "true",

View File

@@ -9,12 +9,12 @@ class TFTPService(BaseService):
ports = [69]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-tftp",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target

View File

@@ -9,12 +9,12 @@ class VNCService(BaseService):
ports = [5900]
default_image = "build"
def compose_fragment(self, decky_name: str, log_target: str | None = None) -> dict:
def compose_fragment(self, decky_name: str, log_target: str | None = None, service_cfg: dict | None = None) -> dict:
fragment: dict = {
"build": {"context": str(TEMPLATES_DIR)},
"container_name": f"{decky_name}-vnc",
"restart": "unless-stopped",
"environment": {"HONEYPOT_NAME": decky_name},
"environment": {"NODE_NAME": decky_name},
}
if log_target:
fragment["environment"]["LOG_TARGET"] = log_target