fix(collector): daemonize background subprocesses with start_new_session
Collector and mutator watcher subprocesses were spawned without start_new_session=True, leaving them in the parent's process group. SIGHUP (sent when the controlling terminal closes) killed both processes silently — stdout/stderr were DEVNULL so the crash was invisible. Also update test_services and test_composer to reflect the ssh plugin no longer using Cowrie env vars (replaced with SSH_ROOT_PASSWORD / SSH_HOSTNAME matching the real_ssh plugin).
This commit is contained in:
@@ -390,7 +390,8 @@ def deploy(
|
|||||||
subprocess.Popen( # nosec B603
|
subprocess.Popen( # nosec B603
|
||||||
[sys.executable, "-m", "decnet.cli", "mutate", "--watch"],
|
[sys.executable, "-m", "decnet.cli", "mutate", "--watch"],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.STDOUT
|
stderr=subprocess.STDOUT,
|
||||||
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
except (FileNotFoundError, subprocess.SubprocessError):
|
except (FileNotFoundError, subprocess.SubprocessError):
|
||||||
console.print("[red]Failed to start mutator watcher.[/]")
|
console.print("[red]Failed to start mutator watcher.[/]")
|
||||||
@@ -405,6 +406,7 @@ def deploy(
|
|||||||
[sys.executable, "-m", "decnet.cli", "collect", "--log-file", str(effective_log_file)],
|
[sys.executable, "-m", "decnet.cli", "collect", "--log-file", str(effective_log_file)],
|
||||||
stdout=subprocess.DEVNULL,
|
stdout=subprocess.DEVNULL,
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
|
start_new_session=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
if api and not dry_run:
|
if api and not dry_run:
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ def test_service_config_http_server_header():
|
|||||||
assert env.get("SERVER_HEADER") == "nginx/1.18.0"
|
assert env.get("SERVER_HEADER") == "nginx/1.18.0"
|
||||||
|
|
||||||
|
|
||||||
def test_service_config_ssh_kernel_version():
|
def test_service_config_ssh_password():
|
||||||
"""service_config for ssh must inject COWRIE_HONEYPOT_KERNEL_VERSION."""
|
"""service_config for ssh must inject SSH_ROOT_PASSWORD."""
|
||||||
from decnet.config import DeckyConfig, DecnetConfig
|
from decnet.config import DeckyConfig, DecnetConfig
|
||||||
from decnet.distros import DISTROS
|
from decnet.distros import DISTROS
|
||||||
profile = DISTROS["debian"]
|
profile = DISTROS["debian"]
|
||||||
@@ -131,7 +131,7 @@ def test_service_config_ssh_kernel_version():
|
|||||||
services=["ssh"], distro="debian",
|
services=["ssh"], distro="debian",
|
||||||
base_image=profile.image, build_base=profile.build_base,
|
base_image=profile.image, build_base=profile.build_base,
|
||||||
hostname="test-host",
|
hostname="test-host",
|
||||||
service_config={"ssh": {"kernel_version": "5.15.0-76-generic"}},
|
service_config={"ssh": {"password": "s3cr3t!"}},
|
||||||
)
|
)
|
||||||
config = DecnetConfig(
|
config = DecnetConfig(
|
||||||
mode="unihost", interface="eth0",
|
mode="unihost", interface="eth0",
|
||||||
@@ -140,7 +140,8 @@ def test_service_config_ssh_kernel_version():
|
|||||||
)
|
)
|
||||||
compose = generate_compose(config)
|
compose = generate_compose(config)
|
||||||
env = compose["services"]["decky-01-ssh"]["environment"]
|
env = compose["services"]["decky-01-ssh"]["environment"]
|
||||||
assert env.get("COWRIE_HONEYPOT_KERNEL_VERSION") == "5.15.0-76-generic"
|
assert env.get("SSH_ROOT_PASSWORD") == "s3cr3t!"
|
||||||
|
assert not any(k.startswith("COWRIE_") for k in env)
|
||||||
|
|
||||||
|
|
||||||
def test_service_config_for_one_service_does_not_affect_another():
|
def test_service_config_for_one_service_does_not_affect_another():
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ UPSTREAM_SERVICES = {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
BUILD_SERVICES = {
|
BUILD_SERVICES = {
|
||||||
"ssh": ([22, 2222], "ssh"),
|
"ssh": ([22], "ssh"),
|
||||||
"http": ([80, 443], "http"),
|
"http": ([80, 443], "http"),
|
||||||
"rdp": ([3389], "rdp"),
|
"rdp": ([3389], "rdp"),
|
||||||
"smb": ([445, 139], "smb"),
|
"smb": ([445, 139], "smb"),
|
||||||
@@ -155,7 +155,10 @@ def test_build_service_restart_policy(name):
|
|||||||
assert frag.get("restart") == "unless-stopped"
|
assert frag.get("restart") == "unless-stopped"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("name", BUILD_SERVICES)
|
_NODE_NAME_SERVICES = [n for n in BUILD_SERVICES if n not in ("ssh", "real_ssh")]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", _NODE_NAME_SERVICES)
|
||||||
def test_build_service_node_name_env(name):
|
def test_build_service_node_name_env(name):
|
||||||
frag = _fragment(name)
|
frag = _fragment(name)
|
||||||
env = frag.get("environment", {})
|
env = frag.get("environment", {})
|
||||||
@@ -163,8 +166,8 @@ def test_build_service_node_name_env(name):
|
|||||||
assert env["NODE_NAME"] == "test-decky"
|
assert env["NODE_NAME"] == "test-decky"
|
||||||
|
|
||||||
|
|
||||||
# SSH uses COWRIE_OUTPUT_TCP_* instead of LOG_TARGET — exclude from generic tests
|
# ssh and real_ssh do not use LOG_TARGET (rsyslog handles log forwarding inside the container)
|
||||||
_LOG_TARGET_SERVICES = [n for n in BUILD_SERVICES if n != "ssh"]
|
_LOG_TARGET_SERVICES = [n for n in BUILD_SERVICES if n not in ("ssh", "real_ssh")]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("name", _LOG_TARGET_SERVICES)
|
@pytest.mark.parametrize("name", _LOG_TARGET_SERVICES)
|
||||||
@@ -181,13 +184,11 @@ def test_build_service_no_log_target_by_default(name):
|
|||||||
assert "LOG_TARGET" not in env
|
assert "LOG_TARGET" not in env
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_log_target_uses_cowrie_tcp_output():
|
def test_ssh_no_log_target_env():
|
||||||
"""SSH forwards logs via Cowrie TCP output, not LOG_TARGET."""
|
"""SSH uses rsyslog internally — no LOG_TARGET or COWRIE_* vars."""
|
||||||
env = _fragment("ssh", log_target="10.0.0.1:5140").get("environment", {})
|
env = _fragment("ssh", log_target="10.0.0.1:5140").get("environment", {})
|
||||||
assert env.get("COWRIE_OUTPUT_TCP_ENABLED") == "true"
|
|
||||||
assert env.get("COWRIE_OUTPUT_TCP_HOST") == "10.0.0.1"
|
|
||||||
assert env.get("COWRIE_OUTPUT_TCP_PORT") == "5140"
|
|
||||||
assert "LOG_TARGET" not in env
|
assert "LOG_TARGET" not in env
|
||||||
|
assert not any(k.startswith("COWRIE_") for k in env)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -266,31 +267,26 @@ def test_http_empty_service_cfg_no_extra_env():
|
|||||||
|
|
||||||
# SSH ------------------------------------------------------------------------
|
# SSH ------------------------------------------------------------------------
|
||||||
|
|
||||||
def test_ssh_default_no_persona_env():
|
def test_ssh_default_env():
|
||||||
env = _fragment("ssh").get("environment", {})
|
env = _fragment("ssh").get("environment", {})
|
||||||
for key in ("COWRIE_HONEYPOT_KERNEL_VERSION", "COWRIE_HONEYPOT_HARDWARE_PLATFORM",
|
assert env.get("SSH_ROOT_PASSWORD") == "admin"
|
||||||
"COWRIE_SSH_VERSION", "COWRIE_USERDB_ENTRIES"):
|
assert not any(k.startswith("COWRIE_") for k in env)
|
||||||
assert key not in env, f"Expected {key} absent by default"
|
assert "NODE_NAME" not in env
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_kernel_version():
|
def test_ssh_custom_password():
|
||||||
env = _fragment("ssh", service_cfg={"kernel_version": "5.15.0-76-generic"}).get("environment", {})
|
env = _fragment("ssh", service_cfg={"password": "h4x!"}).get("environment", {})
|
||||||
assert env.get("COWRIE_HONEYPOT_KERNEL_VERSION") == "5.15.0-76-generic"
|
assert env.get("SSH_ROOT_PASSWORD") == "h4x!"
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_hardware_platform():
|
def test_ssh_custom_hostname():
|
||||||
env = _fragment("ssh", service_cfg={"hardware_platform": "aarch64"}).get("environment", {})
|
env = _fragment("ssh", service_cfg={"hostname": "prod-db"}).get("environment", {})
|
||||||
assert env.get("COWRIE_HONEYPOT_HARDWARE_PLATFORM") == "aarch64"
|
assert env.get("SSH_HOSTNAME") == "prod-db"
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_banner():
|
def test_ssh_no_hostname_by_default():
|
||||||
env = _fragment("ssh", service_cfg={"ssh_banner": "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.3"}).get("environment", {})
|
env = _fragment("ssh").get("environment", {})
|
||||||
assert env.get("COWRIE_SSH_VERSION") == "SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.3"
|
assert "SSH_HOSTNAME" not in env
|
||||||
|
|
||||||
|
|
||||||
def test_ssh_users():
|
|
||||||
env = _fragment("ssh", service_cfg={"users": "root:toor,admin:admin123"}).get("environment", {})
|
|
||||||
assert env.get("COWRIE_USERDB_ENTRIES") == "root:toor,admin:admin123"
|
|
||||||
|
|
||||||
|
|
||||||
# SMTP -----------------------------------------------------------------------
|
# SMTP -----------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user