mirror of
https://github.com/jumpserver/jumpserver.git
synced 2026-07-01 22:49:06 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
16
uv.lock
generated
@@ -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]]
|
||||
|
||||
Reference in New Issue
Block a user