def check_vpc_feature_enabled_and_used( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if the vPC feature is actually used if it is enabled.""" config = parse("\n".join(config)) return _feature_enabled_but_not_configured( config, r"^feature vpc", r"^vpc domain", "vPC feature enabled but never used")
def check_telnet_enabled( config: typing.List[str]) -> typing.Optional[CheckResult]: """Check if the telnet feature is explicitly enabled.""" config = parse("\n".join(config)) lines = config.find_lines("^feature telnet") if lines: return CheckResult(text="Feature telnet is enabled.", lines=lines) else: return None
def check_ip_http_server( config: typing.List[str]) -> typing.Optional[CheckResult]: """Check if the http server is enabled.""" config = parse("\n".join(config)) lines = config.find_lines("^ip http") if lines: return CheckResult(text="HTTP server not disabled.", lines=lines) else: return None
def check_lacp_feature_enabled_and_used( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if the LACP feature is actually used if it is enabled.""" config = parse("\n".join(config)) return _feature_enabled_but_not_configured( config, r"^feature lacp", r"^\s+channel-group \d+ mode active|passive", "LACP feature enabled but never used", )
def check_fex_feature_enabled_and_used( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check whether an enabled fex feature is actually used.""" config = parse("\n".join(config)) return _feature_enabled_but_not_configured( config, r"^feature-set fex", r"^fex id", "Feature-set fex enabled but never used.", )
def check_fex_feature_set_installed_but_not_enabled( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if the fex feature-set is installed but not enabled.""" config = parse("\n".join(config)) return _feature_enabled_but_not_configured( config, r"^install feature-set fex", r"^feature-set fex", "Feature-set fex installed but not enabled.", )
def check_password_strength( config: typing.List[str]) -> typing.Optional[CheckResult]: """Check if the password strength check has been disabled.""" config = parse("\n".join(config)) disabled = config.find_lines("^no password strength-check") if disabled: return CheckResult(text="Password strength-check disabled.", lines=disabled) else: return None
def check_default_snmp_communities( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check for presence of default SNMP community strings.""" config = parse("\n".join(config)) snmp_communities = config.find_lines("^snmp-server community") for community in snmp_communities: if community.startswith( "snmp-server community public") or community.startswith( "snmp-server community private"): return CheckResult(text="Default SNMP communities present.", lines=snmp_communities) return None
def check_password_hash_strength( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if strong password hash algorithms were used.""" config = parse("\n".join(config)) lines_with_passwords = config.find_lines(r"^.*(password|secret)\s\d.*$") bad_lines = [] for line in lines_with_passwords: hash_algorithm = get_password_hash_algorithm(line) if hash_algorithm in c.bad_hash_algorithms: bad_lines.append(line) if bad_lines: return CheckResult("Insecure hash algorithms in use.", lines=bad_lines) else: return None
def check_bogus_as(config: typing.List[str]) -> typing.Optional[CheckResult]: """Check if any bogus autonomous system is used in the configuration.""" config = parse("\n".join(config)) bgp_routers = config.find_lines("^router bgp") bad_lines = [] for line in bgp_routers: as_number = int(line[11:]) if as_number in bogus_as_numbers: bad_lines.append(line) if bad_lines: return CheckResult(text="Bogus AS number in use", lines=bad_lines) else: return None
def check_routing_protocol_enabled_and_used( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if a routing protocol is actually used - should it be enabled.""" config = parse("\n".join(config)) for protocol in ["bgp", "ospf", "eigrp", "rip"]: feature_enabled = config.find_lines(f"^feature {protocol}") if not feature_enabled: return None feature_used = config.find_lines(f"^router {protocol}") if not feature_used: return CheckResult( text=f"{protocol.upper()} enabled but never used.", lines=feature_enabled + feature_used, ) return None
def check_fex_without_interface( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check whether every configured fex id also has an associated interface.""" config = parse("\n".join(config)) configured_fex_ids = config.find_lines(r"^fex id") faulty_fex_ids = [] for line in configured_fex_ids: fex_id = line.split()[2] has_interface = config.find_lines( r"^\s+fex associate {}".format(fex_id)) if not has_interface: faulty_fex_ids.append(line) if faulty_fex_ids: return CheckResult(text="FEX without associated interface configured.", lines=faulty_fex_ids) else: return None
def check_used_but_unconfigured_access_lists( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check for any ACLs that are used but never configured. Potential usages are: * Packet filtering * Rate limiting * Route maps """ config = parse("\n".join(config)) access_list_usages = get_access_list_usage(config) access_list_definitions = get_access_list_definitions(config) defined_access_lists = [] undefined_but_used_access_lists = [] for definition in access_list_definitions: name = get_name_from_acl_definition(definition) defined_access_lists.append(name) for usage in access_list_usages: # Get acl name/number from the configuration line for packet filtering usages # Standard use acl_in_filtering = re.findall(r"access-(class|group)\s(\S+|\d+)", usage) if acl_in_filtering and acl_in_filtering[0][ 1] not in defined_access_lists: undefined_but_used_access_lists.append(usage) # Evaluated in other ACLs acl_evaluated = re.findall(r"^\s+evaluate\s(\S+|\d+)", usage) if acl_evaluated and acl_evaluated[0] not in defined_access_lists: undefined_but_used_access_lists.append(usage) # Get acl name/number from the configuration line for route-map usages acl_in_route_map = re.findall(r"\s+match\sip\s\S+\s(\S+|\d+)", usage) if acl_in_route_map and acl_in_route_map[0] not in defined_access_lists: undefined_but_used_access_lists.append(usage) if undefined_but_used_access_lists: return CheckResult( text="Access lists used but never defined.", lines=undefined_but_used_access_lists, ) else: return None
def check_console_password( config: typing.List[str]) -> typing.Optional[CheckResult]: """Check for authentication on the console line.""" config = parse("\n".join(config)) line_con_config = config.find_all_children("^line con 0") if len(line_con_config) == 0: return None # TODO: Log this? login = False password = False for line in line_con_config: if "login" in line: login = True if "password" in line: password = True if not login or not password: return CheckResult(text="Console line unauthenticated.", lines=line_con_config) else: return None
def check_plaintext_passwords( config: typing.List[str]) -> typing.Optional[CheckResult]: """Check if there are any plaintext passwords in the configuration.""" config = parse("\n".join(config)) lines = config.find_lines("^username.*password") if lines: # If `service password-encryption` is configured, users are saved to the # config like `username test password 7 $ENCRYPTED. The following for-loop # checks for that. for line in lines: has_hash_algorithm = get_password_hash_algorithm(line) if not has_hash_algorithm: break else: # If the for loop wasn't exited prematurely, no plaintext passwords # are present. return None return CheckResult(text="Plaintext user passwords in configuration.", lines=lines) else: return None
def check_switchport_access_config( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if the switchport mode matches all config commands per interface.""" config = parse("\n".join(config)) interfaces = config.find_objects_w_child(r"^interface", r"^\s+switchport mode access") bad_lines = [] for interface in interfaces: bad_lines_per_interface = [interface.text] switchport_config_lines = list( filter(lambda l: re.match(r"^\s+switchport", l.text), interface.children)) for line in switchport_config_lines: if line.text.strip().startswith("switchport trunk"): bad_lines_per_interface.append(line.text) if len(bad_lines_per_interface) > 1: bad_lines.extend(bad_lines_per_interface) if bad_lines: return CheckResult("Trunk port config present on access interfaces.", lines=bad_lines) else: return None
def check_unused_access_lists( config: typing.List[str]) -> typing.Optional[CheckResult]: """Check for any ACLs that are configured but never used. Potential usages are: * Packet filtering * Rate limiting * Route maps """ config = parse("\n".join(config)) access_lists = get_access_list_definitions(config) unused_acls = [] for acl in access_lists: name = get_name_from_acl_definition(acl) usages = get_access_list_usage(config, name=name) if not usages: unused_acls.append(acl) if unused_acls: return CheckResult(text="Unused ACLs configured", lines=unused_acls) else: return None
def check_switchport_mode_fex_fabric( config: typing.List[str], ) -> typing.Optional[CheckResult]: """Check if any interface in switchport mode fex-fabric has a fex-id associated.""" config = parse("\n".join(config)) interfaces = config.find_objects_w_child("^interface", "switchport mode fex-fabric") faulty_lines = [] for interface in interfaces: current_lines = [interface.text] for line in interface.children: current_lines.append(line.text) if line.text.strip().startswith("fex associate"): break else: faulty_lines.extend(current_lines) if faulty_lines: return CheckResult( text= "Interface in switchport mode fex-fabric without associated fex.", lines=faulty_lines, ) else: return None