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)
|
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(
|
deckies.append(DeckyConfig(
|
||||||
name=spec.name,
|
name=spec.name,
|
||||||
ip=ip,
|
ip=ip,
|
||||||
@@ -189,7 +191,7 @@ def _build_deckies_from_ini(
|
|||||||
hostname=hostname,
|
hostname=hostname,
|
||||||
archetype=arch.slug if arch else None,
|
archetype=arch.slug if arch else None,
|
||||||
service_config=spec.service_config,
|
service_config=spec.service_config,
|
||||||
nmap_os=arch.nmap_os if arch else "linux",
|
nmap_os=resolved_nmap_os,
|
||||||
))
|
))
|
||||||
return deckies
|
return deckies
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class DeckySpec:
|
|||||||
services: list[str] | None = None
|
services: list[str] | None = None
|
||||||
archetype: str | None = None
|
archetype: str | None = None
|
||||||
service_config: dict[str, dict] = field(default_factory=dict)
|
service_config: dict[str, dict] = field(default_factory=dict)
|
||||||
|
nmap_os: str | None = None # explicit OS family override (linux/windows/bsd/embedded/cisco)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -113,6 +114,7 @@ def load_ini(path: str | Path) -> IniConfig:
|
|||||||
svc_raw = s.get("services")
|
svc_raw = s.get("services")
|
||||||
services = [sv.strip() for sv in svc_raw.split(",")] if svc_raw else None
|
services = [sv.strip() for sv in svc_raw.split(",")] if svc_raw else None
|
||||||
archetype = s.get("archetype")
|
archetype = s.get("archetype")
|
||||||
|
nmap_os = s.get("nmap_os") or s.get("nmap-os") or None
|
||||||
amount_raw = s.get("amount", "1")
|
amount_raw = s.get("amount", "1")
|
||||||
try:
|
try:
|
||||||
amount = int(amount_raw)
|
amount = int(amount_raw)
|
||||||
@@ -123,7 +125,7 @@ def load_ini(path: str | Path) -> IniConfig:
|
|||||||
|
|
||||||
if amount == 1:
|
if amount == 1:
|
||||||
cfg.deckies.append(DeckySpec(
|
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:
|
else:
|
||||||
# Expand into N deckies; explicit ip is ignored (can't share one IP)
|
# 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,
|
ip=None,
|
||||||
services=services,
|
services=services,
|
||||||
archetype=archetype,
|
archetype=archetype,
|
||||||
|
nmap_os=nmap_os,
|
||||||
))
|
))
|
||||||
|
|
||||||
# Second pass: collect per-service subsections [decky-name.service]
|
# 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)
|
cfg = load_ini(ini_file)
|
||||||
assert cfg.custom_services == []
|
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