From 33594a5caaf489bbc54648e079b60e97e62d6d27 Mon Sep 17 00:00:00 2001 From: Joe Testa Date: Sun, 5 Jul 2026 13:08:39 -0400 Subject: [PATCH] Updated packaging documentation & scripts. --- PACKAGING.md | 16 ++++++++++---- README.md | 6 +++--- build_snap.sh | 47 ++++++++++++++++++++++++++++++++++++------ snapcraft.yaml | 24 ++++++++++++++------- src/ssh_audit/dheat.py | 7 +++++-- 5 files changed, 78 insertions(+), 22 deletions(-) diff --git a/PACKAGING.md b/PACKAGING.md index 61accec..0018bb1 100644 --- a/PACKAGING.md +++ b/PACKAGING.md @@ -18,7 +18,7 @@ An executable can only be made on a Windows host because the PyInstaller tool (h To create package and upload to test server (hint: use API token for test.pypi.org): ``` - $ sudo apt install python3-virtualenv python3.12-venv + $ sudo apt install python3-virtualenv python3.14-venv $ make -f Makefile.pypi $ make -f Makefile.pypi uploadtest ``` @@ -48,7 +48,7 @@ To download from production server and verify: # Snap -To create the Snap package, run a fully-updated Ubuntu Server 24.04 VM. +To create the Snap package, run a fully-updated Ubuntu Server 26.04 VM. Create the Snap package with: ``` @@ -68,9 +68,17 @@ Upload the Snap with: # Docker -Ensure that the `buildx` plugin is available by following the installation instructions available at: https://docs.docker.com/engine/install/ubuntu/ +On an Ubuntu Server 26.06 VM, install dependencies with: +``` + # apt install docker.io docker-buildx +``` -Build a local image with: +Ensure that your non-root build user can access the docker daemon with by adding it to the `docker` group: +``` + # usermod -a -G docker build_user +``` + +As a non-root user, build a local image with: ``` $ make -f Makefile.docker diff --git a/README.md b/README.md index 03f81dd..2762b48 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ usage: ssh-audit.py [-h] [-4] [-6] [-b] [-c] [-d] [--socks5 host:port] [--threads N] [host] -# ssh-audit.py v3.4.0-dev, https://github.com/jtesta/ssh-audit +# ssh-audit.py v3.4.1-dev, https://github.com/jtesta/ssh-audit positional arguments: host target hostname or IPv4/IPv6 address @@ -239,9 +239,9 @@ $ snap install ssh-audit To install from Dockerhub: ``` -$ docker pull positronsecurity/ssh-audit +$ docker pull docker.io/positronsecurity/ssh-audit ``` -(Then run with: `docker run -it --rm -p 2222:2222 positronsecurity/ssh-audit 10.1.1.1`) +(Then run with: `docker run -it --rm -p 2222:2222 docker.io/positronsecurity/ssh-audit 10.1.1.1`) The status of various other platform packages can be found below (via Repology): diff --git a/build_snap.sh b/build_snap.sh index 49efa0b..0171c22 100755 --- a/build_snap.sh +++ b/build_snap.sh @@ -3,7 +3,7 @@ # # The MIT License (MIT) # -# Copyright (C) 2021-2024 Joe Testa (jtesta@positronsecurity.com) +# Copyright (C) 2021-2026 Joe Testa (jtesta@positronsecurity.com) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -32,19 +32,23 @@ # Pre-requisites +echo "Installing pre-requisites..." sudo apt install -y make sudo snap install snapcraft --classic sudo snap install review-tools lxd # Initialize LXD. +echo -e "\nInitializing LXD..." sudo lxd init --auto -# Reset the filesystem from any previous runs. -rm -rf parts/ prime/ snap/ stage/ build/ dist/ src/*.egg-info/ ssh-audit*.snap +# Reset local files that may have been changed through a previous run. +echo "Resetting local files..." +rm -f ssh-audit*.snap git checkout snapcraft.yaml 2> /dev/null git checkout src/ssh_audit/globals.py 2> /dev/null # Add the built-in manual page. +echo "Processing man page..." ./add_builtin_man_page.sh # Get the version from the globals.py file. @@ -67,10 +71,41 @@ if [[ ! $snap_version =~ ^[0-9]\.[0-9]\.[0-9]\-[0-9]$ ]]; then exit 1 fi -# Append the version field to the end of the file. Not pretty, but it works. -echo -e "\nversion: '${snap_version}'" >> snapcraft.yaml +# Set the version field. +sed -i "s/version: \"\"/version: \"${snap_version}\"/" snapcraft.yaml + +# Ensure we set the version field correctly. +grep "version: \"${snap_version}\"" snapcraft.yaml > /dev/null 2> /dev/null +if [[ $? != 0 ]]; then + echo "Failed to set version field in snapcraft.yaml." + exit 1 +fi # Set the SNAP_PACKAGE variable to True so that file permission errors give more user-friendly sed -i 's/SNAP_PACKAGE = False/SNAP_PACKAGE = True/' src/ssh_audit/globals.py -snapcraft --use-lxd && echo -e "\nDone.\n" +# Clean any previous builds. +echo "Running 'snapcraft clean'..." +snapcraft clean + +# Build the snap file. +echo "Running 'snapcraft pack --use-lxd'..." +snapcraft pack --use-lxd +if [[ $? != 0 ]]; then + echo -e "\nPack process failed: $?" + exit 1 +else + echo -e "Pack process succeeded." +fi + +# Run the linter on it. +echo "Running 'snapcraft lint' on *.snap..." +snapcraft lint --quiet *.snap +if [[ $? != 0 ]]; then + echo -e "\nLint process failed: $?" + exit 1 +else + echo -e "Lint process succeeded." +fi + +echo -e "\nDone!\n" diff --git a/snapcraft.yaml b/snapcraft.yaml index da0f5ec..651a366 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,23 +1,33 @@ name: ssh-audit -# 'version' field will be automatically added by build_snap.sh. -license: 'MIT' +title: ssh-audit summary: ssh-audit +version: "" # Set at run-time by build_snap.sh. +license: 'MIT' description: | SSH server and client security configuration auditor. Official repository: - -base: core22 +contact: + - https://github.com/jtesta/ssh-audit/issues +issues: + - https://github.com/jtesta/ssh-audit/issues +source-code: + - https://github.com/jtesta/ssh-audit +website: + - https://github.com/jtesta/ssh-audit +base: core26 grade: stable confinement: strict -architectures: - - build-on: [amd64] +platforms: + all: + build-on: [amd64] build-for: [all] apps: ssh-audit: command: bin/ssh-audit - plugs: [network,network-bind,home] + plugs: [network, network-bind, home] parts: ssh-audit: plugin: python + stage-packages: [python3.14, python3.14-venv] source: . diff --git a/src/ssh_audit/dheat.py b/src/ssh_audit/dheat.py index 616e69f..82cf060 100644 --- a/src/ssh_audit/dheat.py +++ b/src/ssh_audit/dheat.py @@ -326,6 +326,9 @@ class DHEat: del socket_dict[s] + # Check if this platform has socket.AF_UNIX. Windows does not. + has_af_unix = hasattr(socket, 'AF_UNIX') + # Resolve the target into an IP address out.d("Resolving target %s..." % aconf.host) target_address_family, target_ip_address = DHEat._resolve_hostname(aconf.host, aconf.ip_version_preference) @@ -442,7 +445,7 @@ class DHEat: s.setblocking(False) # out.d("Creating socket (%u of %u already exist)..." % (len(socket_dict), concurrent_sockets), write_now=True) - if target_address_family == socket.AF_UNIX: + if has_af_unix and (target_address_family == socket.AF_UNIX): ret = s.connect_ex(target_ip_address) else: ret = s.connect_ex((target_ip_address, aconf.port)) @@ -912,7 +915,7 @@ class DHEat: now = time.time() if (now - last_update) >= 1.0: seconds_running = now - self.start_timer - print(f"{self.WHITEB}{spinner[spinner_index]}{self.CLEAR} TCP SYNs/sec: {self.BLUEB}{int(self.num_attempted_tcp_connections / seconds_running):,d}{self.CLEAR}; Compl. conns/sec: {self.BLUEB}{int(self.num_successful_tcp_connections / seconds_running):,d}{self.CLEAR}; Bytes sent/sec: {self.BLUEB}{DHEat.add_byte_units(self.num_bytes_written / seconds_running)}{self.CLEAR}; DH kex/sec: {self.PURPLEB}{self.num_successful_dh_kex / seconds_running:,.1f}{self.CLEAR} \r", end="") + print(f"{self.WHITEB}{spinner[spinner_index]}{self.CLEAR} TCP SYNs/sec: {self.BLUEB}{int(self.num_attempted_tcp_connections / seconds_running):,d}{self.CLEAR}; Compl. conns/sec: {self.BLUEB}{self.num_successful_tcp_connections / seconds_running:,.1f}{self.CLEAR}; Bytes sent/sec: {self.BLUEB}{DHEat.add_byte_units(self.num_bytes_written / seconds_running)}{self.CLEAR}; DH kex/sec: {self.PURPLEB}{self.num_successful_dh_kex / seconds_running:,.1f}{self.CLEAR} \r", end="") last_update = now spinner_index = (spinner_index + 1) % 4