diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..6878820 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI + +on: + push: + branches: [dev, testing] + +jobs: + lint: + name: Lint (ruff) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: pip install ruff + - run: ruff check . + + test: + name: Test (pytest) + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - run: pip install -e . + - run: pytest tests/ -v --tb=short + + bandit: + name: SAST (bandit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: pip install bandit + - run: bandit -r decnet/ -ll -x decnet/services/registry.py + + pip-audit: + name: Dependency audit (pip-audit) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: pip install pip-audit + - run: pip install -e . + - run: pip-audit --skip-editable + + open-pr: + name: Open PR to main + runs-on: ubuntu-latest + needs: [lint, test, bandit, pip-audit] + if: github.ref == 'refs/heads/dev' + steps: + - name: Open PR via Gitea API + run: | + echo "--- Checking for existing open PRs ---" + LIST_RESPONSE=$(curl -s \ + -H "Authorization: token ${{ secrets.DECNET_PR_TOKEN }}" \ + "https://git.resacachile.cl/api/v1/repos/anti/DECNET/pulls?state=open&head=anti:dev&base=main&limit=5") + echo "$LIST_RESPONSE" + EXISTING=$(echo "$LIST_RESPONSE" | python3 -c "import sys, json; print(len(json.load(sys.stdin)))") + echo "Open PRs found: $EXISTING" + if [ "$EXISTING" -gt "0" ]; then + echo "PR already open, skipping." + exit 0 + fi + echo "--- Creating PR ---" + CREATE_RESPONSE=$(curl -s -X POST \ + -H "Authorization: token ${{ secrets.DECNET_PR_TOKEN }}" \ + -H "Content-Type: application/json" \ + -d '{ + "title": "Auto PR: dev → main", + "head": "dev", + "base": "main", + "body": "All CI and security checks passed. Review and merge when ready." + }' \ + "https://git.resacachile.cl/api/v1/repos/anti/DECNET/pulls") + echo "$CREATE_RESPONSE" diff --git a/.gitea/workflows/pr.yml b/.gitea/workflows/pr.yml new file mode 100644 index 0000000..4e38d3c --- /dev/null +++ b/.gitea/workflows/pr.yml @@ -0,0 +1,31 @@ +name: PR Gate + +on: + pull_request: + branches: [main] + +jobs: + lint: + name: Lint (ruff) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: pip install ruff + - run: ruff check . + + test: + name: Test (pytest) + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.11", "3.12"] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - run: pip install -e . + - run: pytest tests/ -v --tb=short diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 9a8c373..acdc2e6 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -42,7 +42,7 @@ jobs: fi docker: - name: Build & push ${{ matrix.service }} + name: Build, scan & push ${{ matrix.service }} runs-on: ubuntu-latest needs: tag strategy: @@ -76,6 +76,9 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to Gitea container registry uses: docker/login-action@v3 with: @@ -83,7 +86,26 @@ jobs: username: ${{ secrets.REGISTRY_USER }} password: ${{ secrets.REGISTRY_TOKEN }} - - name: Build and push + - name: Build image locally + uses: docker/build-push-action@v5 + with: + context: templates/${{ matrix.service }} + load: true + push: false + tags: decnet-${{ matrix.service }}:scan + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Scan with Trivy + uses: aquasecurity/trivy-action@master + with: + image-ref: decnet-${{ matrix.service }}:scan + exit-code: "1" + severity: CRITICAL + ignore-unfixed: true + + - name: Push image + if: success() uses: docker/build-push-action@v5 with: context: templates/${{ matrix.service }} @@ -91,3 +113,4 @@ jobs: tags: | ${{ env.REGISTRY }}/${{ env.OWNER }}/decnet-${{ matrix.service }}:latest ${{ env.REGISTRY }}/${{ env.OWNER }}/decnet-${{ matrix.service }}:v${{ needs.tag.outputs.version }} + cache-from: type=gha diff --git a/.gitignore b/.gitignore index c1edd0c..f432c66 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,10 @@ decnet-compose.yml decnet-state.json *.ini .env +decnet.log* +*.loggy +*.nmap +linterfails.log +test-scan +webmail +windows1 diff --git a/CLAUDE.md b/CLAUDE.md index b1b13c2..999ec8f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,3 +54,4 @@ DECNET is a honeypot/deception network framework. It deploys fake machines (call - NEVER pass broken code to the user. - Broken means: not running, not passing 100% tests, etc. - After tests pass with 100%, always git commit your changes. +- NEVER add "Co-Authored-By" or any Claude attribution lines to git commit messages. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..f67c04e --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1 @@ +CI/CD TEST 2 diff --git a/NOTES.md b/NOTES.md index b91dd40..389d626 100644 --- a/NOTES.md +++ b/NOTES.md @@ -51,3 +51,63 @@ docker network create -d macvlan \ #### Issues This initial test doesn't seem to be working. Might be that I'm using WSL, so I downloaded a Ubuntu 22.04 Server ISO. I'll try the MACVLAN network on it. Now, if that doesn't work, I don't see how the 802.1q would work, at least on _my network_. Perhaps if I had a switch I could make it work, but currently I don't have one :c + +--- + +# TODO + +## Core / Hardening + +- [ ] **Attacker fingerprinting** — Beyond IP logging: capture TLS JA3/JA4 hashes, TCP window sizes, User-Agent strings, SSH client banners, and tool signatures (nmap, masscan, Metasploit, Cobalt Strike). Build attacker profiles across sessions. +- [ ] **Canary tokens** — Embed canary URLs, fake AWS keys, fake API tokens, and honeydocs (PDF/DOCX with phone-home URLs) into decky filesystems. Fire an alert the moment one is used. +- [ ] **Tarpit mode** — Slow down attackers by making services respond extremely slowly (e.g., SSH that takes 60s to reject, HTTP that drip-feeds bytes). Wastes attacker time and resources. +- [ ] **Dynamic decky mutation** — Deckies that change their exposed services or OS fingerprint over time to confuse port-scan caching and appear more "alive." +- [ ] **Credential harvesting DB** — Every username/password attempt across all services lands in a queryable database. Expose via CLI (`decnet creds`) and flag reuse across deckies. +- [ ] **Session recording** — Full session capture for SSH/Telnet (keystroke logs, commands run, files downloaded). Cowrie already does this — surface it better in the CLI and correlation engine. +- [ ] **Payload capture** — Store every file uploaded or command executed by an attacker. Hash and auto-submit to VirusTotal or a local sandbox. + +## Detection & Intelligence + +- [ ] **Real-time alerting** — Webhook/Slack/Telegram notifications when an attacker hits a decky for the first time, crosses N deckies (lateral movement), or uses a known bad IP. +- [ ] **Threat intel enrichment** — Auto-lookup attacker IPs against AbuseIPDB, Shodan, GreyNoise, and AlienVault OTX. Tag known scanners vs. targeted attackers. +- [ ] **Attack campaign clustering** — Group attacker sessions by tooling signatures, timing patterns, and credential sets. Identify coordinated campaigns hitting multiple deckies. +- [ ] **GeoIP mapping** — Attacker origin on a world map. Correlate with ASN data to identify cloud exit nodes, VPNs, and Tor exits. +- [ ] **TTPs tagging** — Map observed attacker behaviors to MITRE ATT&CK techniques automatically. Tag events in the correlation engine. +- [ ] **Honeypot interaction scoring** — Score attackers on a scale: casual scanner vs. persistent targeted attacker, based on depth of interaction and commands run. + +## Dashboard & Visibility + +- [ ] **Web dashboard** — Real-time web UI showing live decky status, attacker activity, traversal graphs, and credential stats. Could be a simple FastAPI + HTMX or a full React app. +- [ ] **Pre-built Kibana/Grafana dashboards** — Ship dashboard JSON exports out of the box so ELK/Grafana deployments are plug-and-play. +- [ ] **CLI live feed** — `decnet watch` command: tail all decky logs in a unified, colored terminal stream (like `docker-compose logs -f` but prettier). +- [ ] **Traversal graph export** — Export attacker traversal graphs as DOT/Graphviz or JSON for visualization in external tools. +- [ ] **Daily digest** — Automated daily summary email/report: new attackers, top credentials tried, most-hit services. + +## Deployment & Infrastructure + +- [ ] **SWARM / multihost mode** — Full Ansible-based orchestration for deploying deckies across N real hosts. +- [ ] **Terraform/Pulumi provider** — Spin up cloud-hosted deckies on AWS/GCP/Azure with one command. Useful for internet-facing honeynets. +- [ ] **Auto-scaling** — When attack traffic increases, automatically spawn more deckies to absorb and log more activity. +- [ ] **Kubernetes deployment mode** — Run deckies as Kubernetes pods for environments already running k8s. +- [ ] **Proxmox/libvirt backend** — Full VM-based deckies instead of containers, for even more realistic OS fingerprints and behavior. Docker for speed; VMs for realism. +- [ ] **Raspberry Pi / ARM support** — Low-cost physical honeynets using RPis. Validate ARM image builds. +- [ ] **Decky health monitoring** — Watchdog that auto-restarts crashed deckies and alerts if a service goes dark. + +## Services & Realism + +- [ ] **HTTPS/TLS support** — HTTP honeypot with a self-signed or Let's Encrypt cert. Many real-world services use HTTPS; plain HTTP stands out. +- [ ] **Fake Active Directory** — A convincing fake AD/LDAP with fake users, groups, and GPOs. Attacker tools like BloodHound should get juicy (fake) data. +- [ ] **Fake file shares** — SMB/NFS shares pre-populated with enticing but fake files: "passwords.xlsx", "vpn_config.ovpn", "backup_keys.tar.gz". All instrumented to detect access. +- [ ] **Realistic web apps** — HTTP honeypot serving convincing fake apps: a fake WordPress, a fake phpMyAdmin, a fake Grafana login — all logging every interaction. +- [ ] **OT/ICS profiles** — Expand Conpot support: Modbus, DNP3, BACnet, EtherNet/IP. Convincing industrial control system decoys. +- [ ] **Printer/IoT archetypes** — Expand existing printer/camera archetypes with actual service emulation (IPP, ONVIF, WS-Discovery). +- [ ] **Service interaction depth** — Some services currently just log the connection. Deepen interaction: fake MySQL that accepts queries and returns realistic fake data, fake Redis that stores and retrieves dummy keys. + +## Developer Experience + +- [ ] **Plugin SDK docs** — Full documentation and an example plugin for adding custom services. Lower the barrier for community contributions. +- [ ] **Integration tests** — Full deploy/teardown cycle tests against a real Docker daemon (not just unit tests). +- [ ] **Per-service tests** — Each of the 29 service implementations deserves its own test coverage. +- [ ] **CI/CD pipeline** — GitHub/Gitea Actions: run tests on push, lint, build Docker images, publish releases. +- [ ] **Config validation CLI** — `decnet validate my.ini` to dry-check an INI config before deploying. +- [ ] **Config generator wizard** — `decnet wizard` interactive prompt to generate an INI config without writing one by hand. diff --git a/decnet/correlation/engine.py b/decnet/correlation/engine.py index 3e17d4d..1f9f748 100644 --- a/decnet/correlation/engine.py +++ b/decnet/correlation/engine.py @@ -22,7 +22,6 @@ Usage from __future__ import annotations -import json from collections import defaultdict from pathlib import Path diff --git a/decnet/correlation/parser.py b/decnet/correlation/parser.py index 927d0c8..e457254 100644 --- a/decnet/correlation/parser.py +++ b/decnet/correlation/parser.py @@ -17,7 +17,7 @@ The attacker IP may appear under several field names depending on service: from __future__ import annotations import re -from dataclasses import dataclass, field +from dataclasses import dataclass from datetime import datetime # RFC 5424 line structure diff --git a/decnet/deployer.py b/decnet/deployer.py index 5c3bd8f..c2324f2 100644 --- a/decnet/deployer.py +++ b/decnet/deployer.py @@ -14,11 +14,8 @@ from decnet.config import DecnetConfig, clear_state, load_state, save_state from decnet.composer import write_compose from decnet.network import ( MACVLAN_NETWORK_NAME, - allocate_ips, create_ipvlan_network, create_macvlan_network, - detect_interface, - detect_subnet, get_host_ip, ips_to_range, remove_macvlan_network, diff --git a/decnet/logging/file_handler.py b/decnet/logging/file_handler.py index 47267d0..98d0f83 100644 --- a/decnet/logging/file_handler.py +++ b/decnet/logging/file_handler.py @@ -1,4 +1,3 @@ -from __future__ import annotations """ Rotating file handler for DECNET syslog output. @@ -7,6 +6,8 @@ Path is controlled by the DECNET_LOG_FILE environment variable (default: /var/log/decnet/decnet.log). """ +from __future__ import annotations + import logging import logging.handlers import os diff --git a/decnet/logging/syslog_formatter.py b/decnet/logging/syslog_formatter.py index 998fff0..6d43244 100644 --- a/decnet/logging/syslog_formatter.py +++ b/decnet/logging/syslog_formatter.py @@ -1,4 +1,3 @@ -from __future__ import annotations """ RFC 5424 syslog formatter for DECNET. @@ -9,6 +8,8 @@ Facility: local0 (16) PEN for structured data: decnet@55555 """ +from __future__ import annotations + from datetime import datetime, timezone from typing import Any diff --git a/decnet/network.py b/decnet/network.py index aa829d0..a68ca7f 100644 --- a/decnet/network.py +++ b/decnet/network.py @@ -8,10 +8,7 @@ Handles: - IP allocation (sequential, skipping reserved addresses) """ -import ipaddress import os -import shutil -import socket import subprocess from ipaddress import IPv4Address, IPv4Interface, IPv4Network diff --git a/decnet/services/registry.py b/decnet/services/registry.py index bbfc325..297335f 100644 --- a/decnet/services/registry.py +++ b/decnet/services/registry.py @@ -26,6 +26,8 @@ def _load_plugins() -> None: continue importlib.import_module(f"decnet.services.{module_info.name}") for cls in BaseService.__subclasses__(): + if not cls.__module__.startswith("decnet.services."): + continue instance = cls() _registry[instance.name] = instance _loaded = True diff --git a/pyproject.toml b/pyproject.toml index f1d6c67..ca7fc3b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,10 @@ dependencies = [ "docker>=7.0", "pyyaml>=6.0", "jinja2>=3.1", + "pytest>=8.0", + "ruff>=0.4", + "bandit>=1.7", + "pip-audit>=2.0", ] [project.scripts] diff --git a/templates/decnet_logging.py b/templates/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/decnet_logging.py +++ b/templates/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/docker_api/decnet_logging.py b/templates/docker_api/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/docker_api/decnet_logging.py +++ b/templates/docker_api/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/docker_api/server.py b/templates/docker_api/server.py index a8563ba..4a0983c 100644 --- a/templates/docker_api/server.py +++ b/templates/docker_api/server.py @@ -8,8 +8,6 @@ but fake data. Logs all requests as JSON. import json import os -import socket -from datetime import datetime, timezone from flask import Flask, request from decnet_logging import syslog_line, write_syslog_file, forward_syslog diff --git a/templates/elasticsearch/decnet_logging.py b/templates/elasticsearch/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/elasticsearch/decnet_logging.py +++ b/templates/elasticsearch/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/elasticsearch/server.py b/templates/elasticsearch/server.py index 4bacf3c..a09ec81 100644 --- a/templates/elasticsearch/server.py +++ b/templates/elasticsearch/server.py @@ -7,8 +7,6 @@ as JSON. Designed to attract automated scanners and credential stuffers. import json import os -import socket -from datetime import datetime, timezone from http.server import BaseHTTPRequestHandler, HTTPServer from decnet_logging import syslog_line, write_syslog_file, forward_syslog diff --git a/templates/ftp/decnet_logging.py b/templates/ftp/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/ftp/decnet_logging.py +++ b/templates/ftp/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/ftp/server.py b/templates/ftp/server.py index 4ec60c3..c01a080 100644 --- a/templates/ftp/server.py +++ b/templates/ftp/server.py @@ -5,13 +5,10 @@ Accepts any credentials, logs all commands and file requests, forwards events as JSON to LOG_TARGET if set. """ -import json import os -import socket import sys -from datetime import datetime, timezone -from twisted.internet import defer, protocol, reactor +from twisted.internet import defer, reactor from twisted.protocols.ftp import FTP, FTPFactory from twisted.python import log as twisted_log from decnet_logging import syslog_line, write_syslog_file, forward_syslog diff --git a/templates/http/decnet_logging.py b/templates/http/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/http/decnet_logging.py +++ b/templates/http/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/http/server.py b/templates/http/server.py index ced2d36..233d5c0 100644 --- a/templates/http/server.py +++ b/templates/http/server.py @@ -7,8 +7,6 @@ and responds with configurable pages. Forwards events as JSON to LOG_TARGET if s import json import os -import socket -from datetime import datetime, timezone from pathlib import Path from flask import Flask, request, send_from_directory diff --git a/templates/imap/decnet_logging.py b/templates/imap/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/imap/decnet_logging.py +++ b/templates/imap/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/imap/server.py b/templates/imap/server.py index 3eecd2d..ddc00bc 100644 --- a/templates/imap/server.py +++ b/templates/imap/server.py @@ -6,10 +6,7 @@ AUTHENTICATE), then returns a NO response. Logs all commands as JSON. """ import asyncio -import json import os -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mailserver") diff --git a/templates/k8s/decnet_logging.py b/templates/k8s/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/k8s/decnet_logging.py +++ b/templates/k8s/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/k8s/server.py b/templates/k8s/server.py index 5556583..2bc23c1 100644 --- a/templates/k8s/server.py +++ b/templates/k8s/server.py @@ -8,8 +8,6 @@ Responds to recon endpoints (/version, /api, /apis, /api/v1/namespaces, import json import os -import socket -from datetime import datetime, timezone from flask import Flask, request from decnet_logging import syslog_line, write_syslog_file, forward_syslog diff --git a/templates/ldap/decnet_logging.py b/templates/ldap/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/ldap/decnet_logging.py +++ b/templates/ldap/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/ldap/server.py b/templates/ldap/server.py index d126873..23a1f2a 100644 --- a/templates/ldap/server.py +++ b/templates/ldap/server.py @@ -6,10 +6,7 @@ invalidCredentials error. Logs all interactions as JSON. """ import asyncio -import json import os -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "ldapserver") diff --git a/templates/llmnr/decnet_logging.py b/templates/llmnr/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/llmnr/decnet_logging.py +++ b/templates/llmnr/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/llmnr/server.py b/templates/llmnr/server.py index 6153e5e..b033c2c 100644 --- a/templates/llmnr/server.py +++ b/templates/llmnr/server.py @@ -7,11 +7,8 @@ Logs every packet with source IP and decoded query name where possible. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "lan-host") diff --git a/templates/mongodb/decnet_logging.py b/templates/mongodb/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/mongodb/decnet_logging.py +++ b/templates/mongodb/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/mongodb/server.py b/templates/mongodb/server.py index 600cda0..f7ed26e 100644 --- a/templates/mongodb/server.py +++ b/templates/mongodb/server.py @@ -7,11 +7,8 @@ received messages as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mongodb") diff --git a/templates/mqtt/decnet_logging.py b/templates/mqtt/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/mqtt/decnet_logging.py +++ b/templates/mqtt/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/mqtt/server.py b/templates/mqtt/server.py index 61a929f..e49fd87 100644 --- a/templates/mqtt/server.py +++ b/templates/mqtt/server.py @@ -7,11 +7,8 @@ interactions as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mqtt-broker") @@ -48,11 +45,13 @@ def _parse_connect(payload: bytes): # Protocol level (1 byte) if pos >= len(payload): return {}, pos - _proto_level = payload[pos]; pos += 1 + _proto_level = payload[pos] + pos += 1 # Connect flags (1 byte) if pos >= len(payload): return {}, pos - flags = payload[pos]; pos += 1 + flags = payload[pos] + pos += 1 # Keep alive (2 bytes) pos += 2 # Client ID diff --git a/templates/mssql/decnet_logging.py b/templates/mssql/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/mssql/decnet_logging.py +++ b/templates/mssql/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/mssql/server.py b/templates/mssql/server.py index ce9508e..badd921 100644 --- a/templates/mssql/server.py +++ b/templates/mssql/server.py @@ -6,11 +6,8 @@ a login failed error. Logs auth attempts as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "dbserver") diff --git a/templates/mysql/decnet_logging.py b/templates/mysql/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/mysql/decnet_logging.py +++ b/templates/mysql/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/mysql/server.py b/templates/mysql/server.py index 675f576..9fcaff0 100644 --- a/templates/mysql/server.py +++ b/templates/mysql/server.py @@ -7,11 +7,8 @@ attempts as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "dbserver") diff --git a/templates/pop3/decnet_logging.py b/templates/pop3/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/pop3/decnet_logging.py +++ b/templates/pop3/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/pop3/server.py b/templates/pop3/server.py index 7dc3ed7..52b52ec 100644 --- a/templates/pop3/server.py +++ b/templates/pop3/server.py @@ -7,10 +7,7 @@ to LOG_TARGET if set. """ import asyncio -import json import os -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mailserver") diff --git a/templates/postgres/decnet_logging.py b/templates/postgres/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/postgres/decnet_logging.py +++ b/templates/postgres/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/postgres/server.py b/templates/postgres/server.py index a0e41e4..8000c25 100644 --- a/templates/postgres/server.py +++ b/templates/postgres/server.py @@ -7,11 +7,8 @@ returns an error. Logs all interactions as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "pgserver") diff --git a/templates/rdp/decnet_logging.py b/templates/rdp/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/rdp/decnet_logging.py +++ b/templates/rdp/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/rdp/server.py b/templates/rdp/server.py index f5c9fed..24d3650 100644 --- a/templates/rdp/server.py +++ b/templates/rdp/server.py @@ -6,11 +6,8 @@ in the initial RDP negotiation request. Forwards events as JSON to LOG_TARGET if set. """ -import json import os -import socket import sys -from datetime import datetime, timezone from twisted.internet import protocol, reactor from twisted.python import log as twisted_log diff --git a/templates/redis/decnet_logging.py b/templates/redis/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/redis/decnet_logging.py +++ b/templates/redis/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/redis/server.py b/templates/redis/server.py index 02d7044..7b2a6b9 100644 --- a/templates/redis/server.py +++ b/templates/redis/server.py @@ -6,10 +6,7 @@ KEYS, and arbitrary commands. Logs every command and argument as JSON. """ import asyncio -import json import os -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "cache-server") diff --git a/templates/sip/decnet_logging.py b/templates/sip/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/sip/decnet_logging.py +++ b/templates/sip/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/sip/server.py b/templates/sip/server.py index d3e9128..7ef41ac 100644 --- a/templates/sip/server.py +++ b/templates/sip/server.py @@ -6,11 +6,8 @@ Authorization header and call metadata, then responds with 401 Unauthorized. """ import asyncio -import json import os import re -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "pbx") diff --git a/templates/smb/decnet_logging.py b/templates/smb/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/smb/decnet_logging.py +++ b/templates/smb/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/smb/server.py b/templates/smb/server.py index 195cdcd..bd185d4 100644 --- a/templates/smb/server.py +++ b/templates/smb/server.py @@ -4,10 +4,7 @@ Minimal SMB server using Impacket's SimpleSMBServer. Logs all connection attempts, optionally forwarding them as JSON to LOG_TARGET. """ -import json import os -import socket -from datetime import datetime, timezone from impacket import smbserver from decnet_logging import syslog_line, write_syslog_file, forward_syslog diff --git a/templates/smtp/decnet_logging.py b/templates/smtp/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/smtp/decnet_logging.py +++ b/templates/smtp/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/smtp/server.py b/templates/smtp/server.py index be29df2..222e3f9 100644 --- a/templates/smtp/server.py +++ b/templates/smtp/server.py @@ -5,10 +5,7 @@ Logs EHLO/AUTH/MAIL FROM/RCPT TO attempts as JSON, then denies auth. """ import asyncio -import json import os -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "mailserver") diff --git a/templates/snmp/decnet_logging.py b/templates/snmp/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/snmp/decnet_logging.py +++ b/templates/snmp/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/snmp/server.py b/templates/snmp/server.py index 66c1a44..6f97ccc 100644 --- a/templates/snmp/server.py +++ b/templates/snmp/server.py @@ -7,11 +7,8 @@ Logs all requests as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "switch") @@ -94,35 +91,46 @@ def _ber_tlv(tag: int, value: bytes) -> bytes: def _parse_snmp(data: bytes): """Return (version, community, request_id, oids) or raise.""" pos = 0 - assert data[pos] == 0x30; pos += 1 + assert data[pos] == 0x30 + pos += 1 _, pos = _read_ber_length(data, pos) # version - assert data[pos] == 0x02; pos += 1 + assert data[pos] == 0x02 + pos += 1 v_len, pos = _read_ber_length(data, pos) - version = int.from_bytes(data[pos:pos + v_len], "big"); pos += v_len + version = int.from_bytes(data[pos:pos + v_len], "big") + pos += v_len # community - assert data[pos] == 0x04; pos += 1 + assert data[pos] == 0x04 + pos += 1 c_len, pos = _read_ber_length(data, pos) - community = data[pos:pos + c_len].decode(errors="replace"); pos += c_len + community = data[pos:pos + c_len].decode(errors="replace") + pos += c_len # PDU type (0xa0 = GetRequest, 0xa1 = GetNextRequest) - pdu_type = data[pos]; pos += 1 + pos += 1 _, pos = _read_ber_length(data, pos) # request-id - assert data[pos] == 0x02; pos += 1 + assert data[pos] == 0x02 + pos += 1 r_len, pos = _read_ber_length(data, pos) - request_id = int.from_bytes(data[pos:pos + r_len], "big"); pos += r_len + request_id = int.from_bytes(data[pos:pos + r_len], "big") + pos += r_len pos += 4 # skip error-status and error-index # varbind list - assert data[pos] == 0x30; pos += 1 + assert data[pos] == 0x30 + pos += 1 vbl_len, pos = _read_ber_length(data, pos) end = pos + vbl_len oids = [] while pos < end: - assert data[pos] == 0x30; pos += 1 + assert data[pos] == 0x30 + pos += 1 vb_len, pos = _read_ber_length(data, pos) - assert data[pos] == 0x06; pos += 1 + assert data[pos] == 0x06 + pos += 1 oid_len, pos = _read_ber_length(data, pos) - oid = _decode_oid(data[pos:pos + oid_len]); pos += oid_len + oid = _decode_oid(data[pos:pos + oid_len]) + pos += oid_len oids.append(oid) pos += vb_len - oid_len - 2 # skip value return version, community, request_id, oids diff --git a/templates/tftp/decnet_logging.py b/templates/tftp/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/tftp/decnet_logging.py +++ b/templates/tftp/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/tftp/server.py b/templates/tftp/server.py index 3ca9195..0ecaff7 100644 --- a/templates/tftp/server.py +++ b/templates/tftp/server.py @@ -6,11 +6,8 @@ then responds with an error packet. Logs all requests as JSON. """ import asyncio -import json import os -import socket import struct -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "tftpserver") diff --git a/templates/vnc/decnet_logging.py b/templates/vnc/decnet_logging.py index 0ad17fb..9f1f935 100644 --- a/templates/vnc/decnet_logging.py +++ b/templates/vnc/decnet_logging.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import annotations """ Shared RFC 5424 syslog helper for DECNET service templates. diff --git a/templates/vnc/server.py b/templates/vnc/server.py index 8d5fc52..f5bca17 100644 --- a/templates/vnc/server.py +++ b/templates/vnc/server.py @@ -7,10 +7,7 @@ failed". Logs the raw response for offline cracking. """ import asyncio -import json import os -import socket -from datetime import datetime, timezone from decnet_logging import syslog_line, write_syslog_file, forward_syslog NODE_NAME = os.environ.get("NODE_NAME", "desktop") diff --git a/test-scan b/test-scan deleted file mode 100644 index facf080..0000000 --- a/test-scan +++ /dev/null @@ -1,98 +0,0 @@ -# Nmap 7.92 scan initiated Sat Apr 4 04:41:05 2026 as: nmap -sS -sV -O -A -oN test-scan 192.168.1.110-119 -Nmap scan report for 192.168.1.110 -Host is up (0.000035s latency). -Not shown: 996 closed tcp ports (reset) -PORT STATE SERVICE VERSION -25/tcp open smtp Postfix smtpd -|_smtp-commands: decky-webmail, PIPELINING, SIZE 10240000, VRFY, ETRN, AUTH PLAIN LOGIN, ENHANCEDSTATUSCODES, 8BITMIME, DSN -80/tcp open http Apache httpd 2.4.54 ((Debian)) -|_http-title: WordPress › Error -|_http-server-header: Werkzeug/3.1.8 Python/3.11.2 -110/tcp open pop3 -| fingerprint-strings: -| GenericLines, HTTPOptions: -| +OK decky-webmail POP3 server ready -| -ERR Unknown command -| -ERR Unknown command -| NULL: -|_ +OK decky-webmail POP3 server ready -|_pop3-capabilities: USER -143/tcp open imap -| fingerprint-strings: -| GenericLines, NULL: -| * OK [decky-webmail] IMAP4rev1 Service Ready -| GetRequest: -| * OK [decky-webmail] IMAP4rev1 Service Ready -|_ Command not recognized -|_imap-capabilities: OK CAPABILITY AUTH=LOGINA0001 IMAP4rev1 completed AUTH=PLAIN -2 services unrecognized despite returning data. If you know the service/version, please submit the following fingerprints at https://nmap.org/cgi-bin/submit.cgi?new-service : -==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)============== -SF-Port110-TCP:V=7.92%I=7%D=4/4%Time=69D0C099%P=x86_64-redhat-linux-gnu%r( -SF:NULL,25,"\+OK\x20decky-webmail\x20POP3\x20server\x20ready\r\n")%r(Gener -SF:icLines,51,"\+OK\x20decky-webmail\x20POP3\x20server\x20ready\r\n-ERR\x2 -SF:0Unknown\x20command\r\n-ERR\x20Unknown\x20command\r\n")%r(HTTPOptions,5 -SF:1,"\+OK\x20decky-webmail\x20POP3\x20server\x20ready\r\n-ERR\x20Unknown\ -SF:x20command\r\n-ERR\x20Unknown\x20command\r\n"); -==============NEXT SERVICE FINGERPRINT (SUBMIT INDIVIDUALLY)============== -SF-Port143-TCP:V=7.92%I=7%D=4/4%Time=69D0C099%P=x86_64-redhat-linux-gnu%r( -SF:NULL,2E,"\*\x20OK\x20\[decky-webmail\]\x20IMAP4rev1\x20Service\x20Ready -SF:\r\n")%r(GetRequest,4E,"\*\x20OK\x20\[decky-webmail\]\x20IMAP4rev1\x20S -SF:ervice\x20Ready\r\nGET\x20BAD\x20Command\x20not\x20recognized\r\n")%r(G -SF:enericLines,2E,"\*\x20OK\x20\[decky-webmail\]\x20IMAP4rev1\x20Service\x -SF:20Ready\r\n"); -MAC Address: D2:55:C7:0D:B4:FC (Unknown) -Device type: general purpose -Running: Linux 5.X -OS CPE: cpe:/o:linux:linux_kernel:5 -OS details: Linux 5.3 - 5.4 -Network Distance: 1 hop -Service Info: Host: decky-webmail - -TRACEROUTE -HOP RTT ADDRESS -1 0.04 ms 192.168.1.110 - -Nmap scan report for 192.168.1.111 -Host is up (0.000016s latency). -Not shown: 998 closed tcp ports (reset) -PORT STATE SERVICE VERSION -21/tcp open ftp vsftpd (before 2.0.8) or WU-FTPD -445/tcp open microsoft-ds -| fingerprint-strings: -| SMBProgNeg: -| SMBr -|_ "3DUfw -1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service : -SF-Port445-TCP:V=7.92%I=7%D=4/4%Time=69D0C09E%P=x86_64-redhat-linux-gnu%r( -SF:SMBProgNeg,51,"\0\0\0M\xffSMBr\0\0\0\0\x80\0\xc0\0\0\0\0\0\0\0\0\0\0\0\ -SF:0\0\0@\x06\0\0\x01\0\x11\x07\0\x03\x01\0\x01\0\0\xfa\0\0\0\0\x01\0\0\0\ -SF:0\0p\0\0\0\0\0\0\0\0\0\0\0\0\0\x08\x08\0\x11\"3DUfw\x88"); -MAC Address: 92:44:B7:6C:F6:D0 (Unknown) -Device type: general purpose -Running: Linux 5.X -OS CPE: cpe:/o:linux:linux_kernel:5 -OS details: Linux 5.3 - 5.4 -Network Distance: 1 hop -Service Info: Host: Twisted - -Host script results: -| smb-security-mode: -| account_used: guest -| authentication_level: user -| challenge_response: supported -|_ message_signing: disabled (dangerous, but default) -| smb2-time: -| date: 2026-04-04T07:41:24 -|_ start_date: 2026-04-04T07:41:24 -| smb2-security-mode: -| 2.0.2: -|_ Message signing enabled but not required -|_ms-sql-info: ERROR: Script execution failed (use -d to debug) -|_clock-skew: mean: -77660d15h50m42s, deviation: 109828d18h12m51s, median: -155321d07h41m24s - -TRACEROUTE -HOP RTT ADDRESS -1 0.02 ms 192.168.1.111 - -OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . -# Nmap done at Sat Apr 4 04:41:33 2026 -- 10 IP addresses (2 hosts up) scanned in 28.27 seconds diff --git a/tests/test_build.py b/tests/test_build.py new file mode 100644 index 0000000..90a126c --- /dev/null +++ b/tests/test_build.py @@ -0,0 +1,61 @@ +"""Smoke test: verify the package and all submodules import cleanly.""" +import importlib +import pytest + + +MODULES = [ + "decnet", + "decnet.cli", + "decnet.config", + "decnet.composer", + "decnet.deployer", + "decnet.network", + "decnet.archetypes", + "decnet.distros", + "decnet.os_fingerprint", + "decnet.ini_loader", + "decnet.custom_service", + "decnet.correlation", + "decnet.correlation.engine", + "decnet.correlation.graph", + "decnet.correlation.parser", + "decnet.logging", + "decnet.logging.file_handler", + "decnet.logging.forwarder", + "decnet.logging.syslog_formatter", + "decnet.services", + "decnet.services.registry", + "decnet.services.base", + "decnet.services.ssh", + "decnet.services.ftp", + "decnet.services.http", + "decnet.services.smb", + "decnet.services.rdp", + "decnet.services.smtp", + "decnet.services.mysql", + "decnet.services.postgres", + "decnet.services.redis", + "decnet.services.mongodb", + "decnet.services.mssql", + "decnet.services.elasticsearch", + "decnet.services.ldap", + "decnet.services.k8s", + "decnet.services.docker_api", + "decnet.services.vnc", + "decnet.services.telnet", + "decnet.services.tftp", + "decnet.services.snmp", + "decnet.services.sip", + "decnet.services.mqtt", + "decnet.services.llmnr", + "decnet.services.imap", + "decnet.services.pop3", + "decnet.services.conpot", + "decnet.services.real_ssh", + "decnet.services.registry", +] + + +@pytest.mark.parametrize("module", MODULES) +def test_module_imports(module): + importlib.import_module(module)