def list_policies() -> None: # Get a list of all the files in the policies sub-directory, relative to the path of this script. installed_dir = os.path.dirname(os.path.abspath(__file__)) policies_dir = os.path.join(installed_dir, 'policies') # If the path is not a directory, print a useful error and exit. if not os.path.isdir(policies_dir): print( "Error: could not find policies directory. Please report this full output to <%s>:" % GITHUB_ISSUES_URL) print("\nsys.argv[0]: %s" % sys.argv[0]) print("__file__: %s" % __file__) print("policies_dir: %s" % policies_dir) sys.exit(exitcodes.UNKNOWN_ERROR) # Get a list of all the files in the policies sub-directory. files = [] for f in os.listdir(policies_dir): files.append(f) files.sort( ) # Now the files will be in order, like 'ubuntu_client_16_04.txt', 'ubuntu_client_18_04.txt', 'ubuntu_client_20_04.txt', ... server_policies_summary = [] client_policies_summary = [] for f in files: # Load each policy, and generate a short summary from its name and absolute file path. policy_file = os.path.join(policies_dir, f) policy = Policy(policy_file=policy_file) policy_summary = "Name: %s\nPolicy path: %s" % ( policy.get_name_and_version(), policy_file) # We will print the server policies separately from thee client policies... if policy.is_server_policy(): server_policies_summary.append(policy_summary) else: client_policies_summary.append(policy_summary) if len(server_policies_summary) > 0: out.head('\nServer policies:\n') print("\n\n".join(server_policies_summary)) print() if len(client_policies_summary) > 0: out.head('\nClient policies:\n') print("\n\n".join(client_policies_summary)) print() if len(server_policies_summary) == 0 and len(client_policies_summary) == 0: print("Error: no built-in policies found in %s." % policies_dir) else: print( "\nHint: Use -P and provide the path to a policy to run a policy scan.\n" )
def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH2_Kex'], client_host: Optional[str]) -> None: # Set the source of this policy to the server host if this is a server audit, otherwise set it to the client address. source: Optional[str] = aconf.host if aconf.client_audit: source = client_host policy_data = Policy.create(source, banner, kex, aconf.client_audit) if aconf.policy_file is None: raise RuntimeError('Internal error: cannot write policy file since filename is None!') succeeded = False err = '' 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: f.write(policy_data) succeeded = True except FileExistsError: 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: print("Wrote policy to %s. Customize as necessary, then run a policy scan with -P option." % aconf.policy_file) else: print(err)
def make_policy(aconf: AuditConf, banner: Optional['Banner'], kex: Optional['SSH2_Kex'], client_host: Optional[str]) -> None: # Set the source of this policy to the server host if this is a server audit, otherwise set it to the client address. source = aconf.host # type: Optional[str] if aconf.client_audit: source = client_host policy_data = Policy.create(source, banner, kex, aconf.client_audit) if aconf.policy_file 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 = True try: with open(aconf.policy_file, 'x') as f: f.write(policy_data) except FileExistsError: succeeded = False if succeeded: print( "Wrote policy to %s. Customize as necessary, then run a policy scan with -P option." % aconf.policy_file) else: print("Error: file already exists: %s" % aconf.policy_file)
def test_builtin_policy_consistency(self): '''Ensure that the BUILTIN_POLICIES struct is consistent.''' for policy_name in Policy.BUILTIN_POLICIES: # Ensure that the policy name ends with " (version X)", where X is the 'version' field. version_str = " (version %s)" % Policy.BUILTIN_POLICIES[ policy_name]['version'] assert (policy_name.endswith(version_str)) # Ensure that each built-in policy can be loaded with Policy.load_builtin_policy(). assert (Policy.load_builtin_policy(policy_name) is not None) # Ensure that both server and client policy names are returned. server_policy_names, client_policy_names = Policy.list_builtin_policies( ) assert (len(server_policy_names) > 0) assert (len(client_policy_names) > 0)
def list_policies(out: OutputBuffer) -> None: '''Prints a list of server & client policies.''' server_policy_names, client_policy_names = Policy.list_builtin_policies() if len(server_policy_names) > 0: out.head('\nServer policies:\n') out.info(" * \"%s\"" % "\"\n * \"".join(server_policy_names)) if len(client_policy_names) > 0: out.head('\nClient policies:\n') out.info(" * \"%s\"" % "\"\n * \"".join(client_policy_names)) out.sep() if len(server_policy_names) == 0 and len(client_policy_names) == 0: out.fail("Error: no built-in policies found!") else: out.info("\nHint: Use -P and provide the full name of a policy to run a policy scan with.\n") out.write()
def process_commandline(out: OutputBuffer, args: List[str], usage_cb: Callable[..., None]) -> 'AuditConf': # pylint: disable=too-many-statements # pylint: disable=too-many-branches aconf = AuditConf() try: sopts = 'h1246M:p:P:jbcnvl:t:T:Lmd' lopts = ['help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=', 'threads=', 'manual', 'debug'] opts, args = getopt.gnu_getopt(args, sopts, lopts) except getopt.GetoptError as err: usage_cb(str(err)) aconf.ssh1, aconf.ssh2 = False, False host: str = '' oport: Optional[str] = None port: int = 0 for o, a in opts: if o in ('-h', '--help'): usage_cb() elif o in ('-1', '--ssh1'): aconf.ssh1 = True elif o in ('-2', '--ssh2'): aconf.ssh2 = True elif o in ('-4', '--ipv4'): aconf.ipv4 = True elif o in ('-6', '--ipv6'): aconf.ipv6 = True elif o in ('-p', '--port'): oport = a elif o in ('-b', '--batch'): aconf.batch = True aconf.verbose = True elif o in ('-c', '--client-audit'): aconf.client_audit = True elif o in ('-n', '--no-colors'): aconf.colors = False out.use_colors = False elif o in ('-j', '--json'): if aconf.json: # If specified twice, enable indent printing. aconf.json_print_indent = True else: aconf.json = True elif o in ('-v', '--verbose'): aconf.verbose = True out.verbose = True elif o in ('-l', '--level'): if a not in ('info', 'warn', 'fail'): usage_cb('level {} is not valid'.format(a)) aconf.level = a elif o in ('-t', '--timeout'): aconf.timeout = float(a) aconf.timeout_set = True elif o in ('-M', '--make-policy'): aconf.make_policy = True aconf.policy_file = a elif o in ('-P', '--policy'): aconf.policy_file = a elif o in ('-T', '--targets'): aconf.target_file = a elif o == '--threads': aconf.threads = int(a) elif o in ('-L', '--list-policies'): aconf.list_policies = True elif o == '--lookup': aconf.lookup = a elif o in ('-m', '--manual'): aconf.manual = True elif o in ('-d', '--debug'): aconf.debug = True out.debug = True if len(args) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '' and aconf.manual is False: usage_cb() if aconf.manual: return aconf if aconf.lookup != '': return aconf if aconf.list_policies: list_policies(out) sys.exit(exitcodes.GOOD) if aconf.client_audit is False and aconf.target_file is None: if oport is not None: host = args[0] else: host, port = Utils.parse_host_and_port(args[0]) if not host and aconf.target_file is None: usage_cb('host is empty') if port == 0 and oport is None: if aconf.client_audit: # The default port to listen on during a client audit is 2222. port = 2222 else: port = 22 if oport is not None: port = Utils.parse_int(oport) if port <= 0 or port > 65535: usage_cb('port {} is not valid'.format(oport)) aconf.host = host aconf.port = port if not (aconf.ssh1 or aconf.ssh2): aconf.ssh1, aconf.ssh2 = True, True # If a file containing a list of targets was given, read it. if aconf.target_file is not None: try: 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. aconf.target_list = [target.strip() for target in aconf.target_list if target not in ("", "\n")] # If a policy file was provided, validate it. if (aconf.policy_file is not None) and (aconf.make_policy is False): # First, see if this is a built-in policy name. If not, assume a file path was provided, and try to load it from disk. aconf.policy = Policy.load_builtin_policy(aconf.policy_file) if aconf.policy is None: try: aconf.policy = Policy(policy_file=aconf.policy_file) except Exception as e: out.fail("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc())) out.write() sys.exit(exitcodes.UNKNOWN_ERROR) # If the user wants to do a client audit, but provided a server policy, terminate. if aconf.client_audit and aconf.policy.is_server_policy(): out.fail("Error: client audit selected, but server policy provided.") out.write() sys.exit(exitcodes.UNKNOWN_ERROR) # If the user wants to do a server audit, but provided a client policy, terminate. if aconf.client_audit is False and aconf.policy.is_server_policy() is False: out.fail("Error: server audit selected, but client policy provided.") out.write() sys.exit(exitcodes.UNKNOWN_ERROR) return aconf
def process_commandline( args: List[str], usage_cb: Callable[..., None] ) -> 'AuditConf': # pylint: disable=too-many-statements # pylint: disable=too-many-branches aconf = AuditConf() try: sopts = 'h1246M:p:P:jbcnvl:t:T:L' lopts = [ 'help', 'ssh1', 'ssh2', 'ipv4', 'ipv6', 'make-policy=', 'port=', 'policy=', 'json', 'batch', 'client-audit', 'no-colors', 'verbose', 'level=', 'timeout=', 'targets=', 'list-policies', 'lookup=' ] opts, args = getopt.gnu_getopt(args, sopts, lopts) except getopt.GetoptError as err: usage_cb(str(err)) aconf.ssh1, aconf.ssh2 = False, False host = '' # type: str oport = None # type: Optional[str] port = 0 # type: int for o, a in opts: if o in ('-h', '--help'): usage_cb() elif o in ('-1', '--ssh1'): aconf.ssh1 = True elif o in ('-2', '--ssh2'): aconf.ssh2 = True elif o in ('-4', '--ipv4'): aconf.ipv4 = True elif o in ('-6', '--ipv6'): aconf.ipv6 = True elif o in ('-p', '--port'): oport = a elif o in ('-b', '--batch'): aconf.batch = True aconf.verbose = True elif o in ('-c', '--client-audit'): aconf.client_audit = True elif o in ('-n', '--no-colors'): aconf.colors = False elif o in ('-j', '--json'): aconf.json = True elif o in ('-v', '--verbose'): aconf.verbose = True elif o in ('-l', '--level'): if a not in ('info', 'warn', 'fail'): usage_cb('level {} is not valid'.format(a)) aconf.level = a elif o in ('-t', '--timeout'): aconf.timeout = float(a) aconf.timeout_set = True elif o in ('-M', '--make-policy'): aconf.make_policy = True aconf.policy_file = a elif o in ('-P', '--policy'): aconf.policy_file = a elif o in ('-T', '--targets'): aconf.target_file = a elif o in ('-L', '--list-policies'): aconf.list_policies = True elif o == '--lookup': aconf.lookup = a if len( args ) == 0 and aconf.client_audit is False and aconf.target_file is None and aconf.list_policies is False and aconf.lookup == '': usage_cb() if aconf.lookup != '': return aconf if aconf.list_policies: list_policies() sys.exit(exitcodes.GOOD) if aconf.client_audit is False and aconf.target_file is None: if oport is not None: host = args[0] else: host, port = Utils.parse_host_and_port(args[0]) if not host and aconf.target_file is None: usage_cb('host is empty') if port == 0 and oport is None: if aconf.client_audit: # The default port to listen on during a client audit is 2222. port = 2222 else: port = 22 if oport is not None: port = Utils.parse_int(oport) if port <= 0 or port > 65535: usage_cb('port {} is not valid'.format(oport)) aconf.host = host aconf.port = port if not (aconf.ssh1 or aconf.ssh2): aconf.ssh1, aconf.ssh2 = True, True # If a file containing a list of targets was given, read it. if aconf.target_file is not None: with open(aconf.target_file, 'r') as f: aconf.target_list = f.readlines() # 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") ] # If a policy file was provided, validate it. if (aconf.policy_file is not None) and (aconf.make_policy is False): try: aconf.policy = Policy(policy_file=aconf.policy_file) except Exception as e: print("Error while loading policy file: %s: %s" % (str(e), traceback.format_exc())) sys.exit(exitcodes.UNKNOWN_ERROR) # If the user wants to do a client audit, but provided a server policy, terminate. if aconf.client_audit and aconf.policy.is_server_policy(): print("Error: client audit selected, but server policy provided.") sys.exit(exitcodes.UNKNOWN_ERROR) # If the user wants to do a server audit, but provided a client policy, terminate. if aconf.client_audit is False and aconf.policy.is_server_policy( ) is False: print("Error: server audit selected, but client policy provided.") sys.exit(exitcodes.UNKNOWN_ERROR) return aconf