Files
DECNET/templates/conpot/entrypoint.py
anti 8dd4c78b33 refactor: strip DECNET tokens from container-visible surface
Rename the container-side logging module decnet_logging → syslog_bridge
(canonical at templates/syslog_bridge.py, synced into each template by
the deployer). Drop the stale per-template copies; setuptools find was
picking them up anyway. Swap useradd/USER/chown "decnet" for "logrelay"
so no obvious token appears in the rendered container image.

Apply the same cloaking pattern to the telnet template that SSH got:
syslog pipe moves to /run/systemd/journal/syslog-relay and the relay
is cat'd via exec -a "systemd-journal-fwd". rsyslog.d conf rename
99-decnet.conf → 50-journal-forward.conf. SSH capture script:
/var/decnet/captured → /var/lib/systemd/coredump (real systemd path),
logger tag decnet-capture → systemd-journal. Compose volume updated
to match the new in-container quarantine path.

SD element ID shifts decnet@55555 → relay@55555; synced across
collector, parser, sniffer, prober, formatter, tests, and docs so the
host-side pipeline still matches what containers emit.
2026-04-17 22:57:53 -04:00

145 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""
Entrypoint wrapper for the Conpot ICS/SCADA honeypot.
Launches conpot as a child process and bridges its log output into the
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.
Written to be compatible with Python 3.6 (the conpot base image version).
"""
from __future__ import print_function
import os
import re
import signal
import subprocess
import sys
from datetime import datetime, timezone
# ── RFC 5424 inline formatter (Python 3.6-compatible) ─────────────────────────
_FACILITY_LOCAL0 = 16
_SD_ID = "relay@55555"
_NILVALUE = "-"
SEVERITY_INFO = 6
SEVERITY_WARNING = 4
SEVERITY_ERROR = 3
def _sd_escape(value):
return value.replace("\\", "\\\\").replace('"', '\\"').replace("]", "\\]")
def _syslog_line(event_type, severity=SEVERITY_INFO, **fields):
pri = "<{}>".format(_FACILITY_LOCAL0 * 8 + severity)
ts = datetime.now(timezone.utc).isoformat()
host = NODE_NAME[:255]
appname = "conpot"
msgid = event_type[:32]
if fields:
params = " ".join('{}="{}"'.format(k, _sd_escape(str(v))) for k, v in fields.items())
sd = "[{} {}]".format(_SD_ID, params)
else:
sd = _NILVALUE
return "{pri}1 {ts} {host} {appname} {nil} {msgid} {sd}".format(
pri=pri, ts=ts, host=host, appname=appname,
nil=_NILVALUE, msgid=msgid, sd=sd,
)
def _log(event_type, severity=SEVERITY_INFO, **fields):
print(_syslog_line(event_type, severity, **fields), flush=True)
# ── Config ────────────────────────────────────────────────────────────────────
NODE_NAME = os.environ.get("NODE_NAME", "conpot-node")
TEMPLATE = os.environ.get("CONPOT_TEMPLATE", "default")
_CONPOT_CMD = [
"/home/conpot/.local/bin/conpot",
"--template", TEMPLATE,
"--logfile", "/var/log/conpot/conpot.log",
"-f",
"--temp_dir", "/tmp",
]
# Grab the first routable IPv4 address from a log line
_IP_RE = re.compile(r"\b((?!127\.)(?!0\.)(?!255\.)\d{1,3}(?:\.\d{1,3}){3})\b")
_REQUEST_RE = re.compile(
r"request|recv|received|connect|session|query|command|"
r"modbus|snmp|http|s7comm|bacnet|enip",
re.IGNORECASE,
)
_ERROR_RE = re.compile(r"error|exception|traceback|critical|fail", re.IGNORECASE)
_WARN_RE = re.compile(r"warning|warn", re.IGNORECASE)
_STARTUP_RE = re.compile(
r"starting|started|listening|server|initializ|template|conpot",
re.IGNORECASE,
)
# ── Classifier ────────────────────────────────────────────────────────────────
def _classify(raw):
"""Return (event_type, severity, fields) for one conpot log line."""
fields = {}
m = _IP_RE.search(raw)
if m:
fields["src"] = m.group(1)
fields["msg"] = raw[:300]
if _ERROR_RE.search(raw):
return "error", SEVERITY_ERROR, fields
if _WARN_RE.search(raw):
return "warning", SEVERITY_WARNING, fields
if _REQUEST_RE.search(raw):
return "request", SEVERITY_INFO, fields
if _STARTUP_RE.search(raw):
return "startup", SEVERITY_INFO, fields
return "log", SEVERITY_INFO, fields
# ── Main ──────────────────────────────────────────────────────────────────────
def main():
_log("startup", msg="Conpot ICS honeypot starting (template={})".format(TEMPLATE))
proc = subprocess.Popen(
_CONPOT_CMD,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
universal_newlines=True,
)
def _forward(sig, _frame):
proc.send_signal(sig)
signal.signal(signal.SIGTERM, _forward)
signal.signal(signal.SIGINT, _forward)
try:
for raw_line in proc.stdout:
line = raw_line.rstrip()
if not line:
continue
event_type, severity, fields = _classify(line)
_log(event_type, severity, **fields)
finally:
proc.wait()
_log("shutdown", msg="Conpot ICS honeypot stopped")
sys.exit(proc.returncode)
if __name__ == "__main__":
main()