Merge dev into main: resolve conflicts, keep tests out of main

This commit is contained in:
2026-04-04 18:00:17 -03:00
62 changed files with 311 additions and 210 deletions

86
.gitea/workflows/ci.yml Normal file
View File

@@ -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"

31
.gitea/workflows/pr.yml Normal file
View File

@@ -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

View File

@@ -42,7 +42,7 @@ jobs:
fi fi
docker: docker:
name: Build & push ${{ matrix.service }} name: Build, scan & push ${{ matrix.service }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: tag needs: tag
strategy: strategy:
@@ -76,6 +76,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea container registry - name: Log in to Gitea container registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -83,7 +86,26 @@ jobs:
username: ${{ secrets.REGISTRY_USER }} username: ${{ secrets.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_TOKEN }} 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 uses: docker/build-push-action@v5
with: with:
context: templates/${{ matrix.service }} context: templates/${{ matrix.service }}
@@ -91,3 +113,4 @@ jobs:
tags: | tags: |
${{ env.REGISTRY }}/${{ env.OWNER }}/decnet-${{ matrix.service }}:latest ${{ env.REGISTRY }}/${{ env.OWNER }}/decnet-${{ matrix.service }}:latest
${{ env.REGISTRY }}/${{ env.OWNER }}/decnet-${{ matrix.service }}:v${{ needs.tag.outputs.version }} ${{ env.REGISTRY }}/${{ env.OWNER }}/decnet-${{ matrix.service }}:v${{ needs.tag.outputs.version }}
cache-from: type=gha

7
.gitignore vendored
View File

@@ -9,3 +9,10 @@ decnet-compose.yml
decnet-state.json decnet-state.json
*.ini *.ini
.env .env
decnet.log*
*.loggy
*.nmap
linterfails.log
test-scan
webmail
windows1

View File

@@ -54,3 +54,4 @@ DECNET is a honeypot/deception network framework. It deploys fake machines (call
- NEVER pass broken code to the user. - NEVER pass broken code to the user.
- Broken means: not running, not passing 100% tests, etc. - Broken means: not running, not passing 100% tests, etc.
- After tests pass with 100%, always git commit your changes. - After tests pass with 100%, always git commit your changes.
- NEVER add "Co-Authored-By" or any Claude attribution lines to git commit messages.

1
DEVELOPMENT.md Normal file
View File

@@ -0,0 +1 @@
CI/CD TEST 2

View File

@@ -51,3 +51,63 @@ docker network create -d macvlan \
#### Issues #### 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 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.

View File

@@ -22,7 +22,6 @@ Usage
from __future__ import annotations from __future__ import annotations
import json
from collections import defaultdict from collections import defaultdict
from pathlib import Path from pathlib import Path

View File

@@ -17,7 +17,7 @@ The attacker IP may appear under several field names depending on service:
from __future__ import annotations from __future__ import annotations
import re import re
from dataclasses import dataclass, field from dataclasses import dataclass
from datetime import datetime from datetime import datetime
# RFC 5424 line structure # RFC 5424 line structure

View File

@@ -14,11 +14,8 @@ from decnet.config import DecnetConfig, clear_state, load_state, save_state
from decnet.composer import write_compose from decnet.composer import write_compose
from decnet.network import ( from decnet.network import (
MACVLAN_NETWORK_NAME, MACVLAN_NETWORK_NAME,
allocate_ips,
create_ipvlan_network, create_ipvlan_network,
create_macvlan_network, create_macvlan_network,
detect_interface,
detect_subnet,
get_host_ip, get_host_ip,
ips_to_range, ips_to_range,
remove_macvlan_network, remove_macvlan_network,

View File

@@ -1,4 +1,3 @@
from __future__ import annotations
""" """
Rotating file handler for DECNET syslog output. 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). (default: /var/log/decnet/decnet.log).
""" """
from __future__ import annotations
import logging import logging
import logging.handlers import logging.handlers
import os import os

View File

@@ -1,4 +1,3 @@
from __future__ import annotations
""" """
RFC 5424 syslog formatter for DECNET. RFC 5424 syslog formatter for DECNET.
@@ -9,6 +8,8 @@ Facility: local0 (16)
PEN for structured data: decnet@55555 PEN for structured data: decnet@55555
""" """
from __future__ import annotations
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any from typing import Any

View File

@@ -8,10 +8,7 @@ Handles:
- IP allocation (sequential, skipping reserved addresses) - IP allocation (sequential, skipping reserved addresses)
""" """
import ipaddress
import os import os
import shutil
import socket
import subprocess import subprocess
from ipaddress import IPv4Address, IPv4Interface, IPv4Network from ipaddress import IPv4Address, IPv4Interface, IPv4Network

View File

@@ -26,6 +26,8 @@ def _load_plugins() -> None:
continue continue
importlib.import_module(f"decnet.services.{module_info.name}") importlib.import_module(f"decnet.services.{module_info.name}")
for cls in BaseService.__subclasses__(): for cls in BaseService.__subclasses__():
if not cls.__module__.startswith("decnet.services."):
continue
instance = cls() instance = cls()
_registry[instance.name] = instance _registry[instance.name] = instance
_loaded = True _loaded = True

View File

@@ -13,6 +13,10 @@ dependencies = [
"docker>=7.0", "docker>=7.0",
"pyyaml>=6.0", "pyyaml>=6.0",
"jinja2>=3.1", "jinja2>=3.1",
"pytest>=8.0",
"ruff>=0.4",
"bandit>=1.7",
"pip-audit>=2.0",
] ]
[project.scripts] [project.scripts]

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -8,8 +8,6 @@ but fake data. Logs all requests as JSON.
import json import json
import os import os
import socket
from datetime import datetime, timezone
from flask import Flask, request from flask import Flask, request
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,8 +7,6 @@ as JSON. Designed to attract automated scanners and credential stuffers.
import json import json
import os import os
import socket
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, HTTPServer from http.server import BaseHTTPRequestHandler, HTTPServer
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -5,13 +5,10 @@ Accepts any credentials, logs all commands and file requests,
forwards events as JSON to LOG_TARGET if set. forwards events as JSON to LOG_TARGET if set.
""" """
import json
import os import os
import socket
import sys 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.protocols.ftp import FTP, FTPFactory
from twisted.python import log as twisted_log from twisted.python import log as twisted_log
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,8 +7,6 @@ and responds with configurable pages. Forwards events as JSON to LOG_TARGET if s
import json import json
import os import os
import socket
from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from flask import Flask, request, send_from_directory from flask import Flask, request, send_from_directory

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,10 +6,7 @@ AUTHENTICATE), then returns a NO response. Logs all commands as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "mailserver") NODE_NAME = os.environ.get("NODE_NAME", "mailserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -8,8 +8,6 @@ Responds to recon endpoints (/version, /api, /apis, /api/v1/namespaces,
import json import json
import os import os
import socket
from datetime import datetime, timezone
from flask import Flask, request from flask import Flask, request
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,10 +6,7 @@ invalidCredentials error. Logs all interactions as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "ldapserver") NODE_NAME = os.environ.get("NODE_NAME", "ldapserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,11 +7,8 @@ Logs every packet with source IP and decoded query name where possible.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "lan-host") NODE_NAME = os.environ.get("NODE_NAME", "lan-host")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,11 +7,8 @@ received messages as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "mongodb") NODE_NAME = os.environ.get("NODE_NAME", "mongodb")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,11 +7,8 @@ interactions as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "mqtt-broker") NODE_NAME = os.environ.get("NODE_NAME", "mqtt-broker")
@@ -48,11 +45,13 @@ def _parse_connect(payload: bytes):
# Protocol level (1 byte) # Protocol level (1 byte)
if pos >= len(payload): if pos >= len(payload):
return {}, pos return {}, pos
_proto_level = payload[pos]; pos += 1 _proto_level = payload[pos]
pos += 1
# Connect flags (1 byte) # Connect flags (1 byte)
if pos >= len(payload): if pos >= len(payload):
return {}, pos return {}, pos
flags = payload[pos]; pos += 1 flags = payload[pos]
pos += 1
# Keep alive (2 bytes) # Keep alive (2 bytes)
pos += 2 pos += 2
# Client ID # Client ID

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,11 +6,8 @@ a login failed error. Logs auth attempts as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "dbserver") NODE_NAME = os.environ.get("NODE_NAME", "dbserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,11 +7,8 @@ attempts as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "dbserver") NODE_NAME = os.environ.get("NODE_NAME", "dbserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,10 +7,7 @@ to LOG_TARGET if set.
""" """
import asyncio import asyncio
import json
import os import os
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "mailserver") NODE_NAME = os.environ.get("NODE_NAME", "mailserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,11 +7,8 @@ returns an error. Logs all interactions as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "pgserver") NODE_NAME = os.environ.get("NODE_NAME", "pgserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,11 +6,8 @@ in the initial RDP negotiation request. Forwards events as JSON to
LOG_TARGET if set. LOG_TARGET if set.
""" """
import json
import os import os
import socket
import sys import sys
from datetime import datetime, timezone
from twisted.internet import protocol, reactor from twisted.internet import protocol, reactor
from twisted.python import log as twisted_log from twisted.python import log as twisted_log

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,10 +6,7 @@ KEYS, and arbitrary commands. Logs every command and argument as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "cache-server") NODE_NAME = os.environ.get("NODE_NAME", "cache-server")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,11 +6,8 @@ Authorization header and call metadata, then responds with 401 Unauthorized.
""" """
import asyncio import asyncio
import json
import os import os
import re import re
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "pbx") NODE_NAME = os.environ.get("NODE_NAME", "pbx")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -4,10 +4,7 @@ Minimal SMB server using Impacket's SimpleSMBServer.
Logs all connection attempts, optionally forwarding them as JSON to LOG_TARGET. Logs all connection attempts, optionally forwarding them as JSON to LOG_TARGET.
""" """
import json
import os import os
import socket
from datetime import datetime, timezone
from impacket import smbserver from impacket import smbserver
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -5,10 +5,7 @@ Logs EHLO/AUTH/MAIL FROM/RCPT TO attempts as JSON, then denies auth.
""" """
import asyncio import asyncio
import json
import os import os
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "mailserver") NODE_NAME = os.environ.get("NODE_NAME", "mailserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,11 +7,8 @@ Logs all requests as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "switch") 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): def _parse_snmp(data: bytes):
"""Return (version, community, request_id, oids) or raise.""" """Return (version, community, request_id, oids) or raise."""
pos = 0 pos = 0
assert data[pos] == 0x30; pos += 1 assert data[pos] == 0x30
pos += 1
_, pos = _read_ber_length(data, pos) _, pos = _read_ber_length(data, pos)
# version # version
assert data[pos] == 0x02; pos += 1 assert data[pos] == 0x02
pos += 1
v_len, pos = _read_ber_length(data, pos) 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 # community
assert data[pos] == 0x04; pos += 1 assert data[pos] == 0x04
pos += 1
c_len, pos = _read_ber_length(data, pos) 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 (0xa0 = GetRequest, 0xa1 = GetNextRequest)
pdu_type = data[pos]; pos += 1 pos += 1
_, pos = _read_ber_length(data, pos) _, pos = _read_ber_length(data, pos)
# request-id # request-id
assert data[pos] == 0x02; pos += 1 assert data[pos] == 0x02
pos += 1
r_len, pos = _read_ber_length(data, pos) 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 pos += 4 # skip error-status and error-index
# varbind list # varbind list
assert data[pos] == 0x30; pos += 1 assert data[pos] == 0x30
pos += 1
vbl_len, pos = _read_ber_length(data, pos) vbl_len, pos = _read_ber_length(data, pos)
end = pos + vbl_len end = pos + vbl_len
oids = [] oids = []
while pos < end: while pos < end:
assert data[pos] == 0x30; pos += 1 assert data[pos] == 0x30
pos += 1
vb_len, pos = _read_ber_length(data, pos) 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_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) oids.append(oid)
pos += vb_len - oid_len - 2 # skip value pos += vb_len - oid_len - 2 # skip value
return version, community, request_id, oids return version, community, request_id, oids

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -6,11 +6,8 @@ then responds with an error packet. Logs all requests as JSON.
""" """
import asyncio import asyncio
import json
import os import os
import socket
import struct import struct
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "tftpserver") NODE_NAME = os.environ.get("NODE_NAME", "tftpserver")

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from __future__ import annotations
""" """
Shared RFC 5424 syslog helper for DECNET service templates. Shared RFC 5424 syslog helper for DECNET service templates.

View File

@@ -7,10 +7,7 @@ failed". Logs the raw response for offline cracking.
""" """
import asyncio import asyncio
import json
import os import os
import socket
from datetime import datetime, timezone
from decnet_logging import syslog_line, write_syslog_file, forward_syslog from decnet_logging import syslog_line, write_syslog_file, forward_syslog
NODE_NAME = os.environ.get("NODE_NAME", "desktop") NODE_NAME = os.environ.get("NODE_NAME", "desktop")

View File

@@ -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 &rsaquo; 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

61
tests/test_build.py Normal file
View File

@@ -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)