Skip to content

Commit 0585205

Browse files
authored
Extend nmap capability in discovery node (#1090)
1 parent ab9d403 commit 0585205

File tree

7 files changed

+110
-17
lines changed

7 files changed

+110
-17
lines changed

misc/discoverynode/README.md

+13-1
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,21 @@ journalctl -f -xeu udmi_discovery
159159

160160
```
161161
162+
## Integration Test
163+
164+
```
165+
misc/discoverynode/testing/integration/integration_test.sh
166+
```
167+
168+
This will load a docker container, however with a dedicated testing wrapper
169+
`misc/discoverynode/src/tests/test_integration.py`
170+
as the entry point.
171+
172+
This can be used to validate that discovery actually discovers, message formats, etc.
173+
162174
## E2E Test
163175
164-
TODO
176+
Refer to Github CI test.
165177
166178
## Test Suite
167179

misc/discoverynode/src/tests/test_integration.py

+46-9
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ def test_bacnet_integration():
2323
test_config["mqtt"] = dict(device_id="THUNDERBIRD-2")
2424
test_config["bacnet"] = dict(ip=None, port=None, interface=None)
2525
test_config["udmi"] = {"discovery": dict(ipv4=False,vendor=False,ether=False,bacnet=True)}
26-
2726
# Container for storing all discovery messages
2827
messages = []
2928

@@ -53,15 +52,53 @@ def test_bacnet_integration():
5352
print(message.to_json())
5453
print("----")
5554

56-
return
57-
assert False, "failing so messages are printed to terminal"
58-
59-
expected_ethmacs = set(d["ether"] for d in docker_config.values())
60-
seen_ethmac_toplevel = set(m.families["ether"].addr for m in messages if "ether" in m.families)
6155

62-
expected_bacnet_ids = set(str(d["bacnet_id"]) for d in docker_config.values())
56+
57+
expected_bacnet_ids = set(["1"])
6358
seen_bacnet_ids_toplevel = set(m.addr for m in messages if m.family == "bacnet")
6459

65-
# subset because passive scan will find the gateway and device itself
66-
assert seen_ethmac_toplevel == expected_ethmacs + 2
6760
assert expected_bacnet_ids == seen_bacnet_ids_toplevel
61+
62+
63+
def test_nmap():
64+
65+
# This is the "config.json" which is passed to `main.py` typically
66+
test_config = collections.defaultdict()
67+
test_config["mqtt"] = dict(device_id="THUNDERBIRD-2")
68+
test_config["mqtt"] = dict(device_id="THUNDERBIRD-2")
69+
test_config["bacnet"] = dict(ip=None, port=None, interface=None)
70+
test_config["udmi"] = {"discovery": dict(ipv4=False,vendor=False,ether=True,bacnet=False)}
71+
test_config["nmap"] = dict(targets=["192.168.12.1"])
72+
73+
# Container for storing all discovery messages
74+
messages = []
75+
76+
with (
77+
mock.patch.object(
78+
udmi.core.UDMICore, "publish_discovery", new=messages.append
79+
) as published_discovery,
80+
):
81+
mock_mqtt_client = mock.MagicMock()
82+
udmi_client = udmi.core.UDMICore(publisher=mock_mqtt_client, topic_prefix="notneeded", config=test_config)
83+
84+
# Start discovery
85+
udmi_client.config_handler(
86+
json.dumps({
87+
"timestamp": timestamp_now(),
88+
"discovery": {
89+
"families": {
90+
"ether": {"generation": timestamp_now()}
91+
}
92+
},
93+
})
94+
)
95+
96+
time.sleep(30)
97+
98+
print(len(messages))
99+
for message in messages:
100+
print(message.to_json())
101+
print("----")
102+
103+
104+
assert False, "failing so messages are printed to terminal"

misc/discoverynode/src/udmi/discovery/nmap.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import udmi.discovery.utils.nmap as nmap
88
import udmi.schema.discovery_event
99
import udmi.schema.state
10+
import dataclasses
1011

1112

1213
class NmapBannerScan(discovery.DiscoveryController):
@@ -42,7 +43,10 @@ def nmap_runner(self):
4243
"/usr/bin/nmap",
4344
"--script",
4445
"banner",
45-
"127.0.0.1",
46+
"-p-",
47+
"-T4",
48+
"-A",
49+
self.target_ips[0],
4650
"-oX",
4751
OUTPUT_FILE,
4852
"--stats-every",
@@ -71,8 +75,8 @@ def nmap_runner(self):
7175
generation=self.generation,
7276
family=self.family,
7377
addr=host.ip,
74-
families={
75-
"port": {p.port_number: {"banner": p.banner} for p in host.ports}
78+
refs={
79+
f"{p.port_number}": {"adjunct": dataclasses.asdict(p)} for p in host.ports
7680
},
7781
)
78-
self.publish(event.to_json())
82+
self.publish(event)

misc/discoverynode/src/udmi/discovery/utils/nmap.py

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class NmapPort:
1717
port_number: int
1818
sort_index: int = dataclasses.field(init=False, repr=False)
1919
service: str | None = None
20+
version: str | None = None
2021
state: str | None = None
2122
protocol: str | None = None
2223
product: str | None = None
@@ -66,6 +67,7 @@ def results_reader(nmap_file: str) -> Generator[NmapHost, None, None]:
6667
port.product = service.attrib.get('product')
6768
if service.attrib['method'] == 'probed':
6869
port.service = service.attrib['name']
70+
port.version = service.attrib.get('version')
6971

7072
host.ports.append(port)
7173

misc/discoverynode/testing/docker/bacnet_device/Dockerfile

+5-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ COPY requirements.txt /requirements.txt
1010
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt
1111

1212
# Copy the virtualenv into a distroless image
13-
FROM gcr.io/distroless/python3-debian12:debug
13+
FROM debian:12
14+
RUN apt-get update && \
15+
apt-get install --no-install-suggests --no-install-recommends --yes python3 coreutils netcat-openbsd ssh openssh-server
1416
COPY --from=build-venv /venv /venv
1517
COPY . /app
1618
WORKDIR /app
17-
ENTRYPOINT [ "/venv/bin/python3", "bacnet_server.py" ]
19+
ENTRYPOINT ["/app/entrypoint.sh"]
20+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/bash -e
2+
3+
echo Starting FTP 21514 and open default 20,21
4+
nc -nvlt -p 20 &
5+
nc -nvlt -p 21 &
6+
(while true; do echo -e "220 ProFTPD 1.3.5e Server (Debian) $(hostname)" | nc -l -w 1 21514; done) &
7+
8+
echo Starting SMTP 1256 and open default 25, 465, 587
9+
nc -nvlt -p 25 &
10+
nc -nvlt -p 465 &
11+
nc -nvlt -p 587 &
12+
(while true; do echo -e "220 $(hostname) ESMTP Postfix (Ubuntu)" | nc -l -w 1 1256; done) &
13+
14+
echo Starting IMAP 5361 and open default ports 143, 993
15+
nc -nvlt -p 143 &
16+
nc -nvlt -p 993 &
17+
(while true; do echo -e "* OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS AUTH=PLAIN] Dovecot (Ubuntu) ready.\r\n" \
18+
| nc -l -w 1 5361; done) &
19+
20+
echo Starting POP3 23451 and open default 110, 995
21+
nc -nvlt -p 110 &
22+
nc -nvlt -p 995 &
23+
(while true; do echo -ne "+OK POP3 Server ready\r\n" | nc -l -w 1 23451; done) &
24+
25+
echo starting TFTP UDP 69
26+
(while true; do echo -ne "\0\x05\0\0\x07\0" | nc -u -l -w 1 69; done) &
27+
28+
29+
mkdir /var/run/sshd
30+
chmod 0755 /var/run/sshd
31+
/usr/sbin/sshd
32+
33+
/venv/bin/python3 bacnet_server.py

misc/discoverynode/testing/docker/discovery_node/Dockerfile

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ COPY requirements.txt /requirements.txt
1010
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt
1111

1212
# Copy the virtualenv into a distroless image
13-
FROM gcr.io/distroless/python3-debian12:debug
13+
FROM debian:12-slim
14+
RUN apt-get update && \
15+
apt-get install --no-install-suggests --no-install-recommends --yes python3 coreutils nmap
1416
COPY --from=build-venv /venv /venv
1517
COPY . /app
1618
WORKDIR /app

0 commit comments

Comments
 (0)