merge: osm-fix (#16831)

* perf: Update Dockerfile with new base image tag

* perf: update uv lock

* perf: update nmap

* perf: remove nmap

* perf: remove nmap

* perf: remove unused tools

* perf: update uv lock

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: ibuler <ibuler@qq.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ibuler <17382885+ibuler@users.noreply.github.com>
This commit is contained in:
fit2bot
2026-05-13 12:16:46 +08:00
committed by GitHub
parent 59b44094a1
commit f58ff547ef
5 changed files with 108 additions and 50 deletions

View File

@@ -1,4 +1,4 @@
FROM jumpserver/core-base:20260507_060403 AS stage-build
FROM jumpserver/core-base:20260511_022944 AS stage-build
ARG VERSION
@@ -36,7 +36,6 @@ ARG TOOLS=" \
postgresql-client \
openssh-client \
sshpass \
nmap \
bubblewrap"
ARG APT_MIRROR=http://deb.debian.org

View File

@@ -7,13 +7,9 @@ COPY --from=build-xpack /opt/xpack /opt/jumpserver/apps/xpack
ARG TOOLS=" \
g++ \
curl \
iputils-ping \
netcat-openbsd \
nmap \
telnet \
wget \
poppler-utils"
telnet"
RUN set -ex \
&& apt-get update \

View File

@@ -1,39 +1,91 @@
import argparse
import asyncio
import socket
import time
import nmap
from common.utils.timezone import local_now_display
from settings.utils import generate_ips
_SCANNER_VERSION = '1.0'
def get_nmap_result(nm, ip, ports, timeout):
results = []
nm.scan(ip, ports=ports, timeout=timeout)
tcp_port = nm[ip].get('tcp', {})
udp_port = nm[ip].get('udp', {})
results.append(f'PORT\tSTATE\tSERVICE')
for port, info in tcp_port.items():
results.append(f"{port}\t{info.get('state', 'unknown')}\t{info.get('name', 'unknown')}")
for port, info in udp_port.items():
results.append(f"{port}\t{info.get('state', 'unknown')}\t{info.get('name', 'unknown')}")
return results
# Fallback service name table for platforms where getservbyport is unavailable
_KNOWN_SERVICES = {
21: 'ftp', 22: 'ssh', 23: 'telnet', 25: 'smtp', 53: 'domain',
80: 'http', 110: 'pop3', 135: 'msrpc', 139: 'netbios-ssn',
143: 'imap', 443: 'https', 445: 'microsoft-ds', 587: 'submission',
993: 'imaps', 995: 'pop3s', 1433: 'ms-sql-s', 1521: 'oracle',
3306: 'mysql', 3389: 'ms-wbt-server', 5432: 'postgresql',
5900: 'vnc', 6379: 'redis', 8080: 'http-proxy', 8443: 'https-alt',
27017: 'mongodb',
}
async def once_nmap(nm, ip, ports, timeout, display):
def _parse_ports(ports_str):
"""Parse '22,80,443' or '22-100' or a mix into a sorted list of ints."""
if not ports_str:
# mirror nmap's default: the 1000 most common ports; use 1-1024 as a
# reasonable approximation without requiring root privileges.
return list(range(1, 1025))
ports = []
for part in ports_str.split(','):
part = part.strip()
if '-' in part:
start, end = part.split('-', 1)
ports.extend(range(int(start), int(end) + 1))
else:
ports.append(int(part))
return sorted(set(ports))
def _service_name(port: int, proto: str = 'tcp') -> str:
try:
return socket.getservbyport(port, proto)
except OSError:
return _KNOWN_SERVICES.get(port, 'unknown')
async def _scan_tcp_port(ip: str, port: int, timeout: float) -> str:
"""Return 'open' or 'closed' for a single TCP port."""
try:
_, writer = await asyncio.wait_for(
asyncio.open_connection(ip, port), timeout=timeout
)
writer.close()
try:
await writer.wait_closed()
except Exception:
pass
return 'open'
except (asyncio.TimeoutError, ConnectionRefusedError, OSError):
return 'closed'
async def get_nmap_result(ip: str, ports_str, timeout) -> list[str]:
"""Scan *ip* and return formatted result lines (PORT / STATE / SERVICE)."""
timeout = float(timeout) if timeout else 1.0
ports = _parse_ports(ports_str)
states = await asyncio.gather(
*[_scan_tcp_port(ip, p, timeout) for p in ports]
)
lines = ['PORT\tSTATE\tSERVICE']
for port, state in zip(ports, states):
if state == 'open':
lines.append(f'{port}/tcp\t{state}\t{_service_name(port)}')
return lines
async def once_nmap(ip: str, ports_str, timeout, display) -> bool:
await display(f'Starting Nmap at {local_now_display()} for {ip}')
try:
is_ok = True
loop = asyncio.get_running_loop()
results = await loop.run_in_executor(None, get_nmap_result, nm, ip, ports, timeout)
for result in results:
await display(result)
except KeyError:
is_ok = False
await display(f'Host seems down.')
results = await get_nmap_result(ip, ports_str, timeout)
for line in results:
await display(line)
is_ok = len(results) > 1 # at least one open port found
except Exception as err:
is_ok = False
await display(f"Error: %s" % err)
await display(f'Error: {err}')
return is_ok
@@ -44,14 +96,34 @@ async def verbose_nmap(dest_ips, dest_ports=None, timeout=None, display=None):
ips = generate_ips(dest_ips)
dest_port = ','.join(list(dest_ports)) if dest_ports else None
nm = nmap.PortScanner()
success_num, start_time = 0, time.time()
nmap_version = '.'.join(map(lambda x: str(x), nm.nmap_version()))
await display(f'[Summary] Nmap (v{nmap_version}): {len(ips)} addresses were scanned')
await display(f'[Summary] Nmap (v{_SCANNER_VERSION}): {len(ips)} addresses were scanned')
for ip in ips:
ok = await once_nmap(nm, str(ip), dest_port, timeout, display)
ok = await once_nmap(str(ip), dest_port, timeout, display)
if ok:
success_num += 1
await display()
await display(f'[Done] Nmap: {len(ips)} IP addresses ({success_num} hosts up) '
f'scanned in {round(time.time() - start_time, 2)} seconds')
await display(
f'[Done] Nmap: {len(ips)} IP addresses ({success_num} hosts up) '
f'scanned in {round(time.time() - start_time, 2)} seconds'
)
async def _main():
parser = argparse.ArgumentParser(description='Pure-Python TCP port scanner')
parser.add_argument('targets', nargs='+', help='IP / CIDR, e.g. 192.168.1.1 or 10.0.0.0/24')
parser.add_argument('-p', '--ports', default=None,
help='Ports to scan, e.g. 22,80,443 or 22-1024 (default: 1-1024)')
parser.add_argument('--timeout', type=float, default=1.0,
help='Per-port connect timeout in seconds (default: 1.0)')
args = parser.parse_args()
async def display(msg=''):
print(msg)
dest_ports = args.ports.split(',') if args.ports else None
await verbose_nmap(args.targets, dest_ports=dest_ports, timeout=args.timeout, display=display)
if __name__ == '__main__':
asyncio.run(_main())

View File

@@ -44,7 +44,7 @@ dependencies = [
'six==1.17.0',
'sshtunnel==0.4.0',
'sshpubkeys==3.3.1',
'urllib3==2.6.3',
'urllib3==2.7.0',
'uritemplate==4.1.1',
'vine==5.1.0',
'werkzeug==3.0.6',
@@ -68,7 +68,6 @@ dependencies = [
'geoip2==4.8.0',
'ipip-ipdb==1.6.1',
'pywinrm==0.4.3',
'python-nmap==0.7.1',
'django==5.2.13',
'django-bootstrap3==23.4',
'django-filter==24.3',

16
uv.lock generated
View File

@@ -2214,7 +2214,6 @@ dependencies = [
{ name = "python-daemon" },
{ name = "python-dateutil" },
{ name = "python-ldap" },
{ name = "python-nmap" },
{ name = "python-redis-lock" },
{ name = "python3-saml" },
{ name = "pytz" },
@@ -2399,7 +2398,6 @@ requires-dist = [
{ name = "python-daemon", specifier = "==3.0.1" },
{ name = "python-dateutil", specifier = "==2.8.2" },
{ name = "python-ldap", specifier = "==3.4.5" },
{ name = "python-nmap", specifier = "==0.7.1" },
{ name = "python-redis-lock", specifier = "==4.0.0" },
{ name = "python3-saml", specifier = "==1.16.0" },
{ name = "pytz", specifier = "==2025.2" },
@@ -2422,7 +2420,7 @@ requires-dist = [
{ name = "ua-parser", specifier = ">=0.18.0" },
{ name = "unicodecsv", specifier = "==0.14.1" },
{ name = "uritemplate", specifier = "==4.1.1" },
{ name = "urllib3", specifier = "==2.6.3" },
{ name = "urllib3", specifier = "==2.7.0" },
{ name = "user-agents", specifier = ">=2.2.0" },
{ name = "uvicorn", specifier = "==0.22.0" },
{ name = "vine", specifier = "==5.1.0" },
@@ -3695,12 +3693,6 @@ dependencies = [
]
sdist = { url = "https://files.pythonhosted.org/packages/0c/88/8d2797decc42e1c1cdd926df4f005e938b0643d0d1219c08c2b5ee8ae0c0/python_ldap-3.4.5.tar.gz", hash = "sha256:b2f6ef1c37fe2c6a5a85212efe71311ee21847766a7d45fcb711f3b270a5f79a", size = 388482, upload-time = "2025-10-10T20:00:39.06Z" }
[[package]]
name = "python-nmap"
version = "0.7.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f7/1b/8e6b3d1461331e4e8600faf099e7c62ba3c1603987dafdd558681fb8ba37/python-nmap-0.7.1.tar.gz", hash = "sha256:f75af6b91dd8e3b0c31f869db32163f62ada686945e5b7c25f84bc0f7fad3b64", size = 44366, upload-time = "2021-10-26T18:40:26.488Z" }
[[package]]
name = "python-novaclient"
version = "18.3.0"
@@ -4368,11 +4360,11 @@ wheels = [
[[package]]
name = "urllib3"
version = "2.6.3"
version = "2.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
]
[[package]]