diff --git a/core/checkers/cliprotocolchecker.py b/core/checkers/cliprotocolchecker.py new file mode 100644 index 0000000000000000000000000000000000000000..d31ee1dda987743a634ab5786c04e1eecee0b4b0 --- /dev/null +++ b/core/checkers/cliprotocolchecker.py @@ -0,0 +1,76 @@ +# ---------------------------------------------------------------------- +# CLI checker +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2022 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# Python modules +from typing import List, Iterable, Dict + +# NOC modules +from .base import Check, ObjectChecker, CheckResult, CredentialSet +from ..script.credentialchecker import CredentialChecker as CredentialCheckerScript, CLICredential +from ..script.scheme import Protocol + + +class CLIProtocolChecker(ObjectChecker): + """ + Check ManagedObject supported access protocols and credential + """ + + name = "cliprotocolchecker" + CHECKS: List[str] = ["TELNET", "SSH"] + PROTO_CHECK_MAP: Dict[str, Protocol] = {p.config.check: p for p in Protocol if p.config.check} + + def iter_result(self, checks=None) -> Iterable[CheckResult]: + credential = None + if self.object.credentials.user or self.object.credentials.password: + credential = [ + CLICredential( + user=self.object.credentials.user, + password=self.object.credentials.password, + super_password=self.object.credentials.super_password, + ) + ] + cc = CredentialCheckerScript( + self.object.address, + self.object.pool, + self.object.effective_labels, + credentials=credential, + profile=self.object.profile, + logger=self.logger, + calling_service=self.calling_service, + raise_privilege=self.object.to_raise_privileges, + ) + protocols: List[Protocol] = [] + for c in checks or []: + if isinstance(c, Check): + c = c.name + if c not in self.PROTO_CHECK_MAP: + continue + protocols += [self.PROTO_CHECK_MAP[c]] + action = None + r = {} + for proto_r in cc.iter_result(protocols): + if not protocols: + break + if proto_r.protocol not in protocols: + continue + r[proto_r.protocol] = proto_r + if proto_r.status and proto_r.credential: + action = CredentialSet( + user=proto_r.credential.user, + password=proto_r.credential.password, + super_password=proto_r.credential.super_password, + ) + if action and proto_r.protocol in protocols: + protocols.remove(proto_r.protocol) + for x in r.values(): + yield CheckResult( + check=x.protocol.config.check, + status=x.status, + error=x.error, + skipped=x.skipped, + action=action, + ) diff --git a/core/checkers/credentialchecker.py b/core/checkers/credentialchecker.py deleted file mode 100644 index 508a30ecb629dd325a085022b9cd9d4d0a8562cd..0000000000000000000000000000000000000000 --- a/core/checkers/credentialchecker.py +++ /dev/null @@ -1,59 +0,0 @@ -# ---------------------------------------------------------------------- -# Credential checker -# ---------------------------------------------------------------------- -# Copyright (C) 2007-2022 The NOC Project -# See LICENSE for details -# ---------------------------------------------------------------------- - -# Python modules -from typing import List, Iterable - -# NOC modules -from .base import Check, ObjectChecker, CheckResult, CredentialSet -from ..script.credentialchecker import CredentialChecker as CredentialCheckerScript -from ..script.credentialchecker import Protocol, SNMPCredential, CLICredential - - -class CredentialChecker(ObjectChecker): - """ - Check ManagedObject supported access protocols and credential - """ - - name = "credentialchecker" - CHECKS: List[str] = ["TELNET", "SSH", "SNMPv1", "SNMPv2c"] - PROTO_CHECK_MAP = {p.config.check: p for p in Protocol if p.config.check} - USER_DISCOVERY_USE = False - - def iter_result(self, checks=None) -> Iterable[CheckResult]: - cc = CredentialCheckerScript( - self.object.address, - self.object.pool, - self.object.effective_labels, - profile=self.object.profile, - calling_service=self.calling_service, - ) - protocols = [] - for c in checks or []: - if isinstance(c, Check): - c = c.name - if c not in self.PROTO_CHECK_MAP: - continue - protocols += [self.PROTO_CHECK_MAP[c]] - for sr in cc.do_check(*protocols): - action = None - if sr.credential and isinstance(sr.credential, SNMPCredential): - action = CredentialSet(snmp_ro=sr.credential.snmp_ro) - elif sr.credential and isinstance(sr.credential, CLICredential): - action = CredentialSet( - user=sr.credential.user, - password=sr.credential.password, - super_password=sr.credential.super_password, - ) - for pr in sr.protocols: - yield CheckResult( - check=pr.protocol.config.check, - status=pr.status, - error=pr.error, - skipped=pr.skipped, - action=action, - ) diff --git a/core/checkers/profilechecker.py b/core/checkers/profilechecker.py index 675d45133ca2217839790c23fea54edeb6165421..9e3691da0fb7402e52a2bf9a0ad9dc203a2f91e3 100644 --- a/core/checkers/profilechecker.py +++ b/core/checkers/profilechecker.py @@ -9,6 +9,7 @@ from typing import List, Iterable # NOC modules +from noc.core.text import filter_non_printable from .base import ObjectChecker, CheckResult, ProfileSet from ..profile.checker import ProfileChecker as ProfileCheckerProfile from ..script.credentialchecker import Protocol @@ -52,7 +53,11 @@ class ProfileChecker(ObjectChecker): ) profile = checker.get_profile() if not profile: - yield CheckResult(check="PROFILE", status=bool(profile), error=checker.get_error()) + yield CheckResult( + check="PROFILE", + status=bool(profile), + error=filter_non_printable(checker.get_error())[:200], + ) return # Skipped yield CheckResult( diff --git a/core/checkers/snmpprotocolchecker.py b/core/checkers/snmpprotocolchecker.py new file mode 100644 index 0000000000000000000000000000000000000000..1fa5ad5c9b7d7d18006241912e707343f316a937 --- /dev/null +++ b/core/checkers/snmpprotocolchecker.py @@ -0,0 +1,71 @@ +# ---------------------------------------------------------------------- +# SNMP checker +# ---------------------------------------------------------------------- +# Copyright (C) 2007-2022 The NOC Project +# See LICENSE for details +# ---------------------------------------------------------------------- + +# Python modules +from typing import List, Iterable, Dict + +# NOC modules +from .base import Check, ObjectChecker, CheckResult, CredentialSet +from ..script.credentialchecker import CredentialChecker as CredentialCheckerScript, SNMPCredential +from ..script.scheme import Protocol + + +class SNMPProtocolChecker(ObjectChecker): + """ + Check ManagedObject supported access protocols and credential + """ + + name = "snmpprotocolchecker" + CHECKS: List[str] = ["SNMPv1", "SNMPv2c"] + PROTO_CHECK_MAP: Dict[str, Protocol] = {p.config.check: p for p in Protocol if p.config.check} + + def iter_result(self, checks=None) -> Iterable[CheckResult]: + credential = None + if self.object.credentials.snmp_ro or self.object.credentials.snmp_rw: + credential = [ + SNMPCredential( + snmp_ro=self.object.credentials.snmp_ro, + snmp_rw=self.object.credentials.snmp_rw, + ) + ] + cc = CredentialCheckerScript( + self.object.address, + self.object.pool, + self.object.effective_labels, + credentials=credential, + logger=self.logger, + calling_service=self.calling_service, + ) + protocols: List[Protocol] = [] + for c in checks or []: + if isinstance(c, Check): + c = c.name + if c not in self.PROTO_CHECK_MAP: + continue + protocols += [self.PROTO_CHECK_MAP[c]] + action = None + r = {} + for proto_r in cc.iter_result(protocols): + if not protocols: + break + if proto_r.protocol not in protocols: + continue + r[proto_r.protocol] = proto_r + if proto_r.status and proto_r.credential: + action = CredentialSet( + snmp_ro=proto_r.credential.snmp_ro, snmp_rw=proto_r.credential.snmp_rw + ) + if action and proto_r.protocol in protocols: + protocols.remove(proto_r.protocol) + for x in r.values(): + yield CheckResult( + check=x.protocol.config.check, + status=x.status, + error=x.error, + skipped=x.skipped, + action=action, + ) diff --git a/core/script/credentialchecker.py b/core/script/credentialchecker.py index add4ed80da115d5002c492a8d0e6a04bcb6747fa..602acf2271a2d1ec4976a67caa6684b4c9a00c56 100644 --- a/core/script/credentialchecker.py +++ b/core/script/credentialchecker.py @@ -7,22 +7,20 @@ # Python modules import logging -import enum from dataclasses import dataclass -from typing import Optional, List, Tuple, Union, Set, Iterator, Dict +from typing import Optional, List, Tuple, Union, Iterator, Iterable # Third-party modules -import cachetools from pymongo import ReadPreference - +from mongoengine.queryset.visitor import Q as m_q # NOC modules +from .scheme import Protocol from noc.core.log import PrefixLoggerAdapter from noc.core.service.client import open_sync_rpc from noc.core.service.error import RPCError from noc.core.text import safe_shadow from noc.sa.models.profile import Profile -from noc.sa.models.authprofile import AuthProfile from noc.sa.models.credentialcheckrule import CredentialCheckRule from noc.core.mib import mib @@ -34,103 +32,58 @@ CHECK_OIDS = [ @dataclass(frozen=True) -class ProtoConfig(object): - alias: str - check: Optional[str] = None - snmp_version: Optional[int] = None - is_http: bool = False - is_cli: bool = False - - -CONFIGS = { - 1: ProtoConfig("telnet", is_cli=True, check="TELNET"), - 2: ProtoConfig("ssh", is_cli=True, check="SSH"), - 3: ProtoConfig("http", is_http=True, check="HTTP"), - 4: ProtoConfig("https", is_http=True, check="HTTPS"), - 5: ProtoConfig("beef", is_cli=True), - 6: ProtoConfig("snmp_v1", snmp_version=0, check="SNMPv1"), - 7: ProtoConfig("snmp_v2c", snmp_version=1, check="SNMPv2c"), - 8: ProtoConfig("snmp_v3", snmp_version=3), -} - - -class Protocol(enum.Enum): - @property - def config(self): - return CONFIGS[self.value] - - TELNET = 1 - SSH = 2 - HTTP = 3 - HTTPS = 4 - BEEF = 5 - SNMPv1 = 6 - SNMPv2c = 7 - SNMPv3 = 8 - - -@dataclass(frozen=True) -class ProtocolResult(object): - protocol: Protocol - status: bool - skipped: bool = False - error: Optional[str] = None - - -@dataclass(frozen=Tuple) class SNMPCredential(object): snmp_ro: str = None snmp_rw: Optional[str] = None + oids: Optional[List[str]] = None -@dataclass(frozen=Tuple) +@dataclass(frozen=True) class CLICredential(object): user: Optional[str] = None password: Optional[str] = None super_password: Optional[str] = None + raise_privilege: bool = True @dataclass(frozen=True) class SuggestSNMPConfig(object): - preference: int protocols: Tuple[Protocol, ...] + check_method: str = "snmp_check" snmp_ro: Optional[str] = None snmp_rw: Optional[str] = None check_oids: Optional[Tuple[str, ...]] = None def get_credential(self) -> SNMPCredential: - return SNMPCredential(self.snmp_ro, self.snmp_rw) + return SNMPCredential(self.snmp_ro, self.snmp_rw, oids=self.check_oids) @dataclass(frozen=True) class SuggestCLIConfig(object): - preference: int protocols: Tuple[Protocol, ...] + check_method: str = "cli_check" user: Optional[str] = None password: Optional[str] = None super_password: Optional[str] = None + raise_privileges: bool = True + access_preference: Optional[str] = "C" def get_credential(self) -> CLICredential: return CLICredential( - user=self.user, password=self.password, super_password=self.super_password + user=self.user, + password=self.password, + super_password=self.super_password, + raise_privilege=self.raise_privileges, ) -@dataclass -class SuggestResult(object): - protocols: List[ProtocolResult] - credential: Union[CLICredential, SNMPCredential] - - @dataclass(frozen=True) -class Credential(object): - protocols: List[Protocol] - user: Optional[str] = None - password: Optional[str] = None - super_password: Optional[str] = None - snmp_ro: Optional[str] = None - snmp_rw: Optional[str] = None - auth_profile: Optional[AuthProfile] = None +class ProtocolResult(object): + protocol: Protocol + status: bool + skipped: bool = False + error: Optional[str] = None + credential: Optional[Union[CLICredential, SNMPCredential]] = None SUGGEST_SNMP: Tuple[Protocol, ...] = (Protocol(7), Protocol(6)) @@ -140,7 +93,6 @@ SUGGEST_PROTOCOLS: Tuple[Protocol, ...] = SUGGEST_SNMP + SUGGEST_CLI class CredentialChecker(object): base_logger = logging.getLogger("credentialchecker") - _rules_cache = cachetools.TTLCache(10, ttl=60) def __init__( self, @@ -149,7 +101,9 @@ class CredentialChecker(object): labels: List[str] = None, logger=None, profile: Optional[str] = None, + raise_privilege: bool = True, calling_service: str = "credentialchecker", + credentials: Optional[List[Union[SuggestCLIConfig, SuggestSNMPConfig]]] = None, ): self.address = address self.pool = pool @@ -161,22 +115,26 @@ class CredentialChecker(object): self.profile: Optional["Profile"] = profile if isinstance(self.profile, str): self.profile = Profile.get_by_name(profile) if profile else None + self.credentials: List[Union[CLICredential, SNMPCredential]] = credentials or [] self.ignoring_cli = False + self.raise_privilege = raise_privilege if self.profile is None or self.profile.is_generic: self.logger.error("CLI Access for Generic profile is not supported. Ignoring") self.ignoring_cli = True - # Credential - self.auth_profiles: Set[AuthProfile] = set() - self.result: List[SuggestResult] = [] @staticmethod - def merge_protocols(*args, order: Tuple[Protocol, ...] = None): - return tuple( - sorted( - set(args[0]).intersection(*[set(s) for s in args[1:] if s]), - key=lambda x: order.index(x), - ) - ) + def iter_protocols(*args, order: Tuple[Protocol, ...] = None) -> Iterable[Protocol]: + """ + + :param args: + :param order: + :return: + """ + for p in sorted( + set(args[0]).intersection(*[set(s) for s in args[1:] if s]), + key=lambda x: order.index(x), + ): + yield p @staticmethod def is_unsupported_error(message) -> bool: @@ -189,8 +147,10 @@ class CredentialChecker(object): return True if "Error: Connection refused" in message: return True - if "SNMP Timeout" in message: + if "Error: Connection reset" in message: return True + # if "SNMP Timeout" in message: + # return True return False def iter_suggests( @@ -202,178 +162,164 @@ class CredentialChecker(object): :param protocols: :return: """ - r = set() - auth_profiles = set() ccr: List[CredentialCheckRule] = CredentialCheckRule.objects.filter(is_active=True) if self.labels: - ccr = ccr.filter(match__labels__in=self.labels) + ccr = ccr.filter( + (m_q(match__labels__in=self.labels, match__exclude_labels__nin=self.labels)) + | m_q(match__labels__exists=False) + ) + # Try custom credential first + for c in self.credentials: + if isinstance(c, CLICredential) and ( + Protocol(1) in protocols or Protocol(2) in protocols + ): + yield SuggestCLIConfig( + protocols=(Protocol(1), Protocol(2)), + user=c.user, + password=c.password, + super_password=c.super_password, + raise_privileges=self.raise_privilege, + ) + elif isinstance(c, SNMPCredential) and ( + Protocol(6) in protocols or Protocol(7) in protocols + ): + yield SuggestSNMPConfig( + protocols=(Protocol(6), Protocol(7)), + snmp_ro=c.snmp_ro, + snmp_rw=c.snmp_rw, + ) for cc in ccr.read_preference(ReadPreference.SECONDARY_PREFERRED).order_by("preference"): - sp = cc.suggest_protocols or protocols or SUGGEST_PROTOCOLS - cli_protocols = self.merge_protocols( - SUGGEST_CLI, protocols, cc.suggest_protocols, order=sp + # Suggest protocol order + protocol_order = cc.suggest_protocols or protocols or SUGGEST_PROTOCOLS + cli = tuple( + self.iter_protocols( + SUGGEST_CLI, protocols, cc.suggest_protocols, order=protocol_order + ) ) - snmp_protocols = self.merge_protocols( - SUGGEST_SNMP, protocols, cc.suggest_protocols, order=sp + snmp = tuple( + self.iter_protocols( + SUGGEST_SNMP, protocols, cc.suggest_protocols, order=protocol_order + ) ) + # CLI for ap in cc.suggest_auth_profile: ap = ap.auth_profile - if ap in auth_profiles: - self.logger.info("Authentication profile already processed. Skipping.") - continue - auth_profiles.add(ap) - # self.auth_profiles.add(ap) - if (ap.user or ap.password) and cli_protocols: - sc = SuggestCLIConfig( - cc.preference, - cli_protocols, + if cli and (ap.user or ap.password): + yield SuggestCLIConfig( + cli, user=ap.user, password=ap.password, super_password=ap.super_password, + raise_privileges=self.raise_privilege, ) - if sc not in r: - yield sc - r.add(sc) - if (ap.snmp_ro or ap.snmp_rw) and snmp_protocols: - ss = SuggestSNMPConfig( - cc.preference, snmp_protocols, snmp_ro=ap.snmp_ro, snmp_rw=ap.snmp_rw + if snmp and (ap.snmp_ro or ap.snmp_rw): + yield SuggestSNMPConfig( + snmp, + snmp_ro=ap.snmp_ro, + snmp_rw=ap.snmp_rw, + check_oids=cc.suggest_snmp_oids or None, ) - if ss not in r: - yield ss - r.add(ss) - if snmp_protocols: - for ss in cc.suggest_snmp: - ss = SuggestSNMPConfig( - cc.preference, snmp_protocols, snmp_ro=ss.snmp_ro, snmp_rw=ss.snmp_rw - ) - if ss not in r: - yield ss - r.add(ss) - if cli_protocols: + if cli: for sc in cc.suggest_credential: - sc = SuggestCLIConfig( - cc.preference, - cli_protocols, + yield SuggestCLIConfig( + cli, user=sc.user, password=sc.password, super_password=sc.super_password, + raise_privileges=self.raise_privilege, + ) + if snmp: + for ss in cc.suggest_snmp: + yield SuggestSNMPConfig( + snmp, + snmp_ro=ss.snmp_ro, + snmp_rw=ss.snmp_rw, + check_oids=cc.suggest_snmp_oids or None, ) - if sc not in r: - yield sc - r.add(sc) - # return r - def do_check(self, *protocols: Tuple[Protocol, ...]) -> List[SuggestResult]: + def iter_result( + self, protocols: Optional[Iterable[Protocol]] = None, first_success: bool = True + ) -> List[ProtocolResult]: """ - Detect Protocol Status - :param protocols: + Iterate over suggest result + :param protocols: List protocols for check + :param first_success: Skip other suggest for protocol after first success :return: """ - sr: List[SuggestResult] = [] - r: Dict[Protocol:ProtocolResult] = {} - protocols = protocols or SUGGEST_PROTOCOLS + unsupported_proto = set() + success_proto = set() + processed = set() for suggest in self.iter_suggests(protocols): - success = False - for proto in suggest.protocols: - if proto in r: - # Skip already detect proto + cred = suggest.get_credential() + for protocol in suggest.protocols: + if unsupported_proto and protocol in unsupported_proto: + # Skip unsupported proto continue - if isinstance(suggest, SuggestSNMPConfig): - oid = suggest.check_oids or CHECK_OIDS - status, message = self.check_oid( - oid[0], suggest.snmp_ro, f"{proto.config.alias}_get" - ) - if not status and not message: - message = "SNMP Timeout" - self.logger.info( - "Guessed community: %s, version: %d", - suggest.snmp_ro, - proto.config.snmp_version, - ) - elif isinstance(suggest, SuggestCLIConfig) and self.ignoring_cli: - # Skipped - r[proto] = ProtocolResult(protocol=proto, status=True, skipped=True) + if success_proto and protocol in success_proto: continue - elif isinstance(suggest, SuggestCLIConfig): - status, message = self.check_login( - suggest.user, suggest.password, suggest.super_password, protocol=proto - ) - - else: - self.logger.info("Not check") + if (protocol, cred) in processed: + # Skip already checked credential continue - if status: - r[proto] = ProtocolResult(protocol=proto, status=status) - success = True - elif self.is_unsupported_error(message): - r[proto] = ProtocolResult(protocol=proto, status=status, error=message) - if success: - sr.append( - SuggestResult( - protocols=[r[p] for p in suggest.protocols if p in r], - credential=suggest.get_credential(), - ) - ) - if not set(protocols) - set(r): - # If check all proto - break - return sr - - def do_snmp_check(self): + self.logger.debug("Trying suggest: %s:%s", protocol, cred) + if not hasattr(self, f"do_{suggest.check_method}"): + self.logger.info("Unknown check method: %s", suggest.check_method) + continue + check = getattr(self, f"do_{suggest.check_method}") + p_check: "ProtocolResult" = check(protocol, cred) + if not p_check.status and self.is_unsupported_error(p_check.error): + # Protocol is unsupported, ignored + unsupported_proto.add(p_check.protocol) + if first_success and p_check.status: + success_proto.add(protocol) + processed.add((protocol, cred)) + yield p_check + + def do_snmp_check(self, protocol: Protocol, cred: SNMPCredential) -> ProtocolResult: """ + :param protocol: + :param cred: :return: """ - protocols = [] - for suggest in self.iter_suggests(SUGGEST_SNMP): - for oid in suggest.check_oids or CHECK_OIDS: - for proto in suggest.protocols: - if self.check_oid(oid, suggest.snmp_ro, f"{proto.config.alias}_get"): - self.logger.info( - "Guessed community: %s, version: %d", - suggest.snmp_ro, - proto.config.snmp_version, - ) - protocols.append(ProtocolResult(protocol=proto, status=True)) - if protocols: - self.result.append( - SuggestResult( - protocols=protocols, - credential=suggest.get_credential(), - ) - ) - break - if protocols: - break + oid = cred.oids or CHECK_OIDS + status, message = self.check_oid(oid[0], cred.snmp_ro, f"{protocol.config.alias}_get") + if not status and not message: + message = "SNMP Timeout" + # self.logger.info( + # "Guessed community: %s, version: %d", + # config.snmp_ro, + # config.protocol.config.snmp_version, + # ) + return ProtocolResult( + protocol=protocol, + status=status, + error=message, + credential=cred, + ) - def do_cli_check(self): + def do_cli_check(self, protocol: Protocol, cred: CLICredential) -> ProtocolResult: """ - Iter CLI Credential - :return: user, password, enable_password + Check suggest CLIT config + :param protocol: + :param cred: Credential for Check + :return: """ if self.ignoring_cli: - return - protocols = [] - refused_proto: Set[Protocol] = set() - for suggest in self.iter_suggests(SUGGEST_CLI): - for proto in suggest.protocols: - if proto in refused_proto: - continue - result, message = self.check_login( - suggest.user, suggest.password, suggest.super_password, protocol=proto - ) - if result: - protocols.append(ProtocolResult(protocol=proto, status=True)) - elif self.is_unsupported_error(message): - refused_proto.add(proto) - protocols.append(ProtocolResult(protocol=proto, status=False, error=message)) - if protocols: - self.result.append( - SuggestResult( - protocols=protocols, - credential=suggest.get_credential(), - ) - ) - break + # Skipped + return ProtocolResult(protocol=protocol, status=True, skipped=True) + status, message = self.check_login( + cred.user, + cred.password, + cred.super_password, + protocol=protocol, + raise_privilege=cred.raise_privilege, + ) + return ProtocolResult( + protocol=protocol, + status=status, + error=message, + credential=cred, + ) def check_oid(self, oid: str, community: str, version="snmp_v2c_get") -> Tuple[bool, str]: """ @@ -391,7 +337,7 @@ class CredentialChecker(object): try: result, message = open_sync_rpc( "activator", pool=self.pool, calling_service=self.calling_service - ).__getattr__(version)(self.address, community, oid, 10, True) + ).__getattr__(version)(self.address, community, oid, 5, True) self.logger.info("Result: %s (%s)", result, message) return result is not None, message or "" except RPCError as e: @@ -399,7 +345,12 @@ class CredentialChecker(object): return False, str(e) def check_login( - self, user: str, password: str, super_password: str, protocol: Protocol + self, + user: str, + password: str, + super_password: str, + protocol: Protocol, + raise_privilege: bool = True, ) -> Tuple[bool, str]: """ Check user, password for cli proto @@ -407,6 +358,7 @@ class CredentialChecker(object): :param password: :param super_password: :param protocol: + :param raise_privilege: :return: """ self.logger.debug("Checking %s: %s/%s/%s", protocol, user, password, super_password) @@ -429,8 +381,8 @@ class CredentialChecker(object): "password": password, "super_password": super_password, "path": None, - "raise_privileges": "E", - "access_preference": "C", + "raise_privileges": False, + "access_preference": raise_privilege, }, ) self.logger.info("Result: %s, %s", r, r["message"]) @@ -439,46 +391,22 @@ class CredentialChecker(object): self.logger.debug("RPC Error: %s", e) return False, "" - def get_auth_profile(self, credential: Credential) -> Optional[AuthProfile]: + def get_first(self, protocols: Iterable[Protocol]) -> List[ProtocolResult]: """ - Find Auth Profile for suggest credential + Get first result + :param protocols: :return: """ - # Combination suggest for auth_profile - for ap in self.auth_profiles: - if ( - ap.snmp_ro == credential.snmp_ro - and ap.user == credential.user - and ap.password == credential.password - and ap.super_password == credential.super_password + processed_proto = set() + result = [] + for sr in self.iter_result(protocols): + if not sr.status and not result: + # Skip failed result + continue + elif ( + result and result[-1].credential != sr.credential and sr.protocol in processed_proto ): - return ap - - # def get_credential(self) -> Optional[Credential]: - # """ - # Return Address Credential - # :return: - # """ - # if not self.result: - # return - # protocols = [] - # snmp_ro, snmp_rw = None, None - # user, password, super_password = None, None, None - # for sc in self.result: - # if set(SUGGEST_SNMP).intersection(set(sc.protocols)): - # protocols += list(sc.protocols) - # snmp_ro = sc.credential.snmp_ro - # snmp_rw = sc.credential.snmp_rw - # if set(SUGGEST_CLI).intersection(set(sc.protocols)): - # protocols += list(sc.protocols) - # user = sc.credential.user - # password = sc.credential.password - # super_password = sc.credential.super_password - # return Credential( - # protocols=protocols, - # user=user, - # password=password, - # super_password=super_password, - # snmp_ro=snmp_ro, - # snmp_rw=snmp_rw, - # ) + break + result.append(sr) + processed_proto.add(sr.protocol) + return result diff --git a/core/script/scheme.py b/core/script/scheme.py index 03d90d88235af8f96b5a7b3c51b4a80b4e3595d5..a4b8b5eef26a0c5c8dcd2e7b432d4ea9bed9df67 100644 --- a/core/script/scheme.py +++ b/core/script/scheme.py @@ -5,6 +5,11 @@ # See LICENSE for details # ---------------------------------------------------------------------- +# Python modules +import enum +from dataclasses import dataclass +from typing import Optional + TELNET = 1 SSH = 2 HTTP = 3 @@ -23,3 +28,39 @@ PROTOCOLS = {TELNET: "telnet", SSH: "ssh", HTTP: "http", HTTPS: "https", BEEF: " CLI_PROTOCOLS = {TELNET, SSH, BEEF} HTTP_PROTOCOLS = {HTTP, HTTPS} + + +@dataclass(frozen=True) +class ProtoConfig(object): + alias: str + check: Optional[str] = None + snmp_version: Optional[int] = None + is_http: bool = False + is_cli: bool = False + + +CONFIGS = { + 1: ProtoConfig("telnet", is_cli=True, check="TELNET"), + 2: ProtoConfig("ssh", is_cli=True, check="SSH"), + 3: ProtoConfig("http", is_http=True, check="HTTP"), + 4: ProtoConfig("https", is_http=True, check="HTTPS"), + 5: ProtoConfig("beef", is_cli=True), + 6: ProtoConfig("snmp_v1", snmp_version=0, check="SNMPv1"), + 7: ProtoConfig("snmp_v2c", snmp_version=1, check="SNMPv2c"), + 8: ProtoConfig("snmp_v3", snmp_version=3), +} + + +class Protocol(enum.Enum): + @property + def config(self): + return CONFIGS[self.value] + + TELNET = 1 + SSH = 2 + HTTP = 3 + HTTPS = 4 + BEEF = 5 + SNMPv1 = 6 + SNMPv2c = 7 + SNMPv3 = 8 diff --git a/core/text.py b/core/text.py index f82dd3805bbd7b264a6261494e43e82cf154ef91..f1622a61d60ed3597a14d7f79ca3e0d99e5139dd 100644 --- a/core/text.py +++ b/core/text.py @@ -7,6 +7,7 @@ # Python modules import re +import string from itertools import zip_longest # Third-party modules @@ -650,3 +651,7 @@ def split_text(text: str, max_chunk: int) -> Iterable[str]: result = [line] else: yield "\n".join(result) + + +def filter_non_printable(text: str) -> str: + return "".join(filter(lambda x: x in string.printable, text)) diff --git a/sa/migrations/0234_auth_profile_suggests_rule.py b/sa/migrations/0234_auth_profile_suggests_rule.py index 3341dfa8b39cc2e83e40bbcf477be210294c62d6..41723dc35ae96775f0ca97d3563a914658333725 100644 --- a/sa/migrations/0234_auth_profile_suggests_rule.py +++ b/sa/migrations/0234_auth_profile_suggests_rule.py @@ -79,7 +79,7 @@ class Migration(BaseMigration): # Reset suggest on non-suggest profile self.db.execute( """ - UPDATE sa_authprofile SET enable_suggest_by_rule = FALSE, type = 'G' + UPDATE sa_authprofile SET enable_suggest_by_rule = TRUE, type = 'G' WHERE type = 'S' """ ) diff --git a/services/discovery/jobs/base.py b/services/discovery/jobs/base.py index a61ac50c70fe181f279c03f7d673610b511e73b7..491c59bec3d92a52d186c0892c428be8fb6b2805 100644 --- a/services/discovery/jobs/base.py +++ b/services/discovery/jobs/base.py @@ -350,8 +350,8 @@ class MODiscoveryJob(PeriodicJob): ) processed.add(p.diagnostic) # Set OK state - for diagnostic in discovery_diagnostics - processed: - self.object.set_diagnostic_state(diagnostic, state=True, changed_ts=now, bulk=bulk) + # for diagnostic in discovery_diagnostics - processed: + # self.object.set_diagnostic_state(diagnostic, state=True, changed_ts=now, bulk=bulk) if bulk: self.logger.info("Diagnostic changed: %s", ", ".join(di.diagnostic for di in bulk)) self.object.save_diagnostics(self.object.id, bulk) diff --git a/services/discovery/jobs/box/config.py b/services/discovery/jobs/box/config.py index 384976611d813851a1dc8f58d1ce129e7bff1e1a..e10f30eac9a42110649514922923d2e29f623da1 100644 --- a/services/discovery/jobs/box/config.py +++ b/services/discovery/jobs/box/config.py @@ -66,12 +66,12 @@ class ConfigCheck(DiscoveryCheck): return self.object.scripts.get_config(policy=self.object.get_config_fetch_policy()) except NOCError as e: self.logger.error("Failed to request config: %s", e) - self.set_problem( - alarm_class=self.error_map.get(e.remote_code), - message=f"RPC Error: {e}", - diagnostic="CLI" if e.remote_code in self.error_map else None, - ) - return None + if hasattr(e, "remote_code"): + self.set_problem( + alarm_class=self.error_map.get(e.remote_code), + message=f"RPC Error: {e}", + diagnostic="CLI" if e.remote_code in self.error_map else None, + ) def get_config_download(self): self.logger.info("Downloading config from external storage") diff --git a/services/discovery/jobs/periodic/diagnostic.py b/services/discovery/jobs/periodic/diagnostic.py index f7316f4ad83551ee96727893a044d3304b0898e9..cda30467e601506871e0d45b3e190385b91b71b2 100644 --- a/services/discovery/jobs/periodic/diagnostic.py +++ b/services/discovery/jobs/periodic/diagnostic.py @@ -7,6 +7,7 @@ # Python modules import datetime +import logging from typing import Dict, List, Optional, Literal, Iterable, Any from collections import defaultdict @@ -25,6 +26,7 @@ from noc.core.checkers.loader import loader from noc.core.wf.diagnostic import DiagnosticState from noc.sa.models.profile import Profile from noc.pm.models.metrictype import MetricType +from noc.core.debug import error_report class DiagnosticCheck(DiscoveryCheck): @@ -143,9 +145,9 @@ class DiagnosticCheck(DiscoveryCheck): if c.name not in self.CHECK_MAP: self.logger.warning("[%s] Unknown check. Skipping", c.name) continue - # if c in self.CHECK_CACHE: - # r.append(self.CHECK_CACHE[c]) - # continue + if self.CHECK_MAP[c.name] not in self.CHECKERS: + self.logger.warning("[%s] Unknown checker. Skipping", c.name) + continue do_checks[self.CHECK_MAP[c.name]] += [c] for checker, d_checks in do_checks.items(): checker = self.CHECKERS[checker](self.object, self.logger, "discovery") @@ -154,6 +156,8 @@ class DiagnosticCheck(DiscoveryCheck): for check in checker.iter_result(d_checks): yield check except Exception as e: + if self.logger.isEnabledFor(logging.DEBUG): + error_report() self.logger.error("[%s] Error when run checker: %s", checker.name, str(e)) def action_set_sa_profile(self, data: ProfileSet):