mirror of
				https://github.com/jtesta/ssh-audit.git
				synced 2025-10-30 21:15:27 +01:00 
			
		
		
		
	Now prints a more user-friendly error message when installed as a Snap package and permission errors are encountered. Updated the Snap build process as well.
This commit is contained in:
		| @@ -1,6 +0,0 @@ | |||||||
| all: |  | ||||||
| 	echo -e "\n\nDid you remember to bump the version number in snapcraft.yaml?\n\n" |  | ||||||
| 	snapcraft --use-lxd |  | ||||||
|  |  | ||||||
| clean: |  | ||||||
| 	rm -rf parts/ prime/ snap/ stage/ build/ dist/ src/*.egg-info/ ssh-audit*.snap |  | ||||||
							
								
								
									
										19
									
								
								PACKAGING.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								PACKAGING.md
									
									
									
									
									
								
							| @@ -51,27 +51,14 @@ To download from production server and verify: | |||||||
|     $ pip3 install ssh-audit |     $ pip3 install ssh-audit | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  |  | ||||||
| # Snap | # Snap | ||||||
|  |  | ||||||
| To create the snap package, run a fully-updated Ubuntu Server 20.04 VM. | To create the snap package, run a fully-updated Ubuntu Server 20.04 VM. | ||||||
|  |  | ||||||
| Install pre-requisites with: | Create the snap package with: | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|     $ sudo apt install make snapcraft |     $ ./build_snap.sh | ||||||
|     $ sudo snap install review-tools lxd |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Initialize LXD: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
|     $ sudo lxd init --auto |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| Bump the version number in snapcraft.yaml.  Then run: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
|     $ make -f Makefile.snap |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Upload the snap with: | Upload the snap with: | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								build_snap.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										72
									
								
								build_snap.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | #!/usr/bin/env bash | ||||||
|  |  | ||||||
|  | # | ||||||
|  | #   The MIT License (MIT) | ||||||
|  | # | ||||||
|  | #   Copyright (C) 2021 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 | ||||||
|  | #   in the Software without restriction, including without limitation the rights | ||||||
|  | #   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | #   copies of the Software, and to permit persons to whom the Software is | ||||||
|  | #   furnished to do so, subject to the following conditions: | ||||||
|  | # | ||||||
|  | #   The above copyright notice and this permission notice shall be included in | ||||||
|  | #   all copies or substantial portions of the Software. | ||||||
|  | # | ||||||
|  | #   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | #   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | #   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | #   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | #   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | #   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|  | #   THE SOFTWARE. | ||||||
|  | # | ||||||
|  |  | ||||||
|  | ################################################################################ | ||||||
|  | # build_snap.sh | ||||||
|  | # | ||||||
|  | # Builds a Snap package. | ||||||
|  | ################################################################################ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | # Pre-requisites | ||||||
|  | sudo apt install -y make snapcraft | ||||||
|  | sudo snap install review-tools lxd 2> /dev/null | ||||||
|  |  | ||||||
|  | # Initialize 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 | ||||||
|  | git checkout snapcraft.yaml 2> /dev/null | ||||||
|  | git checkout src/ssh_audit/globals.py 2> /dev/null | ||||||
|  |  | ||||||
|  | # Get the version from the globals.py file. | ||||||
|  | version=$(grep VERSION src/ssh_audit/globals.py | awk 'BEGIN {FS="="} ; {print $2}' | tr -d '[:space:]') | ||||||
|  |  | ||||||
|  | # Strip the quotes around the version (along with the initial 'v' character) and append "-1" to make the default Snap version (i.e.: 'v2.5.0' => '2.5.0-1') | ||||||
|  | default_snap_version="${version:2:-1}-1" | ||||||
|  | echo -e -n "\nEnter Snap package version [default: ${default_snap_version}]: " | ||||||
|  | read -r snap_version | ||||||
|  |  | ||||||
|  | # If no version was specified, use the default version. | ||||||
|  | if [[ $snap_version == '' ]]; then | ||||||
|  |     snap_version=$default_snap_version | ||||||
|  |     echo -e "Using default snap version: ${snap_version}\n" | ||||||
|  | fi | ||||||
|  |  | ||||||
|  | # Ensure that the snap version fits the format of X.X.X-X. | ||||||
|  | if [[ ! $snap_version =~ ^[0-9]\.[0-9]\.[0-9]\-[0-9]$ ]]; then | ||||||
|  |    echo "Error: version string does not match format X.X.X-X!" | ||||||
|  |    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 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" | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| name: ssh-audit | name: ssh-audit | ||||||
| version: '2.5.0-1' | # 'version' field will be automatically added by build_snap.sh. | ||||||
| license: 'MIT' | license: 'MIT' | ||||||
| summary: ssh-audit | summary: ssh-audit | ||||||
| description: | | description: | | ||||||
| @@ -12,10 +12,9 @@ confinement: strict | |||||||
| apps: | apps: | ||||||
|   ssh-audit: |   ssh-audit: | ||||||
|     command: bin/ssh-audit |     command: bin/ssh-audit | ||||||
|     plugs: [network,network-bind] |     plugs: [network,network-bind,home] | ||||||
|  |  | ||||||
| parts: | parts: | ||||||
|   ssh-audit: |   ssh-audit: | ||||||
|     plugin: python |     plugin: python | ||||||
|     # python-version: python3 |  | ||||||
|     source: . |     source: . | ||||||
|   | |||||||
| @@ -21,7 +21,20 @@ | |||||||
|    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||||
|    THE SOFTWARE. |    THE SOFTWARE. | ||||||
| """ | """ | ||||||
| VERSION = 'v2.6.0-dev' | # The version to display. | ||||||
| SSH_HEADER = 'SSH-{0}-OpenSSH_8.2'  # SSH software to impersonate | VERSION = 'v2.6.0' | ||||||
| GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues'  # The URL to the Github issues tracker. |  | ||||||
|  | # SSH software to impersonate | ||||||
|  | SSH_HEADER = 'SSH-{0}-OpenSSH_8.2' | ||||||
|  |  | ||||||
|  | # The URL to the Github issues tracker. | ||||||
|  | GITHUB_ISSUES_URL = 'https://github.com/jtesta/ssh-audit/issues' | ||||||
|  |  | ||||||
|  | # The man page.  Only filled in on Windows systems. | ||||||
| WINDOWS_MAN_PAGE = '' | WINDOWS_MAN_PAGE = '' | ||||||
|  |  | ||||||
|  | # True when installed from a Snap package, otherwise False. | ||||||
|  | SNAP_PACKAGE = False | ||||||
|  |  | ||||||
|  | # Error message when installed as a Snap package and a file access fails. | ||||||
|  | SNAP_PERMISSIONS_ERROR = 'Error while accessing file.  It appears that ssh-audit was installed as a Snap package.  In that case, there are two options:  1.) only try to read & write files in the $HOME/snap/ssh-audit/common/ directory, or 2.) grant permissions to read & write files in $HOME using the following command: "sudo snap connect ssh-audit:home :home"' | ||||||
|   | |||||||
| @@ -30,6 +30,7 @@ from datetime import date | |||||||
| from ssh_audit import exitcodes | from ssh_audit import exitcodes | ||||||
| from ssh_audit.ssh2_kex import SSH2_Kex  # pylint: disable=unused-import | from ssh_audit.ssh2_kex import SSH2_Kex  # pylint: disable=unused-import | ||||||
| from ssh_audit.banner import Banner  # pylint: disable=unused-import | from ssh_audit.banner import Banner  # pylint: disable=unused-import | ||||||
|  | from ssh_audit.globals import SNAP_PACKAGE, SNAP_PERMISSIONS_ERROR | ||||||
|  |  | ||||||
|  |  | ||||||
| # Validates policy files and performs policy testing | # Validates policy files and performs policy testing | ||||||
| @@ -122,6 +123,13 @@ class Policy: | |||||||
|             except FileNotFoundError: |             except FileNotFoundError: | ||||||
|                 print("Error: policy file not found: %s" % policy_file) |                 print("Error: policy file not found: %s" % policy_file) | ||||||
|                 sys.exit(exitcodes.UNKNOWN_ERROR) |                 sys.exit(exitcodes.UNKNOWN_ERROR) | ||||||
|  |             except PermissionError as e: | ||||||
|  |                 # If installed as a Snap package, print a more useful message with potential work-arounds. | ||||||
|  |                 if SNAP_PACKAGE: | ||||||
|  |                     print(SNAP_PERMISSIONS_ERROR) | ||||||
|  |                 else: | ||||||
|  |                     print("Error: insufficient permissions: %s" % str(e)) | ||||||
|  |                 sys.exit(exitcodes.UNKNOWN_ERROR) | ||||||
|  |  | ||||||
|         lines = [] |         lines = [] | ||||||
|         if policy_data is not None: |         if policy_data is not None: | ||||||
|   | |||||||
| @@ -36,6 +36,8 @@ import traceback | |||||||
| from typing import Dict, List, Set, Sequence, Tuple, Iterable  # noqa: F401 | from typing import Dict, List, Set, Sequence, Tuple, Iterable  # noqa: F401 | ||||||
| from typing import Callable, Optional, Union, Any  # noqa: F401 | from typing import Callable, Optional, Union, Any  # noqa: F401 | ||||||
|  |  | ||||||
|  | from ssh_audit.globals import SNAP_PACKAGE | ||||||
|  | from ssh_audit.globals import SNAP_PERMISSIONS_ERROR | ||||||
| from ssh_audit.globals import VERSION | from ssh_audit.globals import VERSION | ||||||
| from ssh_audit.globals import WINDOWS_MAN_PAGE | from ssh_audit.globals import WINDOWS_MAN_PAGE | ||||||
| from ssh_audit.algorithm import Algorithm | from ssh_audit.algorithm import Algorithm | ||||||
| @@ -560,18 +562,27 @@ def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH | |||||||
|     if aconf.policy_file is None: |     if aconf.policy_file is None: | ||||||
|         raise RuntimeError('Internal error: cannot write policy file since filename is None!') |         raise RuntimeError('Internal error: cannot write policy file since filename is None!') | ||||||
|  |  | ||||||
|     # Open with mode 'x' (creates the file, or fails if it already exist). |     succeeded = False | ||||||
|     succeeded = True |     err = '' | ||||||
|     try: |     try: | ||||||
|  |         # Open with mode 'x' (creates the file, or fails if it already exist). | ||||||
|         with open(aconf.policy_file, 'x', encoding='utf-8') as f: |         with open(aconf.policy_file, 'x', encoding='utf-8') as f: | ||||||
|             f.write(policy_data) |             f.write(policy_data) | ||||||
|  |         succeeded = True | ||||||
|     except FileExistsError: |     except FileExistsError: | ||||||
|         succeeded = False |         err = "Error: file already exists: %s" % aconf.policy_file | ||||||
|  |     except PermissionError as e: | ||||||
|  |         # If installed as a Snap package, print a more useful message with potential work-arounds. | ||||||
|  |         if SNAP_PACKAGE: | ||||||
|  |             print(SNAP_PERMISSIONS_ERROR) | ||||||
|  |             sys.exit(exitcodes.UNKNOWN_ERROR) | ||||||
|  |         else: | ||||||
|  |             err = "Error: insufficient permissions: %s" % str(e) | ||||||
|  |  | ||||||
|     if succeeded: |     if succeeded: | ||||||
|         print("Wrote policy to %s.  Customize as necessary, then run a policy scan with -P option." % aconf.policy_file) |         print("Wrote policy to %s.  Customize as necessary, then run a policy scan with -P option." % aconf.policy_file) | ||||||
|     else: |     else: | ||||||
|         print("Error: file already exists: %s" % aconf.policy_file) |         print(err) | ||||||
|  |  | ||||||
|  |  | ||||||
| def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[..., None]) -> 'AuditConf':  # pylint: disable=too-many-statements | def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[..., None]) -> 'AuditConf':  # pylint: disable=too-many-statements | ||||||
| @@ -681,8 +692,16 @@ def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[. | |||||||
|  |  | ||||||
|     # If a file containing a list of targets was given, read it. |     # If a file containing a list of targets was given, read it. | ||||||
|     if aconf.target_file is not None: |     if aconf.target_file is not None: | ||||||
|         with open(aconf.target_file, 'r', encoding='utf-8') as f: |         try: | ||||||
|             aconf.target_list = f.readlines() |             with open(aconf.target_file, 'r', encoding='utf-8') as f: | ||||||
|  |                 aconf.target_list = f.readlines() | ||||||
|  |         except PermissionError as e: | ||||||
|  |             # If installed as a Snap package, print a more useful message with potential work-arounds. | ||||||
|  |             if SNAP_PACKAGE: | ||||||
|  |                 print(SNAP_PERMISSIONS_ERROR) | ||||||
|  |             else: | ||||||
|  |                 print("Error: insufficient permissions: %s" % str(e)) | ||||||
|  |             sys.exit(exitcodes.UNKNOWN_ERROR) | ||||||
|  |  | ||||||
|         # Strip out whitespace from each line in target file, and skip empty lines. |         # Strip out whitespace from each line in target file, and skip empty lines. | ||||||
|         aconf.target_list = [target.strip() for target in aconf.target_list if target not in ("", "\n")] |         aconf.target_list = [target.strip() for target in aconf.target_list if target not in ("", "\n")] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Joe Testa
					Joe Testa