Expose nmap_os in INI loader and update test-full.ini
- ini_loader.py: DeckySpec gains nmap_os field; load_ini parses nmap_os= (also accepts nmap-os= hyphen alias) and propagates it to amount-expanded deckies - cli.py: _build_deckies_from_ini resolves nmap_os with priority: explicit INI key > archetype default > "linux" - test-full.ini: every decky now carries nmap_os=; [windows-workstation] gains archetype= so its OS family is set correctly; decky-winbox/fileserv/ ldapdc → windows, decky-iot → embedded, decky-legacy → bsd, rest → linux - tests/test_ini_loader.py: 7 new tests covering nmap_os parsing, defaults, hyphen alias, and amount= expansion propagation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -179,6 +179,8 @@ def _build_deckies_from_ini(
|
||||
)
|
||||
raise typer.Exit(1)
|
||||
|
||||
# nmap_os priority: explicit INI key > archetype default > "linux"
|
||||
resolved_nmap_os = spec.nmap_os or (arch.nmap_os if arch else "linux")
|
||||
deckies.append(DeckyConfig(
|
||||
name=spec.name,
|
||||
ip=ip,
|
||||
@@ -189,7 +191,7 @@ def _build_deckies_from_ini(
|
||||
hostname=hostname,
|
||||
archetype=arch.slug if arch else None,
|
||||
service_config=spec.service_config,
|
||||
nmap_os=arch.nmap_os if arch else "linux",
|
||||
nmap_os=resolved_nmap_os,
|
||||
))
|
||||
return deckies
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ class DeckySpec:
|
||||
services: list[str] | None = None
|
||||
archetype: str | None = None
|
||||
service_config: dict[str, dict] = field(default_factory=dict)
|
||||
nmap_os: str | None = None # explicit OS family override (linux/windows/bsd/embedded/cisco)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -113,6 +114,7 @@ def load_ini(path: str | Path) -> IniConfig:
|
||||
svc_raw = s.get("services")
|
||||
services = [sv.strip() for sv in svc_raw.split(",")] if svc_raw else None
|
||||
archetype = s.get("archetype")
|
||||
nmap_os = s.get("nmap_os") or s.get("nmap-os") or None
|
||||
amount_raw = s.get("amount", "1")
|
||||
try:
|
||||
amount = int(amount_raw)
|
||||
@@ -123,7 +125,7 @@ def load_ini(path: str | Path) -> IniConfig:
|
||||
|
||||
if amount == 1:
|
||||
cfg.deckies.append(DeckySpec(
|
||||
name=section, ip=ip, services=services, archetype=archetype,
|
||||
name=section, ip=ip, services=services, archetype=archetype, nmap_os=nmap_os,
|
||||
))
|
||||
else:
|
||||
# Expand into N deckies; explicit ip is ignored (can't share one IP)
|
||||
@@ -138,6 +140,7 @@ def load_ini(path: str | Path) -> IniConfig:
|
||||
ip=None,
|
||||
services=services,
|
||||
archetype=archetype,
|
||||
nmap_os=nmap_os,
|
||||
))
|
||||
|
||||
# Second pass: collect per-service subsections [decky-name.service]
|
||||
|
||||
192
test-full.ini
Normal file
192
test-full.ini
Normal file
@@ -0,0 +1,192 @@
|
||||
# DECNET Full Test Config
|
||||
# Covers all 25 registered services across 10 role-themed deckies + archetype pool.
|
||||
# Distros are auto-cycled for heterogeneity (9 profiles, round-robin).
|
||||
#
|
||||
# nmap_os controls the TCP/IP stack sysctls injected into each decky's base
|
||||
# container so nmap OS detection returns the expected OS family:
|
||||
# linux → TTL 64, syn_retries 6
|
||||
# windows → TTL 128, syn_retries 2, large recv buffer
|
||||
# embedded → TTL 255, syn_retries 3
|
||||
# bsd → TTL 64, syn_retries 6
|
||||
# cisco → TTL 255, syn_retries 2
|
||||
#
|
||||
# Usage:
|
||||
# decnet deploy --config test-full.ini --dry-run
|
||||
# sudo decnet deploy --config test-full.ini --log-target 192.168.1.200:5140 \
|
||||
# --log-file /var/log/decnet/decnet.log
|
||||
|
||||
[general]
|
||||
net = 192.168.1.0/24
|
||||
gw = 192.168.1.1
|
||||
interface = wlp6s0
|
||||
#log_target = 192.168.1.200:5140
|
||||
|
||||
# ── Archetype pool: 10 Windows workstations ───────────────────────────────────
|
||||
# archetype=windows-workstation already sets nmap_os=windows automatically.
|
||||
|
||||
[windows-workstation]
|
||||
archetype = windows-workstation
|
||||
amount = 10
|
||||
|
||||
|
||||
# ── Web / Mail stack ──────────────────────────────────────────────────────────
|
||||
# Looks like an internet-facing Linux mail + web host
|
||||
|
||||
[decky-webmail]
|
||||
ip = 192.168.1.110
|
||||
services = http, smtp, imap, pop3
|
||||
nmap_os = linux
|
||||
|
||||
[decky-webmail.http]
|
||||
server_header = Apache/2.4.54 (Debian)
|
||||
response_code = 200
|
||||
fake_app = wordpress
|
||||
|
||||
[decky-webmail.smtp]
|
||||
smtp_banner = 220 mail.corp.local ESMTP Postfix (Debian/GNU)
|
||||
smtp_mta = mail.corp.local
|
||||
|
||||
|
||||
# ── File / Transfer services ──────────────────────────────────────────────────
|
||||
# Presents as a Windows/Samba file server — TTL 128 seals the illusion.
|
||||
|
||||
[decky-fileserv]
|
||||
ip = 192.168.1.111
|
||||
services = smb, ftp, tftp
|
||||
nmap_os = windows
|
||||
|
||||
[decky-fileserv.smb]
|
||||
workgroup = CORP
|
||||
server_name = FILESERV01
|
||||
os_version = Windows Server 2019
|
||||
|
||||
|
||||
# ── LAMP-style database host ──────────────────────────────────────────────────
|
||||
|
||||
[decky-dbsrv01]
|
||||
ip = 192.168.1.112
|
||||
services = mysql, redis
|
||||
nmap_os = linux
|
||||
|
||||
[decky-dbsrv01.mysql]
|
||||
mysql_version = 5.7.38-log
|
||||
mysql_banner = MySQL Community Server
|
||||
|
||||
[decky-dbsrv01.redis]
|
||||
redis_version = 6.2.7
|
||||
|
||||
|
||||
# ── Modern stack databases ────────────────────────────────────────────────────
|
||||
|
||||
[decky-dbsrv02]
|
||||
ip = 192.168.1.113
|
||||
services = postgres, mongodb, elasticsearch
|
||||
nmap_os = linux
|
||||
|
||||
[decky-dbsrv02.postgres]
|
||||
pg_version = 14.5
|
||||
|
||||
[decky-dbsrv02.mongodb]
|
||||
mongo_version = 5.0.9
|
||||
|
||||
[decky-dbsrv02.elasticsearch]
|
||||
es_version = 8.4.3
|
||||
cluster_name = prod-search
|
||||
|
||||
|
||||
# ── Windows workstation / server ──────────────────────────────────────────────
|
||||
# RDP + SMB + MSSQL — nmap_os=windows gives TTL 128 to complete the fingerprint.
|
||||
|
||||
[decky-winbox]
|
||||
ip = 192.168.1.114
|
||||
services = rdp, smb, mssql
|
||||
nmap_os = windows
|
||||
|
||||
[decky-winbox.rdp]
|
||||
os_version = Windows Server 2016
|
||||
build = 14393
|
||||
|
||||
[decky-winbox.smb]
|
||||
workgroup = CORP
|
||||
server_name = WINSRV-DC01
|
||||
os_version = Windows Server 2016
|
||||
|
||||
[decky-winbox.mssql]
|
||||
mssql_version = Microsoft SQL Server 2019
|
||||
|
||||
|
||||
# ── DevOps / Container infra ──────────────────────────────────────────────────
|
||||
|
||||
[decky-devops]
|
||||
ip = 192.168.1.115
|
||||
services = k8s, docker_api
|
||||
nmap_os = linux
|
||||
|
||||
[decky-devops.k8s]
|
||||
k8s_version = v1.26.3
|
||||
|
||||
[decky-devops.docker_api]
|
||||
docker_version = 24.0.2
|
||||
|
||||
|
||||
# ── Directory / Auth services ─────────────────────────────────────────────────
|
||||
# Active Directory DC persona — Windows TCP stack matches the LDAP/SMB services.
|
||||
|
||||
[decky-ldapdc]
|
||||
ip = 192.168.1.116
|
||||
services = ldap, ssh
|
||||
nmap_os = windows
|
||||
|
||||
[decky-ldapdc.ldap]
|
||||
base_dn = dc=corp,dc=local
|
||||
domain = corp.local
|
||||
|
||||
[decky-ldapdc.ssh]
|
||||
ssh_version = OpenSSH_8.9p1 Ubuntu-3ubuntu0.6
|
||||
kernel_version = 5.15.0-91-generic
|
||||
users = root:toor,admin:admin123,svc_backup:backup2024
|
||||
|
||||
|
||||
# ── IoT / Industrial / Network management ─────────────────────────────────────
|
||||
# TTL 255 is the embedded/network-device giveaway nmap looks for.
|
||||
|
||||
[decky-iot]
|
||||
ip = 192.168.1.117
|
||||
services = mqtt, snmp, conpot
|
||||
nmap_os = embedded
|
||||
|
||||
[decky-iot.mqtt]
|
||||
mqtt_version = Mosquitto 2.0.15
|
||||
|
||||
[decky-iot.snmp]
|
||||
snmp_community = public
|
||||
sys_descr = Linux router 5.4.0 #1 SMP x86_64
|
||||
|
||||
|
||||
# ── VoIP / Local network services ────────────────────────────────────────────
|
||||
|
||||
[decky-voip]
|
||||
ip = 192.168.1.118
|
||||
services = sip, llmnr
|
||||
nmap_os = linux
|
||||
|
||||
[decky-voip.sip]
|
||||
sip_server = Asterisk PBX 18.12.0
|
||||
sip_domain = pbx.corp.local
|
||||
|
||||
|
||||
# ── Legacy admin / remote access ─────────────────────────────────────────────
|
||||
# Old-school unpatched box — BSD stack for variety.
|
||||
|
||||
[decky-legacy]
|
||||
ip = 192.168.1.119
|
||||
services = telnet, vnc, ssh
|
||||
nmap_os = bsd
|
||||
|
||||
[decky-legacy.ssh]
|
||||
ssh_version = OpenSSH_7.4p1 Debian-10+deb9u7
|
||||
kernel_version = 4.9.0-19-amd64
|
||||
users = root:root,admin:password,pi:raspberry
|
||||
|
||||
[decky-legacy.vnc]
|
||||
vnc_version = RealVNC 6.7.2
|
||||
@@ -156,3 +156,62 @@ def test_no_custom_services_gives_empty_list(tmp_path):
|
||||
""")
|
||||
cfg = load_ini(ini_file)
|
||||
assert cfg.custom_services == []
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# nmap_os parsing
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_nmap_os_parsed_from_ini(tmp_path):
|
||||
ini_file = _write_ini(tmp_path, """
|
||||
[decky-win]
|
||||
ip = 192.168.1.101
|
||||
services = rdp, smb
|
||||
nmap_os = windows
|
||||
""")
|
||||
cfg = load_ini(ini_file)
|
||||
assert cfg.deckies[0].nmap_os == "windows"
|
||||
|
||||
|
||||
def test_nmap_os_defaults_to_none_when_absent(tmp_path):
|
||||
ini_file = _write_ini(tmp_path, """
|
||||
[decky-01]
|
||||
services = ssh
|
||||
""")
|
||||
cfg = load_ini(ini_file)
|
||||
assert cfg.deckies[0].nmap_os is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("os_family", ["linux", "windows", "bsd", "embedded", "cisco"])
|
||||
def test_nmap_os_all_families_accepted(tmp_path, os_family):
|
||||
ini_file = _write_ini(tmp_path, f"""
|
||||
[decky-01]
|
||||
services = ssh
|
||||
nmap_os = {os_family}
|
||||
""")
|
||||
cfg = load_ini(ini_file)
|
||||
assert cfg.deckies[0].nmap_os == os_family
|
||||
|
||||
|
||||
def test_nmap_os_propagates_to_amount_expanded_deckies(tmp_path):
|
||||
ini_file = _write_ini(tmp_path, """
|
||||
[corp-printers]
|
||||
services = snmp
|
||||
nmap_os = embedded
|
||||
amount = 3
|
||||
""")
|
||||
cfg = load_ini(ini_file)
|
||||
assert len(cfg.deckies) == 3
|
||||
for d in cfg.deckies:
|
||||
assert d.nmap_os == "embedded"
|
||||
|
||||
|
||||
def test_nmap_os_hyphen_alias_accepted(tmp_path):
|
||||
"""nmap-os= (hyphen) should work as an alias for nmap_os=."""
|
||||
ini_file = _write_ini(tmp_path, """
|
||||
[decky-01]
|
||||
services = ssh
|
||||
nmap-os = bsd
|
||||
""")
|
||||
cfg = load_ini(ini_file)
|
||||
assert cfg.deckies[0].nmap_os == "bsd"
|
||||
|
||||
Reference in New Issue
Block a user