diff --git a/decnet/collector/worker.py b/decnet/collector/worker.py index 01dbd41..bb87c74 100644 --- a/decnet/collector/worker.py +++ b/decnet/collector/worker.py @@ -114,7 +114,7 @@ _RFC5424_RE = re.compile( r"(\S+) " # 4: MSGID (event_type) r"(.+)$", # 5: SD element + optional MSG ) -_SD_BLOCK_RE = re.compile(r'\[decnet@55555\s+(.*?)\]', re.DOTALL) +_SD_BLOCK_RE = re.compile(r'\[relay@55555\s+(.*?)\]', re.DOTALL) _PARAM_RE = re.compile(r'(\w+)="((?:[^"\\]|\\.)*)"') _IP_FIELDS = ("src_ip", "src", "client_ip", "remote_ip", "remote_addr", "target_ip", "ip") diff --git a/decnet/correlation/parser.py b/decnet/correlation/parser.py index 001019e..4aae381 100644 --- a/decnet/correlation/parser.py +++ b/decnet/correlation/parser.py @@ -6,7 +6,7 @@ the fields needed for cross-decky correlation: attacker IP, decky name, service, event type, and timestamp. Log format (produced by decnet.logging.syslog_formatter): - 1 TIMESTAMP HOSTNAME APP-NAME - MSGID [decnet@55555 k1="v1" k2="v2"] [MSG] + 1 TIMESTAMP HOSTNAME APP-NAME - MSGID [relay@55555 k1="v1" k2="v2"] [MSG] The attacker IP may appear under several field names depending on service: src_ip — ftp, smtp, http, most services @@ -31,8 +31,8 @@ _RFC5424_RE = re.compile( r"(.+)$", # 5: SD element + optional MSG ) -# Structured data block: [decnet@55555 k="v" ...] -_SD_BLOCK_RE = re.compile(r'\[decnet@55555\s+(.*?)\]', re.DOTALL) +# Structured data block: [relay@55555 k="v" ...] +_SD_BLOCK_RE = re.compile(r'\[relay@55555\s+(.*?)\]', re.DOTALL) # Individual param: key="value" (with escaped chars inside value) _PARAM_RE = re.compile(r'(\w+)="((?:[^"\\]|\\.)*)"') diff --git a/decnet/engine/deployer.py b/decnet/engine/deployer.py index d468b0a..5ac1417 100644 --- a/decnet/engine/deployer.py +++ b/decnet/engine/deployer.py @@ -31,11 +31,11 @@ from decnet.network import ( log = get_logger("engine") console = Console() COMPOSE_FILE = Path("decnet-compose.yml") -_CANONICAL_LOGGING = Path(__file__).parent.parent.parent / "templates" / "decnet_logging.py" +_CANONICAL_LOGGING = Path(__file__).parent.parent.parent / "templates" / "syslog_bridge.py" def _sync_logging_helper(config: DecnetConfig) -> None: - """Copy the canonical decnet_logging.py into every active template build context.""" + """Copy the canonical syslog_bridge.py into every active template build context.""" from decnet.services.registry import get_service seen: set[Path] = set() for decky in config.deckies: @@ -47,7 +47,7 @@ def _sync_logging_helper(config: DecnetConfig) -> None: if ctx is None or ctx in seen: continue seen.add(ctx) - dest = ctx / "decnet_logging.py" + dest = ctx / "syslog_bridge.py" if not dest.exists() or dest.read_bytes() != _CANONICAL_LOGGING.read_bytes(): shutil.copy2(_CANONICAL_LOGGING, dest) diff --git a/decnet/logging/syslog_formatter.py b/decnet/logging/syslog_formatter.py index 6d43244..5745bba 100644 --- a/decnet/logging/syslog_formatter.py +++ b/decnet/logging/syslog_formatter.py @@ -5,7 +5,7 @@ Produces fully-compliant syslog messages: 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG Facility: local0 (16) -PEN for structured data: decnet@55555 +PEN for structured data: relay@55555 """ from __future__ import annotations @@ -16,7 +16,7 @@ from typing import Any FACILITY_LOCAL0 = 16 NILVALUE = "-" -_SD_ID = "decnet@55555" +_SD_ID = "relay@55555" SEVERITY_INFO = 6 SEVERITY_WARNING = 4 diff --git a/decnet/prober/worker.py b/decnet/prober/worker.py index face17b..07e0aa0 100644 --- a/decnet/prober/worker.py +++ b/decnet/prober/worker.py @@ -51,7 +51,7 @@ DEFAULT_TCPFP_PORTS: list[int] = [22, 80, 443, 8080, 8443, 445, 3389] # ─── RFC 5424 formatting (inline, mirrors templates/*/decnet_logging.py) ───── _FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" +_SD_ID = "relay@55555" _SEVERITY_INFO = 6 _SEVERITY_WARNING = 4 @@ -98,7 +98,7 @@ _RFC5424_RE = re.compile( r"(\S+) " # 4: MSGID (event_type) r"(.+)$", # 5: SD + MSG ) -_SD_BLOCK_RE = re.compile(r'\[decnet@55555\s+(.*?)\]', re.DOTALL) +_SD_BLOCK_RE = re.compile(r'\[relay@55555\s+(.*?)\]', re.DOTALL) _PARAM_RE = re.compile(r'(\w+)="((?:[^"\\]|\\.)*)"') _IP_FIELDS = ("src_ip", "src", "client_ip", "remote_ip", "ip", "target_ip") diff --git a/decnet/services/ssh.py b/decnet/services/ssh.py index e358931..1148c82 100644 --- a/decnet/services/ssh.py +++ b/decnet/services/ssh.py @@ -38,7 +38,8 @@ class SSHService(BaseService): # File-catcher quarantine: bind-mount a per-decky host dir so attacker # drops (scp/sftp/wget) are mirrored out-of-band for forensic analysis. - # The container path is internal-only; attackers never see this mount. + # The in-container path masquerades as systemd-coredump so `mount`/`df` + # from inside the container looks benign. quarantine_host = f"/var/lib/decnet/artifacts/{decky_name}/ssh" return { "build": {"context": str(TEMPLATES_DIR)}, @@ -46,7 +47,7 @@ class SSHService(BaseService): "restart": "unless-stopped", "cap_add": ["NET_BIND_SERVICE"], "environment": env, - "volumes": [f"{quarantine_host}:/var/decnet/captured:rw"], + "volumes": [f"{quarantine_host}:/var/lib/systemd/coredump:rw"], } def dockerfile_context(self) -> Path: diff --git a/decnet/sniffer/syslog.py b/decnet/sniffer/syslog.py index 8889b78..a32fd6d 100644 --- a/decnet/sniffer/syslog.py +++ b/decnet/sniffer/syslog.py @@ -16,7 +16,7 @@ from decnet.telemetry import traced as _traced # ─── Constants (must match templates/sniffer/decnet_logging.py) ────────────── _FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" +_SD_ID = "relay@55555" _NILVALUE = "-" SEVERITY_INFO = 6 diff --git a/development/docs/services/COLLECTOR.md b/development/docs/services/COLLECTOR.md index e5c5f50..5baf909 100644 --- a/development/docs/services/COLLECTOR.md +++ b/development/docs/services/COLLECTOR.md @@ -31,7 +31,7 @@ The main asynchronous entry point. DECNET services emit logs using a standardized RFC 5424 format with structured data. The `parse_rfc5424` function is the primary tool for extracting this information. -- **Structured Data**: Extracts parameters from the `decnet@55555` SD-ELEMENT. +- **Structured Data**: Extracts parameters from the `relay@55555` SD-ELEMENT. - **Field Mapping**: Identifies the `attacker_ip` by scanning common source IP fields (`src_ip`, `client_ip`, etc.). - **Consistency**: Formats timestamps into a human-readable `%Y-%m-%d %H:%M:%S` format for the analytical stream. diff --git a/pyproject.toml b/pyproject.toml index 20f5f5f..036aef2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,9 +103,5 @@ include = ["decnet*"] [tool.bandit] exclude_dirs = [ - "templates/http/decnet_logging.py", - "templates/imap/decnet_logging.py", - "templates/pop3/decnet_logging.py", - "templates/real_ssh/decnet_logging.py", - "templates/smtp/decnet_logging.py", + "templates/syslog_bridge.py", ] diff --git a/templates/conpot/Dockerfile b/templates/conpot/Dockerfile index 6bfad6d..1d3bb3e 100644 --- a/templates/conpot/Dockerfile +++ b/templates/conpot/Dockerfile @@ -11,16 +11,16 @@ RUN find /opt /usr /etc /home -name "*.xml" -exec sed -i 's/port="5020"/port="50 RUN (apt-get update && apt-get install -y --no-install-recommends libcap2-bin 2>/dev/null) || (apk add --no-cache libcap 2>/dev/null) || true RUN find /home/conpot/.local/bin /usr /opt -type f -name 'python*' -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true -# Bridge conpot's own logger into DECNET's RFC 5424 syslog pipeline. +# Bridge conpot's own logger into syslog-relay's RFC 5424 syslog pipeline. # entrypoint.py is self-contained (inlines the formatter) because the # conpot base image runs Python 3.6, which cannot import the shared -# decnet_logging.py (that file uses 3.9+ / 3.10+ type syntax). +# syslog_bridge.py (that file uses 3.9+ / 3.10+ type syntax). COPY entrypoint.py /home/conpot/entrypoint.py RUN chown conpot:conpot /home/conpot/entrypoint.py \ && chmod +x /home/conpot/entrypoint.py # The upstream image already runs as non-root 'conpot'. -# We do NOT switch to a 'decnet' user — doing so breaks pkg_resources +# We do NOT switch to a 'logrelay' user — doing so breaks pkg_resources # because conpot's eggs live under /home/conpot/.local and are only on # the Python path for that user. USER conpot diff --git a/templates/conpot/entrypoint.py b/templates/conpot/entrypoint.py index 534eeb0..59b9b99 100644 --- a/templates/conpot/entrypoint.py +++ b/templates/conpot/entrypoint.py @@ -3,7 +3,7 @@ Entrypoint wrapper for the Conpot ICS/SCADA honeypot. Launches conpot as a child process and bridges its log output into the -DECNET structured syslog pipeline. Each line from conpot stdout/stderr +syslog-relay structured syslog pipeline. Each line from conpot stdout/stderr is classified and emitted as an RFC 5424 syslog line so the host-side collector can ingest it alongside every other service. @@ -21,7 +21,7 @@ from datetime import datetime, timezone # ── RFC 5424 inline formatter (Python 3.6-compatible) ───────────────────────── _FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" +_SD_ID = "relay@55555" _NILVALUE = "-" SEVERITY_INFO = 6 diff --git a/templates/cowrie/Dockerfile b/templates/cowrie/Dockerfile index 9e7ce84..c8f0fba 100644 --- a/templates/cowrie/Dockerfile +++ b/templates/cowrie/Dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ git authbind \ && rm -rf /var/lib/apt/lists/* -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -18,5 +18,5 @@ RUN chmod +x /entrypoint.sh HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/cowrie/decnet_logging.py b/templates/cowrie/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/cowrie/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/decnet_logging.py b/templates/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/docker_api/Dockerfile b/templates/docker_api/Dockerfile index f67a0c7..61e09d5 100644 --- a/templates/docker_api/Dockerfile +++ b/templates/docker_api/Dockerfile @@ -8,13 +8,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir flask -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 2375 2376 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -22,5 +22,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/docker_api/decnet_logging.py b/templates/docker_api/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/docker_api/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/docker_api/server.py b/templates/docker_api/server.py index 5210d0e..03d4961 100644 --- a/templates/docker_api/server.py +++ b/templates/docker_api/server.py @@ -10,7 +10,7 @@ import json import os from flask import Flask, request -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "docker-host") SERVICE_NAME = "docker_api" diff --git a/templates/elasticsearch/Dockerfile b/templates/elasticsearch/Dockerfile index a2d952f..5dca7b8 100644 --- a/templates/elasticsearch/Dockerfile +++ b/templates/elasticsearch/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 9200 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/elasticsearch/decnet_logging.py b/templates/elasticsearch/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/elasticsearch/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/elasticsearch/server.py b/templates/elasticsearch/server.py index 287c0bb..e65ee4c 100644 --- a/templates/elasticsearch/server.py +++ b/templates/elasticsearch/server.py @@ -8,7 +8,7 @@ as JSON. Designed to attract automated scanners and credential stuffers. import json import os from http.server import BaseHTTPRequestHandler, HTTPServer -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "esserver") SERVICE_NAME = "elasticsearch" diff --git a/templates/ftp/Dockerfile b/templates/ftp/Dockerfile index d2365e6..378b3c8 100644 --- a/templates/ftp/Dockerfile +++ b/templates/ftp/Dockerfile @@ -8,13 +8,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir twisted jinja2 -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 21 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -22,5 +22,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/ftp/decnet_logging.py b/templates/ftp/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/ftp/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/ftp/server.py b/templates/ftp/server.py index 95f756d..be6136f 100644 --- a/templates/ftp/server.py +++ b/templates/ftp/server.py @@ -12,7 +12,7 @@ from twisted.internet import defer, reactor from twisted.protocols.ftp import FTP, FTPFactory, FTPAnonymousShell from twisted.python.filepath import FilePath from twisted.python import log as twisted_log -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "ftpserver") SERVICE_NAME = "ftp" diff --git a/templates/http/Dockerfile b/templates/http/Dockerfile index 4014032..a8f2876 100644 --- a/templates/http/Dockerfile +++ b/templates/http/Dockerfile @@ -8,13 +8,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir flask jinja2 -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 80 443 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -22,5 +22,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/http/decnet_logging.py b/templates/http/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/http/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/http/server.py b/templates/http/server.py index cb8d17d..b169804 100644 --- a/templates/http/server.py +++ b/templates/http/server.py @@ -12,7 +12,7 @@ from pathlib import Path from flask import Flask, request, send_from_directory from werkzeug.serving import make_server, WSGIRequestHandler -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog logging.getLogger("werkzeug").setLevel(logging.ERROR) diff --git a/templates/https/Dockerfile b/templates/https/Dockerfile index 02d3d74..7dbd915 100644 --- a/templates/https/Dockerfile +++ b/templates/https/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir flask jinja2 -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh @@ -16,8 +16,8 @@ RUN chmod +x /entrypoint.sh RUN mkdir -p /opt/tls EXPOSE 443 -RUN useradd -r -s /bin/false -d /opt decnet \ - && chown -R decnet:decnet /opt/tls \ +RUN useradd -r -s /bin/false -d /opt logrelay \ + && chown -R logrelay:logrelay /opt/tls \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -25,5 +25,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/https/decnet_logging.py b/templates/https/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/https/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/https/server.py b/templates/https/server.py index 450f17a..40fd785 100644 --- a/templates/https/server.py +++ b/templates/https/server.py @@ -14,7 +14,7 @@ from pathlib import Path from flask import Flask, request, send_from_directory from werkzeug.serving import make_server, WSGIRequestHandler -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog logging.getLogger("werkzeug").setLevel(logging.ERROR) diff --git a/templates/imap/Dockerfile b/templates/imap/Dockerfile index a0e8fa2..35d1b67 100644 --- a/templates/imap/Dockerfile +++ b/templates/imap/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 143 993 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/imap/decnet_logging.py b/templates/imap/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/imap/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/imap/server.py b/templates/imap/server.py index 6d5498a..5b01588 100644 --- a/templates/imap/server.py +++ b/templates/imap/server.py @@ -12,7 +12,7 @@ Banner advertises Dovecot so nmap fingerprints correctly. import asyncio import os -from decnet_logging import SEVERITY_WARNING, syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import SEVERITY_WARNING, syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mailserver") SERVICE_NAME = "imap" diff --git a/templates/k8s/Dockerfile b/templates/k8s/Dockerfile index 118ed00..1da6296 100644 --- a/templates/k8s/Dockerfile +++ b/templates/k8s/Dockerfile @@ -8,13 +8,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir flask -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 6443 8080 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -22,5 +22,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/k8s/decnet_logging.py b/templates/k8s/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/k8s/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/k8s/server.py b/templates/k8s/server.py index 283307a..8e5ba51 100644 --- a/templates/k8s/server.py +++ b/templates/k8s/server.py @@ -10,7 +10,7 @@ import json import os from flask import Flask, request -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "k8s-master") SERVICE_NAME = "k8s" diff --git a/templates/ldap/Dockerfile b/templates/ldap/Dockerfile index 2d8aa48..64e1a50 100644 --- a/templates/ldap/Dockerfile +++ b/templates/ldap/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 389 636 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/ldap/decnet_logging.py b/templates/ldap/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/ldap/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/ldap/server.py b/templates/ldap/server.py index 7c3135c..c7d4136 100644 --- a/templates/ldap/server.py +++ b/templates/ldap/server.py @@ -7,7 +7,7 @@ invalidCredentials error. Logs all interactions as JSON. import asyncio import os -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "ldapserver") SERVICE_NAME = "ldap" diff --git a/templates/llmnr/Dockerfile b/templates/llmnr/Dockerfile index cddfc7d..724f4db 100644 --- a/templates/llmnr/Dockerfile +++ b/templates/llmnr/Dockerfile @@ -5,14 +5,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 5355/udp EXPOSE 5353/udp -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -20,5 +20,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/llmnr/decnet_logging.py b/templates/llmnr/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/llmnr/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/llmnr/server.py b/templates/llmnr/server.py index e9efcee..ac94707 100644 --- a/templates/llmnr/server.py +++ b/templates/llmnr/server.py @@ -9,7 +9,7 @@ Logs every packet with source IP and decoded query name where possible. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "lan-host") SERVICE_NAME = "llmnr" diff --git a/templates/mongodb/Dockerfile b/templates/mongodb/Dockerfile index d8f7039..d7bc953 100644 --- a/templates/mongodb/Dockerfile +++ b/templates/mongodb/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 27017 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/mongodb/decnet_logging.py b/templates/mongodb/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/mongodb/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/mongodb/server.py b/templates/mongodb/server.py index 1979b48..ce14f02 100644 --- a/templates/mongodb/server.py +++ b/templates/mongodb/server.py @@ -9,7 +9,7 @@ received messages as JSON. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mongodb") SERVICE_NAME = "mongodb" diff --git a/templates/mqtt/Dockerfile b/templates/mqtt/Dockerfile index 1ee311d..562ed42 100644 --- a/templates/mqtt/Dockerfile +++ b/templates/mqtt/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 1883 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/mqtt/decnet_logging.py b/templates/mqtt/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/mqtt/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/mqtt/server.py b/templates/mqtt/server.py index a25860d..66438bd 100644 --- a/templates/mqtt/server.py +++ b/templates/mqtt/server.py @@ -12,7 +12,7 @@ import json import os import random import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mqtt-broker") SERVICE_NAME = "mqtt" diff --git a/templates/mssql/Dockerfile b/templates/mssql/Dockerfile index 07607cb..2f34156 100644 --- a/templates/mssql/Dockerfile +++ b/templates/mssql/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 1433 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/mssql/decnet_logging.py b/templates/mssql/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/mssql/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/mssql/server.py b/templates/mssql/server.py index 114c01b..61114d5 100644 --- a/templates/mssql/server.py +++ b/templates/mssql/server.py @@ -8,7 +8,7 @@ a login failed error. Logs auth attempts as JSON. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "dbserver") SERVICE_NAME = "mssql" diff --git a/templates/mysql/Dockerfile b/templates/mysql/Dockerfile index cbfb532..926e74b 100644 --- a/templates/mysql/Dockerfile +++ b/templates/mysql/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 3306 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/mysql/decnet_logging.py b/templates/mysql/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/mysql/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/mysql/server.py b/templates/mysql/server.py index 02a7f7f..a6b1d94 100644 --- a/templates/mysql/server.py +++ b/templates/mysql/server.py @@ -9,7 +9,7 @@ attempts as JSON. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "dbserver") SERVICE_NAME = "mysql" diff --git a/templates/pop3/Dockerfile b/templates/pop3/Dockerfile index ccbfe65..08ac966 100644 --- a/templates/pop3/Dockerfile +++ b/templates/pop3/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 110 995 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/pop3/decnet_logging.py b/templates/pop3/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/pop3/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/pop3/server.py b/templates/pop3/server.py index 6978fdd..8599bc8 100644 --- a/templates/pop3/server.py +++ b/templates/pop3/server.py @@ -11,7 +11,7 @@ Credentials via IMAP_USERS env var (shared with IMAP service). import asyncio import os -from decnet_logging import SEVERITY_WARNING, syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import SEVERITY_WARNING, syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mailserver") SERVICE_NAME = "pop3" diff --git a/templates/postgres/Dockerfile b/templates/postgres/Dockerfile index 0a6a6bf..6eab4e1 100644 --- a/templates/postgres/Dockerfile +++ b/templates/postgres/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 5432 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/postgres/decnet_logging.py b/templates/postgres/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/postgres/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/postgres/server.py b/templates/postgres/server.py index 22cc821..267154f 100644 --- a/templates/postgres/server.py +++ b/templates/postgres/server.py @@ -9,7 +9,7 @@ returns an error. Logs all interactions as JSON. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "pgserver") SERVICE_NAME = "postgres" diff --git a/templates/rdp/Dockerfile b/templates/rdp/Dockerfile index cf68714..06ed165 100644 --- a/templates/rdp/Dockerfile +++ b/templates/rdp/Dockerfile @@ -8,13 +8,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir twisted jinja2 -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 3389 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -22,5 +22,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/rdp/decnet_logging.py b/templates/rdp/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/rdp/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/rdp/server.py b/templates/rdp/server.py index 274045f..2f61d7b 100644 --- a/templates/rdp/server.py +++ b/templates/rdp/server.py @@ -10,7 +10,7 @@ import os from twisted.internet import protocol, reactor from twisted.python import log as twisted_log -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "WORKSTATION") SERVICE_NAME = "rdp" diff --git a/templates/redis/Dockerfile b/templates/redis/Dockerfile index bc627ac..b3f85de 100644 --- a/templates/redis/Dockerfile +++ b/templates/redis/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 6379 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/redis/decnet_logging.py b/templates/redis/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/redis/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/redis/server.py b/templates/redis/server.py index fae4dee..4d3242f 100644 --- a/templates/redis/server.py +++ b/templates/redis/server.py @@ -7,7 +7,7 @@ KEYS, and arbitrary commands. Logs every command and argument as JSON. import asyncio import os -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "cache-server") SERVICE_NAME = "redis" diff --git a/templates/sip/Dockerfile b/templates/sip/Dockerfile index ab37230..e42a5e2 100644 --- a/templates/sip/Dockerfile +++ b/templates/sip/Dockerfile @@ -5,14 +5,14 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 5060/udp EXPOSE 5060/tcp -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -20,5 +20,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/sip/decnet_logging.py b/templates/sip/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/sip/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/sip/server.py b/templates/sip/server.py index cbacaca..dd40166 100644 --- a/templates/sip/server.py +++ b/templates/sip/server.py @@ -8,7 +8,7 @@ Authorization header and call metadata, then responds with 401 Unauthorized. import asyncio import os import re -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "pbx") SERVICE_NAME = "sip" diff --git a/templates/smb/Dockerfile b/templates/smb/Dockerfile index cea8028..64120be 100644 --- a/templates/smb/Dockerfile +++ b/templates/smb/Dockerfile @@ -8,13 +8,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ENV PIP_BREAK_SYSTEM_PACKAGES=1 RUN pip3 install --no-cache-dir impacket jinja2 -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 445 139 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -22,5 +22,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/smb/decnet_logging.py b/templates/smb/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/smb/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/smb/server.py b/templates/smb/server.py index 6df2588..24356a8 100644 --- a/templates/smb/server.py +++ b/templates/smb/server.py @@ -7,7 +7,7 @@ Logs all connection attempts, optionally forwarding them as JSON to LOG_TARGET. import os from impacket import smbserver -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "WORKSTATION") SERVICE_NAME = "smb" diff --git a/templates/smtp/Dockerfile b/templates/smtp/Dockerfile index 2013f50..c7bf5c8 100644 --- a/templates/smtp/Dockerfile +++ b/templates/smtp/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 25 587 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/smtp/decnet_logging.py b/templates/smtp/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/smtp/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/smtp/server.py b/templates/smtp/server.py index 8bf21a3..9cd52a2 100644 --- a/templates/smtp/server.py +++ b/templates/smtp/server.py @@ -23,7 +23,7 @@ import base64 import os import random import string -from decnet_logging import SEVERITY_WARNING, syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import SEVERITY_WARNING, syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mailserver") SERVICE_NAME = "smtp" diff --git a/templates/sniffer/Dockerfile b/templates/sniffer/Dockerfile index c6a9702..ff9a6fc 100644 --- a/templates/sniffer/Dockerfile +++ b/templates/sniffer/Dockerfile @@ -7,6 +7,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN pip3 install --no-cache-dir --break-system-packages "scapy==2.6.1" -COPY decnet_logging.py server.py /opt/ +COPY syslog_bridge.py server.py /opt/ ENTRYPOINT ["python3", "/opt/server.py"] diff --git a/templates/sniffer/decnet_logging.py b/templates/sniffer/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/sniffer/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/sniffer/server.py b/templates/sniffer/server.py index a6aa2fd..9bd7714 100644 --- a/templates/sniffer/server.py +++ b/templates/sniffer/server.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -DECNET passive TLS sniffer. +syslog-relay passive TLS sniffer. Captures TLS handshakes on the MACVLAN interface (shared network namespace with the decky base container). Extracts fingerprints and connection @@ -32,7 +32,7 @@ from typing import Any from scapy.layers.inet import IP, TCP from scapy.sendrecv import sniff -from decnet_logging import SEVERITY_INFO, SEVERITY_WARNING, syslog_line, write_syslog_file +from syslog_bridge import SEVERITY_INFO, SEVERITY_WARNING, syslog_line, write_syslog_file # ─── Configuration ──────────────────────────────────────────────────────────── diff --git a/templates/snmp/Dockerfile b/templates/snmp/Dockerfile index 5a452e9..9b79675 100644 --- a/templates/snmp/Dockerfile +++ b/templates/snmp/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 161/udp -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/snmp/decnet_logging.py b/templates/snmp/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/snmp/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/snmp/server.py b/templates/snmp/server.py index fdb8a06..9410939 100644 --- a/templates/snmp/server.py +++ b/templates/snmp/server.py @@ -9,7 +9,7 @@ Logs all requests as JSON. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "switch") SERVICE_NAME = "snmp" diff --git a/templates/ssh/Dockerfile b/templates/ssh/Dockerfile index 0775100..aea60dc 100644 --- a/templates/ssh/Dockerfile +++ b/templates/ssh/Dockerfile @@ -19,8 +19,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ jq \ && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /var/run/sshd /root/.ssh /var/log/decnet /var/decnet/captured \ - && chmod 700 /var/decnet /var/decnet/captured +RUN mkdir -p /var/run/sshd /root/.ssh /var/log/journal /var/lib/systemd/coredump \ + && chmod 700 /var/lib/systemd/coredump # sshd_config: allow root + password auth; VERBOSE so session lines carry # client IP + session PID (needed for file-capture attribution). @@ -34,11 +34,11 @@ RUN sed -i \ # rsyslog: forward auth.* and user.* to named pipe in RFC 5424 format. # The entrypoint relays the pipe to stdout for Docker log capture. RUN printf '%s\n' \ - '# DECNET log bridge — auth + user events → named pipe as RFC 5424' \ + '# syslog-relay log bridge — auth + user events → named pipe as RFC 5424' \ '$template RFC5424fmt,"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n"' \ 'auth,authpriv.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ 'user.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ - > /etc/rsyslog.d/99-decnet.conf + > /etc/rsyslog.d/50-journal-forward.conf # Silence default catch-all rules so we own auth/user routing exclusively RUN sed -i \ diff --git a/templates/ssh/capture.sh b/templates/ssh/capture.sh index 0861376..c65e7b4 100755 --- a/templates/ssh/capture.sh +++ b/templates/ssh/capture.sh @@ -1,5 +1,5 @@ #!/bin/bash -# DECNET SSH honeypot file-catcher. +# SSH honeypot file-catcher. # # Watches attacker-writable paths with inotifywait. On close_write/moved_to, # copies the file to the host-mounted quarantine dir, writes a .meta.json @@ -13,7 +13,7 @@ set -u -CAPTURE_DIR="${CAPTURE_DIR:-/var/decnet/captured}" +CAPTURE_DIR="${CAPTURE_DIR:-/var/lib/systemd/coredump}" CAPTURE_MAX_BYTES="${CAPTURE_MAX_BYTES:-52428800}" # 50 MiB CAPTURE_WATCH_PATHS="${CAPTURE_WATCH_PATHS:-/root /tmp /var/tmp /home /var/www /opt /dev/shm}" # Invoke inotifywait through a plausible-looking symlink so ps output doesn't @@ -29,7 +29,7 @@ _is_ignored_path() { local p="$1" case "$p" in "$CAPTURE_DIR"/*) return 0 ;; - /var/decnet/*) return 0 ;; + /var/lib/systemd/*) return 0 ;; */.bash_history) return 0 ;; */.viminfo) return 0 ;; */ssh_host_*_key*) return 0 ;; @@ -116,7 +116,7 @@ _capture_one() { size="$(stat -c '%s' "$src" 2>/dev/null)" [ -z "$size" ] && return 0 if [ "$size" -gt "$CAPTURE_MAX_BYTES" ]; then - logger -p user.info -t decnet-capture "file_skipped size=$size path=$src reason=oversize" + logger -p user.info -t systemd-journal "file_skipped size=$size path=$src reason=oversize" return 0 fi @@ -242,7 +242,7 @@ _capture_one() { ss_snapshot: $ss_snapshot }' > "$CAPTURE_DIR/$stored_as.meta.json" - logger -p user.info -t decnet-capture \ + logger -p user.info -t systemd-journal \ "file_captured orig_path=$src sha256=$sha size=$size stored_as=$stored_as src_ip=${src_ip:-unknown} ssh_user=${ssh_user:-unknown} attribution=$attribution" } diff --git a/templates/ssh/decnet_logging.py b/templates/ssh/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/ssh/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/ssh/entrypoint.sh b/templates/ssh/entrypoint.sh index f495600..b6b9d96 100644 --- a/templates/ssh/entrypoint.sh +++ b/templates/ssh/entrypoint.sh @@ -39,13 +39,13 @@ mkfifo /run/systemd/journal/syslog-relay bash -c 'exec -a "systemd-journal-fwd" cat /run/systemd/journal/syslog-relay' & -# Start rsyslog (reads /etc/rsyslog.d/99-decnet.conf, writes to the pipe above) +# Start rsyslog (reads /etc/rsyslog.d/50-journal-forward.conf, writes to the pipe above) rsyslogd # File-catcher: mirror attacker drops into host-mounted quarantine with attribution. # Script lives at /usr/libexec/udev/journal-relay so `ps aux` shows a # plausible udev helper. See Dockerfile for the rename rationale. -CAPTURE_DIR=/var/decnet/captured /usr/libexec/udev/journal-relay & +CAPTURE_DIR=/var/lib/systemd/coredump /usr/libexec/udev/journal-relay & # sshd logs via syslog — no -e flag, so auth events flow through rsyslog → pipe → stdout exec /usr/sbin/sshd -D diff --git a/templates/conpot/decnet_logging.py b/templates/syslog_bridge.py similarity index 84% rename from templates/conpot/decnet_logging.py rename to templates/syslog_bridge.py index 5a09505..c0a78d0 100644 --- a/templates/conpot/decnet_logging.py +++ b/templates/syslog_bridge.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 """ -Shared RFC 5424 syslog helper for DECNET service templates. +Shared RFC 5424 syslog helper used by service containers. Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. +write_syslog_file() to emit it to stdout — the container runtime +captures it, and the host-side collector streams it into the log file. RFC 5424 structure: 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG -Facility: local0 (16), PEN for SD element ID: decnet@55555 +Facility: local0 (16). SD element ID uses PEN 55555. """ from datetime import datetime, timezone @@ -18,7 +18,7 @@ from typing import Any # ─── Constants ──────────────────────────────────────────────────────────────── _FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" +_SD_ID = "relay@55555" _NILVALUE = "-" SEVERITY_EMERG = 0 @@ -62,7 +62,7 @@ def syslog_line( Args: service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) + hostname: HOSTNAME (node name) event_type: MSGID (e.g. "request", "login_attempt") severity: Syslog severity integer (default: INFO=6) timestamp: UTC datetime; defaults to now @@ -80,10 +80,10 @@ def syslog_line( def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" + """Emit a syslog line to stdout for container log capture.""" print(line, flush=True) def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" + """No-op stub. TCP forwarding is handled by rsyslog, not by service containers.""" pass diff --git a/templates/telnet/Dockerfile b/templates/telnet/Dockerfile index ad66570..483446b 100644 --- a/templates/telnet/Dockerfile +++ b/templates/telnet/Dockerfile @@ -10,11 +10,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # rsyslog: forward auth.* and user.* to named pipe in RFC 5424 format RUN printf '%s\n' \ - '# DECNET log bridge — auth + user events → named pipe as RFC 5424' \ + '# syslog-relay log bridge — auth + user events → named pipe as RFC 5424' \ '$template RFC5424fmt,"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n"' \ - 'auth,authpriv.* |/var/run/decnet-logs;RFC5424fmt' \ - 'user.* |/var/run/decnet-logs;RFC5424fmt' \ - > /etc/rsyslog.d/99-decnet.conf + 'auth,authpriv.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ + 'user.* |/run/systemd/journal/syslog-relay;RFC5424fmt' \ + > /etc/rsyslog.d/50-journal-forward.conf # Disable imklog — containers can't read /proc/kmsg RUN sed -i 's/^\(module(load="imklog"\)/# \1/' /etc/rsyslog.conf diff --git a/templates/telnet/decnet_logging.py b/templates/telnet/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/telnet/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/telnet/entrypoint.sh b/templates/telnet/entrypoint.sh index 81da1e4..78dff79 100644 --- a/templates/telnet/entrypoint.sh +++ b/templates/telnet/entrypoint.sh @@ -27,12 +27,14 @@ cat /root/.env HIST fi -# Logging pipeline: named pipe → rsyslogd (RFC 5424) → stdout -rm -f /var/run/decnet-logs -mkfifo /var/run/decnet-logs +# Logging pipeline: named pipe → rsyslogd (RFC 5424) → stdout. +# Cloak the pipe path and the relay `cat` so `ps aux` / `ls /run` don't +# betray the honeypot — see ssh/entrypoint.sh for the same pattern. +mkdir -p /run/systemd/journal +rm -f /run/systemd/journal/syslog-relay +mkfifo /run/systemd/journal/syslog-relay -# Relay pipe to stdout so Docker captures all syslog events -cat /var/run/decnet-logs & +bash -c 'exec -a "systemd-journal-fwd" cat /run/systemd/journal/syslog-relay' & # Start rsyslog rsyslogd diff --git a/templates/tftp/Dockerfile b/templates/tftp/Dockerfile index dc7296c..fec26b1 100644 --- a/templates/tftp/Dockerfile +++ b/templates/tftp/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 69/udp -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/tftp/decnet_logging.py b/templates/tftp/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/tftp/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/tftp/server.py b/templates/tftp/server.py index 775bde8..1faf0bd 100644 --- a/templates/tftp/server.py +++ b/templates/tftp/server.py @@ -8,7 +8,7 @@ then responds with an error packet. Logs all requests as JSON. import asyncio import os import struct -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "tftpserver") SERVICE_NAME = "tftp" diff --git a/templates/vnc/Dockerfile b/templates/vnc/Dockerfile index 62a5581..5957dee 100644 --- a/templates/vnc/Dockerfile +++ b/templates/vnc/Dockerfile @@ -5,13 +5,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ python3 \ && rm -rf /var/lib/apt/lists/* -COPY decnet_logging.py /opt/decnet_logging.py +COPY syslog_bridge.py /opt/syslog_bridge.py COPY server.py /opt/server.py COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh EXPOSE 5900 -RUN useradd -r -s /bin/false -d /opt decnet \ +RUN useradd -r -s /bin/false -d /opt logrelay \ && apt-get update && apt-get install -y --no-install-recommends libcap2-bin \ && rm -rf /var/lib/apt/lists/* \ && (find /usr/bin/ -maxdepth 1 -name 'python3*' -type f -exec setcap 'cap_net_bind_service+eip' {} \; 2>/dev/null || true) @@ -19,5 +19,5 @@ RUN useradd -r -s /bin/false -d /opt decnet \ HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ CMD kill -0 1 || exit 1 -USER decnet +USER logrelay ENTRYPOINT ["/entrypoint.sh"] diff --git a/templates/vnc/decnet_logging.py b/templates/vnc/decnet_logging.py deleted file mode 100644 index 5a09505..0000000 --- a/templates/vnc/decnet_logging.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Shared RFC 5424 syslog helper for DECNET service templates. - -Services call syslog_line() to format an RFC 5424 message, then -write_syslog_file() to emit it to stdout — Docker captures it, and the -host-side collector streams it into the log file. - -RFC 5424 structure: - 1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [SD-ELEMENT] MSG - -Facility: local0 (16), PEN for SD element ID: decnet@55555 -""" - -from datetime import datetime, timezone -from typing import Any - -# ─── Constants ──────────────────────────────────────────────────────────────── - -_FACILITY_LOCAL0 = 16 -_SD_ID = "decnet@55555" -_NILVALUE = "-" - -SEVERITY_EMERG = 0 -SEVERITY_ALERT = 1 -SEVERITY_CRIT = 2 -SEVERITY_ERROR = 3 -SEVERITY_WARNING = 4 -SEVERITY_NOTICE = 5 -SEVERITY_INFO = 6 -SEVERITY_DEBUG = 7 - -_MAX_HOSTNAME = 255 -_MAX_APPNAME = 48 -_MAX_MSGID = 32 - -# ─── Formatter ──────────────────────────────────────────────────────────────── - -def _sd_escape(value: str) -> str: - """Escape SD-PARAM-VALUE per RFC 5424 §6.3.3.""" - return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]") - - -def _sd_element(fields: dict[str, Any]) -> str: - if not fields: - return _NILVALUE - params = " ".join(f'{k}="{_sd_escape(str(v))}"' for k, v in fields.items()) - return f"[{_SD_ID} {params}]" - - -def syslog_line( - service: str, - hostname: str, - event_type: str, - severity: int = SEVERITY_INFO, - timestamp: datetime | None = None, - msg: str | None = None, - **fields: Any, -) -> str: - """ - Return a single RFC 5424-compliant syslog line (no trailing newline). - - Args: - service: APP-NAME (e.g. "http", "mysql") - hostname: HOSTNAME (decky node name) - event_type: MSGID (e.g. "request", "login_attempt") - severity: Syslog severity integer (default: INFO=6) - timestamp: UTC datetime; defaults to now - msg: Optional free-text MSG - **fields: Encoded as structured data params - """ - pri = f"<{_FACILITY_LOCAL0 * 8 + severity}>" - ts = (timestamp or datetime.now(timezone.utc)).isoformat() - host = (hostname or _NILVALUE)[:_MAX_HOSTNAME] - appname = (service or _NILVALUE)[:_MAX_APPNAME] - msgid = (event_type or _NILVALUE)[:_MAX_MSGID] - sd = _sd_element(fields) - message = f" {msg}" if msg else "" - return f"{pri}1 {ts} {host} {appname} {_NILVALUE} {msgid} {sd}{message}" - - -def write_syslog_file(line: str) -> None: - """Emit a syslog line to stdout for Docker log capture.""" - print(line, flush=True) - - -def forward_syslog(line: str, log_target: str) -> None: - """No-op stub. TCP forwarding is now handled by rsyslog, not by service containers.""" - pass diff --git a/templates/vnc/server.py b/templates/vnc/server.py index 6f549b9..3f82f6d 100644 --- a/templates/vnc/server.py +++ b/templates/vnc/server.py @@ -8,7 +8,7 @@ failed". Logs the raw response for offline cracking. import asyncio import os -from decnet_logging import syslog_line, write_syslog_file, forward_syslog +from syslog_bridge import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "desktop") SERVICE_NAME = "vnc" diff --git a/tests/live/test_service_isolation_live.py b/tests/live/test_service_isolation_live.py index d14824d..2239be4 100644 --- a/tests/live/test_service_isolation_live.py +++ b/tests/live/test_service_isolation_live.py @@ -172,7 +172,7 @@ class TestCollectorLiveIsolation: def test_rfc5424_parser_handles_real_formats(self): """Parser works on real log lines, not just test fixtures.""" - valid = '<134>1 2026-04-14T12:00:00Z decky-01 ssh - login_attempt [decnet@55555 src_ip="10.0.0.1" username="root" password="toor"] Failed login' + valid = '<134>1 2026-04-14T12:00:00Z decky-01 ssh - login_attempt [relay@55555 src_ip="10.0.0.1" username="root" password="toor"] Failed login' result = parse_rfc5424(valid) assert result is not None assert result["decky"] == "decky-01" @@ -236,7 +236,7 @@ class TestIngesterLiveIsolation: "attacker_ip": "10.99.99.1", "fields": {"username": "root", "password": "toor"}, "msg": "Failed login", - "raw_line": '<134>1 2026-04-14T12:00:00Z decky-live-01 ssh - login_attempt [decnet@55555 src_ip="10.99.99.1"] Failed login', + "raw_line": '<134>1 2026-04-14T12:00:00Z decky-live-01 ssh - login_attempt [relay@55555 src_ip="10.99.99.1"] Failed login', } json_file.write_text(json.dumps(record) + "\n") @@ -333,7 +333,7 @@ class TestAttackerWorkerLiveIsolation: "attacker_ip": "10.77.77.1", "fields": {"username": "admin"}, "msg": "", - "raw_line": f'<134>1 2026-04-14T14:0{i}:00Z decky-live-03 {"ssh" if i < 2 else "http"} - login_attempt [decnet@55555 src_ip="10.77.77.1" username="admin"]', + "raw_line": f'<134>1 2026-04-14T14:0{i}:00Z decky-live-03 {"ssh" if i < 2 else "http"} - login_attempt [relay@55555 src_ip="10.77.77.1" username="admin"]', }) state = _WorkerState() diff --git a/tests/service_testing/conftest.py b/tests/service_testing/conftest.py index 3708cf1..a634af3 100644 --- a/tests/service_testing/conftest.py +++ b/tests/service_testing/conftest.py @@ -18,8 +18,8 @@ _FUZZ_SETTINGS = dict( ) -def make_fake_decnet_logging() -> ModuleType: - mod = ModuleType("decnet_logging") +def make_fake_syslog_bridge() -> ModuleType: + mod = ModuleType("syslog_bridge") mod.syslog_line = MagicMock(return_value="") mod.write_syslog_file = MagicMock() mod.forward_syslog = MagicMock() diff --git a/tests/service_testing/test_imap.py b/tests/service_testing/test_imap.py index f376dcd..def6ed4 100644 --- a/tests/service_testing/test_imap.py +++ b/tests/service_testing/test_imap.py @@ -17,8 +17,8 @@ import pytest # ── Helpers ─────────────────────────────────────────────────────────────────── -def _make_fake_decnet_logging() -> ModuleType: - mod = ModuleType("decnet_logging") +def _make_fake_syslog_bridge() -> ModuleType: + mod = ModuleType("syslog_bridge") mod.syslog_line = MagicMock(return_value="") mod.write_syslog_file = MagicMock() mod.forward_syslog = MagicMock() @@ -28,17 +28,17 @@ def _make_fake_decnet_logging() -> ModuleType: def _load_imap(): - """Import imap server module, injecting a stub decnet_logging.""" + """Import imap server module, injecting a stub syslog_bridge.""" env = { "NODE_NAME": "testhost", "IMAP_USERS": "admin:admin123,root:toor", "IMAP_BANNER": "* OK [testhost] Dovecot ready.", } for key in list(sys.modules): - if key in ("imap_server", "decnet_logging"): + if key in ("imap_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = _make_fake_decnet_logging() + sys.modules["syslog_bridge"] = _make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location( "imap_server", "templates/imap/server.py" diff --git a/tests/service_testing/test_mongodb.py b/tests/service_testing/test_mongodb.py index 730a6cc..9ad17e6 100644 --- a/tests/service_testing/test_mongodb.py +++ b/tests/service_testing/test_mongodb.py @@ -14,16 +14,16 @@ import pytest from hypothesis import given, settings from hypothesis import strategies as st -from .conftest import _FUZZ_SETTINGS, make_fake_decnet_logging, run_with_timeout +from .conftest import _FUZZ_SETTINGS, make_fake_syslog_bridge, run_with_timeout # ── Helpers ─────────────────────────────────────────────────────────────────── def _load_mongodb(): for key in list(sys.modules): - if key in ("mongodb_server", "decnet_logging"): + if key in ("mongodb_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = make_fake_decnet_logging() + sys.modules["syslog_bridge"] = make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("mongodb_server", "templates/mongodb/server.py") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) diff --git a/tests/service_testing/test_mqtt.py b/tests/service_testing/test_mqtt.py index 751aea6..71bb6d5 100644 --- a/tests/service_testing/test_mqtt.py +++ b/tests/service_testing/test_mqtt.py @@ -16,8 +16,8 @@ import pytest # ── Helpers ─────────────────────────────────────────────────────────────────── -def _make_fake_decnet_logging() -> ModuleType: - mod = ModuleType("decnet_logging") +def _make_fake_syslog_bridge() -> ModuleType: + mod = ModuleType("syslog_bridge") mod.syslog_line = MagicMock(return_value="") mod.write_syslog_file = MagicMock() mod.forward_syslog = MagicMock() @@ -34,10 +34,10 @@ def _load_mqtt(accept_all: bool = True, custom_topics: str = "", persona: str = "MQTT_CUSTOM_TOPICS": custom_topics, } for key in list(sys.modules): - if key in ("mqtt_server", "decnet_logging"): + if key in ("mqtt_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = _make_fake_decnet_logging() + sys.modules["syslog_bridge"] = _make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("mqtt_server", "templates/mqtt/server.py") mod = importlib.util.module_from_spec(spec) diff --git a/tests/service_testing/test_mqtt_fuzz.py b/tests/service_testing/test_mqtt_fuzz.py index de90b1d..ad1a24a 100644 --- a/tests/service_testing/test_mqtt_fuzz.py +++ b/tests/service_testing/test_mqtt_fuzz.py @@ -15,16 +15,16 @@ import pytest from hypothesis import given, settings from hypothesis import strategies as st -from .conftest import _FUZZ_SETTINGS, make_fake_decnet_logging, run_with_timeout +from .conftest import _FUZZ_SETTINGS, make_fake_syslog_bridge, run_with_timeout # ── Helpers ─────────────────────────────────────────────────────────────────── def _load_mqtt(): for key in list(sys.modules): - if key in ("mqtt_server", "decnet_logging"): + if key in ("mqtt_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = make_fake_decnet_logging() + sys.modules["syslog_bridge"] = make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("mqtt_server", "templates/mqtt/server.py") mod = importlib.util.module_from_spec(spec) with patch.dict("os.environ", {"MQTT_ACCEPT_ALL": "1", "MQTT_PERSONA": "water_plant"}, clear=False): diff --git a/tests/service_testing/test_mssql.py b/tests/service_testing/test_mssql.py index 1aff5dc..2655e5c 100644 --- a/tests/service_testing/test_mssql.py +++ b/tests/service_testing/test_mssql.py @@ -14,16 +14,16 @@ import pytest from hypothesis import given, settings from hypothesis import strategies as st -from .conftest import _FUZZ_SETTINGS, make_fake_decnet_logging, run_with_timeout +from .conftest import _FUZZ_SETTINGS, make_fake_syslog_bridge, run_with_timeout # ── Helpers ─────────────────────────────────────────────────────────────────── def _load_mssql(): for key in list(sys.modules): - if key in ("mssql_server", "decnet_logging"): + if key in ("mssql_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = make_fake_decnet_logging() + sys.modules["syslog_bridge"] = make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("mssql_server", "templates/mssql/server.py") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) diff --git a/tests/service_testing/test_mysql.py b/tests/service_testing/test_mysql.py index e575570..8d641a4 100644 --- a/tests/service_testing/test_mysql.py +++ b/tests/service_testing/test_mysql.py @@ -14,16 +14,16 @@ import pytest from hypothesis import given, settings from hypothesis import strategies as st -from .conftest import _FUZZ_SETTINGS, make_fake_decnet_logging, run_with_timeout +from .conftest import _FUZZ_SETTINGS, make_fake_syslog_bridge, run_with_timeout # ── Helpers ─────────────────────────────────────────────────────────────────── def _load_mysql(): for key in list(sys.modules): - if key in ("mysql_server", "decnet_logging"): + if key in ("mysql_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = make_fake_decnet_logging() + sys.modules["syslog_bridge"] = make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("mysql_server", "templates/mysql/server.py") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) @@ -89,7 +89,7 @@ def test_login_packet_returns_access_denied(mysql_mod): def test_login_logs_username(): mod = _load_mysql() - log_mock = sys.modules["decnet_logging"] + log_mock = sys.modules["syslog_bridge"] proto, _, _ = _make_protocol(mod) proto.data_received(_login_packet(username="hacker")) calls_str = str(log_mock.syslog_line.call_args_list) diff --git a/tests/service_testing/test_pop3.py b/tests/service_testing/test_pop3.py index 9543bbc..1f038ba 100644 --- a/tests/service_testing/test_pop3.py +++ b/tests/service_testing/test_pop3.py @@ -17,8 +17,8 @@ import pytest # ── Helpers ─────────────────────────────────────────────────────────────────── -def _make_fake_decnet_logging() -> ModuleType: - mod = ModuleType("decnet_logging") +def _make_fake_syslog_bridge() -> ModuleType: + mod = ModuleType("syslog_bridge") mod.syslog_line = MagicMock(return_value="") mod.write_syslog_file = MagicMock() mod.forward_syslog = MagicMock() @@ -34,10 +34,10 @@ def _load_pop3(): "IMAP_BANNER": "+OK [testhost] Dovecot ready.", } for key in list(sys.modules): - if key in ("pop3_server", "decnet_logging"): + if key in ("pop3_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = _make_fake_decnet_logging() + sys.modules["syslog_bridge"] = _make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location( "pop3_server", "templates/pop3/server.py" diff --git a/tests/service_testing/test_postgres.py b/tests/service_testing/test_postgres.py index e69a4fa..76ef9d7 100644 --- a/tests/service_testing/test_postgres.py +++ b/tests/service_testing/test_postgres.py @@ -14,16 +14,16 @@ import pytest from hypothesis import given, settings from hypothesis import strategies as st -from .conftest import _FUZZ_SETTINGS, make_fake_decnet_logging, run_with_timeout +from .conftest import _FUZZ_SETTINGS, make_fake_syslog_bridge, run_with_timeout # ── Helpers ─────────────────────────────────────────────────────────────────── def _load_postgres(): for key in list(sys.modules): - if key in ("postgres_server", "decnet_logging"): + if key in ("postgres_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = make_fake_decnet_logging() + sys.modules["syslog_bridge"] = make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("postgres_server", "templates/postgres/server.py") mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) @@ -80,7 +80,7 @@ def test_startup_sends_auth_challenge(postgres_mod): def test_startup_logs_username(): mod = _load_postgres() - log_mock = sys.modules["decnet_logging"] + log_mock = sys.modules["syslog_bridge"] proto, _, _ = _make_protocol(mod) proto.data_received(_startup_msg(user="attacker")) log_mock.syslog_line.assert_called() diff --git a/tests/service_testing/test_redis.py b/tests/service_testing/test_redis.py index 4ad6f33..08872ab 100644 --- a/tests/service_testing/test_redis.py +++ b/tests/service_testing/test_redis.py @@ -6,8 +6,8 @@ from unittest.mock import MagicMock, patch import pytest -def _make_fake_decnet_logging() -> ModuleType: - mod = ModuleType("decnet_logging") +def _make_fake_syslog_bridge() -> ModuleType: + mod = ModuleType("syslog_bridge") mod.syslog_line = MagicMock(return_value="") mod.write_syslog_file = MagicMock() mod.forward_syslog = MagicMock() @@ -19,10 +19,10 @@ def _make_fake_decnet_logging() -> ModuleType: def _load_redis(): env = {"NODE_NAME": "testredis"} for key in list(sys.modules): - if key in ("redis_server", "decnet_logging"): + if key in ("redis_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = _make_fake_decnet_logging() + sys.modules["syslog_bridge"] = _make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("redis_server", "templates/redis/server.py") mod = importlib.util.module_from_spec(spec) diff --git a/tests/service_testing/test_smtp.py b/tests/service_testing/test_smtp.py index 64051b9..9cb11c6 100644 --- a/tests/service_testing/test_smtp.py +++ b/tests/service_testing/test_smtp.py @@ -19,9 +19,9 @@ import pytest # ── Helpers ─────────────────────────────────────────────────────────────────── -def _make_fake_decnet_logging() -> ModuleType: - """Return a stub decnet_logging module that does nothing.""" - mod = ModuleType("decnet_logging") +def _make_fake_syslog_bridge() -> ModuleType: + """Return a stub syslog_bridge module that does nothing.""" + mod = ModuleType("syslog_bridge") mod.syslog_line = MagicMock(return_value="") mod.write_syslog_file = MagicMock() mod.forward_syslog = MagicMock() @@ -33,15 +33,15 @@ def _make_fake_decnet_logging() -> ModuleType: def _load_smtp(open_relay: bool): """Import smtp server module with desired OPEN_RELAY value. - Injects a stub decnet_logging into sys.modules so the template can import + Injects a stub syslog_bridge into sys.modules so the template can import it without needing the real file on sys.path. """ env = {"SMTP_OPEN_RELAY": "1" if open_relay else "0", "NODE_NAME": "testhost"} for key in list(sys.modules): - if key in ("smtp_server", "decnet_logging"): + if key in ("smtp_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = _make_fake_decnet_logging() + sys.modules["syslog_bridge"] = _make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("smtp_server", "templates/smtp/server.py") mod = importlib.util.module_from_spec(spec) diff --git a/tests/service_testing/test_snmp.py b/tests/service_testing/test_snmp.py index 0694739..623588c 100644 --- a/tests/service_testing/test_snmp.py +++ b/tests/service_testing/test_snmp.py @@ -15,8 +15,8 @@ import pytest # ── Helpers ─────────────────────────────────────────────────────────────────── -def _make_fake_decnet_logging() -> ModuleType: - mod = ModuleType("decnet_logging") +def _make_fake_syslog_bridge() -> ModuleType: + mod = ModuleType("syslog_bridge") def syslog_line(*args, **kwargs): print("LOG:", args, kwargs) return "" @@ -34,10 +34,10 @@ def _load_snmp(archetype: str = "default"): "SNMP_ARCHETYPE": archetype, } for key in list(sys.modules): - if key in ("snmp_server", "decnet_logging"): + if key in ("snmp_server", "syslog_bridge"): del sys.modules[key] - sys.modules["decnet_logging"] = _make_fake_decnet_logging() + sys.modules["syslog_bridge"] = _make_fake_syslog_bridge() spec = importlib.util.spec_from_file_location("snmp_server", "templates/snmp/server.py") mod = importlib.util.module_from_spec(spec) diff --git a/tests/test_cli.py b/tests/test_cli.py index 23df3aa..a656176 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -316,7 +316,7 @@ class TestCorrelateCommand: log_file = tmp_path / "test.log" log_file.write_text( "<134>1 2024-01-15T12:00:00+00:00 decky-01 ssh - auth " - '[decnet@55555 src_ip="10.0.0.5" username="admin"] login\n' + '[relay@55555 src_ip="10.0.0.5" username="admin"] login\n' ) result = runner.invoke(app, ["correlate", "--log-file", str(log_file)]) assert result.exit_code == 0 diff --git a/tests/test_collector.py b/tests/test_collector.py index 5835a4a..3cbec8f 100644 --- a/tests/test_collector.py +++ b/tests/test_collector.py @@ -23,7 +23,7 @@ def _make_container(name="omega-decky-http"): class TestParseRfc5424: def _make_line(self, fields_str="", msg=""): - sd = f"[decnet@55555 {fields_str}]" if fields_str else "-" + sd = f"[relay@55555 {fields_str}]" if fields_str else "-" suffix = f" {msg}" if msg else "" return f"<134>1 2024-01-15T12:00:00+00:00 decky-01 http - request {sd}{suffix}" @@ -126,7 +126,7 @@ class TestParseRfc5424: assert result["msg"] == "hello world" def test_sd_with_msg_after_bracket(self): - line = '<134>1 2024-01-15T12:00:00+00:00 decky-01 http - request [decnet@55555 src_ip="1.2.3.4"] login attempt' + line = '<134>1 2024-01-15T12:00:00+00:00 decky-01 http - request [relay@55555 src_ip="1.2.3.4"] login attempt' result = parse_rfc5424(line) assert result is not None assert result["fields"]["src_ip"] == "1.2.3.4" @@ -227,7 +227,7 @@ class TestStreamContainer: json_path = tmp_path / "test.json" mock_container = MagicMock() - rfc_line = '<134>1 2024-01-15T12:00:00+00:00 decky-01 ssh - auth [decnet@55555 src_ip="1.2.3.4"] login\n' + rfc_line = '<134>1 2024-01-15T12:00:00+00:00 decky-01 ssh - auth [relay@55555 src_ip="1.2.3.4"] login\n' mock_container.logs.return_value = [rfc_line.encode("utf-8")] mock_client = MagicMock() @@ -320,7 +320,7 @@ class TestStreamContainer: rfc_line = ( '<134>1 2024-01-15T12:00:00+00:00 decky-01 ssh - auth ' - '[decnet@55555 src_ip="1.2.3.4"] login\n' + '[relay@55555 src_ip="1.2.3.4"] login\n' ) encoded = rfc_line.encode("utf-8") @@ -436,7 +436,7 @@ class TestIngestRateLimiter: json_path = tmp_path / "test.json" line = ( '<134>1 2024-01-15T12:00:00+00:00 decky-01 ssh - connect ' - '[decnet@55555 src_ip="1.2.3.4"]\n' + '[relay@55555 src_ip="1.2.3.4"]\n' ) payload = (line * 5).encode("utf-8") diff --git a/tests/test_prober_worker.py b/tests/test_prober_worker.py index 95b882f..231ae83 100644 --- a/tests/test_prober_worker.py +++ b/tests/test_prober_worker.py @@ -471,7 +471,7 @@ class TestWriteEvent: log_content = log_path.read_text() assert "test_event" in log_content - assert "decnet@55555" in log_content + assert "relay@55555" in log_content json_content = json_path.read_text() record = json.loads(json_content.strip()) diff --git a/tests/test_sniffer_ja3.py b/tests/test_sniffer_ja3.py index e854544..a087436 100644 --- a/tests/test_sniffer_ja3.py +++ b/tests/test_sniffer_ja3.py @@ -2,7 +2,7 @@ Unit tests for the JA3/JA3S parsing logic in templates/sniffer/server.py. Imports the parser functions directly via sys.path manipulation, with -decnet_logging mocked out (it's a container-side stub at template build time). +syslog_bridge mocked out (it's a container-side stub at template build time). """ from __future__ import annotations @@ -16,19 +16,19 @@ from unittest.mock import MagicMock import pytest -# ─── Import sniffer module with mocked decnet_logging ───────────────────────── +# ─── Import sniffer module with mocked syslog_bridge ───────────────────────── _SNIFFER_DIR = str(Path(__file__).parent.parent / "templates" / "sniffer") def _load_sniffer(): - """Load templates/sniffer/server.py with decnet_logging stubbed out.""" - # Stub the decnet_logging module that server.py imports - _stub = types.ModuleType("decnet_logging") + """Load templates/sniffer/server.py with syslog_bridge stubbed out.""" + # Stub the syslog_bridge module that server.py imports + _stub = types.ModuleType("syslog_bridge") _stub.SEVERITY_INFO = 6 _stub.SEVERITY_WARNING = 4 _stub.syslog_line = MagicMock(return_value="<134>1 fake") _stub.write_syslog_file = MagicMock() - sys.modules.setdefault("decnet_logging", _stub) + sys.modules.setdefault("syslog_bridge", _stub) if _SNIFFER_DIR not in sys.path: sys.path.insert(0, _SNIFFER_DIR) diff --git a/tests/test_sniffer_tcp_fingerprint.py b/tests/test_sniffer_tcp_fingerprint.py index fc04714..39c4ec0 100644 --- a/tests/test_sniffer_tcp_fingerprint.py +++ b/tests/test_sniffer_tcp_fingerprint.py @@ -72,7 +72,7 @@ def _windows_syn(src_port: int = 45001): def _fields_from_line(line: str) -> dict[str, str]: """Parse the SD-params section of an RFC 5424 syslog line into a dict.""" import re - m = re.search(r"\[decnet@55555 (.*?)\]", line) + m = re.search(r"\[relay@55555 (.*?)\]", line) if not m: return {} body = m.group(1) diff --git a/tests/test_ssh.py b/tests/test_ssh.py index 6317f8e..c26cbf2 100644 --- a/tests/test_ssh.py +++ b/tests/test_ssh.py @@ -127,7 +127,7 @@ def test_dockerfile_runs_as_root(): def test_dockerfile_rsyslog_conf_created(): df = _dockerfile_text() - assert "99-decnet.conf" in df + assert "50-journal-forward.conf" in df assert "RFC5424fmt" in df @@ -231,7 +231,8 @@ def test_dockerfile_does_not_ship_decnet_capture_name(): def test_dockerfile_creates_quarantine_dir(): df = _dockerfile_text() - assert "/var/decnet/captured" in df + # In-container path masquerades as the real systemd-coredump dir. + assert "/var/lib/systemd/coredump" in df assert "chmod 700" in df @@ -265,8 +266,8 @@ def test_capture_script_uses_close_write_and_moved_to(): def test_capture_script_skips_quarantine_path(): body = _capture_text() - # Must not loop on its own writes. - assert "/var/decnet/" in body + # Must not loop on its own writes — quarantine lives under /var/lib/systemd. + assert "/var/lib/systemd/" in body def test_capture_script_resolves_writer_pid(): @@ -329,7 +330,7 @@ def test_fragment_mounts_quarantine_volume(): frag = _fragment() vols = frag.get("volumes", []) assert any( - v.endswith(":/var/decnet/captured:rw") for v in vols + v.endswith(":/var/lib/systemd/coredump:rw") for v in vols ), f"quarantine volume missing: {vols}" diff --git a/tests/test_syslog_formatter.py b/tests/test_syslog_formatter.py index 0b07bfc..6e08815 100644 --- a/tests/test_syslog_formatter.py +++ b/tests/test_syslog_formatter.py @@ -106,7 +106,7 @@ class TestStructuredData: def test_sd_element_present(self): line = format_rfc5424("http", "h", "request", remote_addr="1.2.3.4", method="GET") sd_and_msg = _parse(line).group(6) - assert sd_and_msg.startswith("[decnet@55555 ") + assert sd_and_msg.startswith("[relay@55555 ") assert 'remote_addr="1.2.3.4"' in sd_and_msg assert 'method="GET"' in sd_and_msg