From 97ac6146a268dc17f4dcaa07b12ce6ba917042fe Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 10:35:03 +0500 Subject: [PATCH 1/9] Simplify credential diagnostic. --- core/checkers/cliprotocolchecker.py | 68 +++ core/checkers/credentialchecker.py | 59 --- core/checkers/snmpprotocolchecker.py | 57 +++ core/script/credentialchecker.py | 435 +++++++----------- core/script/scheme.py | 41 ++ .../discovery/jobs/periodic/diagnostic.py | 6 +- 6 files changed, 324 insertions(+), 342 deletions(-) create mode 100644 core/checkers/cliprotocolchecker.py delete mode 100644 core/checkers/credentialchecker.py create mode 100644 core/checkers/snmpprotocolchecker.py diff --git a/core/checkers/cliprotocolchecker.py b/core/checkers/cliprotocolchecker.py new file mode 100644 index 0000000000..c94d70c6de --- /dev/null +++ b/core/checkers/cliprotocolchecker.py @@ -0,0 +1,68 @@ +# ---------------------------------------------------------------------- +# 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.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, + ) + 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 + for proto_r in cc.iter_result(protocols): + if not protocols: + break + 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) + yield CheckResult( + check=proto_r.protocol.config.check, + status=proto_r.status, + error=proto_r.error, + skipped=proto_r.skipped, + action=action, + ) diff --git a/core/checkers/credentialchecker.py b/core/checkers/credentialchecker.py deleted file mode 100644 index 508a30ecb6..0000000000 --- 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/snmpprotocolchecker.py b/core/checkers/snmpprotocolchecker.py new file mode 100644 index 0000000000..8e2e7c08dd --- /dev/null +++ b/core/checkers/snmpprotocolchecker.py @@ -0,0 +1,57 @@ +# ---------------------------------------------------------------------- +# 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 +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]: + cc = CredentialCheckerScript( + self.object.address, + self.object.pool, + self.object.effective_labels, + 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 + for proto_r in cc.iter_result(protocols): + if not protocols: + break + 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: + protocols.pop(proto_r.protocol, None) + yield CheckResult( + check=proto_r.protocol.config.check, + status=proto_r.status, + error=proto_r.error, + skipped=proto_r.skipped, + action=action, + ) diff --git a/core/script/credentialchecker.py b/core/script/credentialchecker.py index add4ed80da..6e6cb8129e 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,56 +32,12 @@ 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 -@dataclass(frozen=Tuple) +@dataclass(frozen=True) class CLICredential(object): user: Optional[str] = None password: Optional[str] = None @@ -92,8 +46,8 @@ class CLICredential(object): @dataclass(frozen=True) class SuggestSNMPConfig(object): - preference: int - protocols: Tuple[Protocol, ...] + protocol: Protocol + check_method: str = "snmp_check" snmp_ro: Optional[str] = None snmp_rw: Optional[str] = None check_oids: Optional[Tuple[str, ...]] = None @@ -104,8 +58,8 @@ class SuggestSNMPConfig(object): @dataclass(frozen=True) class SuggestCLIConfig(object): - preference: int - protocols: Tuple[Protocol, ...] + protocol: Protocol + check_method: str = "cli_check" user: Optional[str] = None password: Optional[str] = None super_password: Optional[str] = None @@ -116,21 +70,13 @@ class SuggestCLIConfig(object): ) -@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 +86,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, @@ -150,6 +95,7 @@ class CredentialChecker(object): logger=None, profile: Optional[str] = None, calling_service: str = "credentialchecker", + credentials: Optional[List[Union[SuggestCLIConfig, SuggestSNMPConfig]]] = None, ): self.address = address self.pool = pool @@ -161,22 +107,25 @@ 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 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 +138,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 +153,126 @@ 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) - 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 - ) - snmp_protocols = self.merge_protocols( - SUGGEST_SNMP, protocols, cc.suggest_protocols, order=sp + ccr = ccr.filter( + (m_q(match__labels__in=self.labels, match__exclude_labels__nin=self.labels)) + | m_q(match__labels__exists=False) ) - 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, - user=ap.user, - password=ap.password, - super_password=ap.super_password, - ) - 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 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 + # For custom credential + for c in self.credentials: + for proto in self.iter_protocols( + SUGGEST_PROTOCOLS, protocols, order=protocols or SUGGEST_PROTOCOLS + ): + if isinstance(c, CLICredential) and proto in SUGGEST_CLI: + yield SuggestCLIConfig( + protocol=proto, + user=c.user, + password=c.password, + super_password=c.super_password, ) - if ss not in r: - yield ss - r.add(ss) - if cli_protocols: - for sc in cc.suggest_credential: - sc = SuggestCLIConfig( - cc.preference, - cli_protocols, - user=sc.user, - password=sc.password, - super_password=sc.super_password, + elif isinstance(c, SNMPCredential) and proto in SUGGEST_SNMP: + yield SuggestSNMPConfig( + proto=proto, + snmp_ro=c.snmp_ro, + snmp_rw=c.snmp_rw, ) - if sc not in r: - yield sc - r.add(sc) - # return r + for cc in ccr.read_preference(ReadPreference.SECONDARY_PREFERRED).order_by("preference"): + # Suggest protocol order + protocol_order = cc.suggest_protocols or protocols or SUGGEST_PROTOCOLS + # CLI + for proto in self.iter_protocols( + SUGGEST_PROTOCOLS, protocols, cc.suggest_protocols, order=protocol_order + ): + for ap in cc.suggest_auth_profile: + if proto in SUGGEST_CLI and (ap.user or ap.password): + yield SuggestCLIConfig( + proto, + user=ap.user, + password=ap.password, + super_password=ap.super_password, + ) + if proto in SUGGEST_SNMP and (ap.snmp_ro or ap.snmp_rw): + yield SuggestSNMPConfig(proto, snmp_ro=ap.snmp_ro, snmp_rw=ap.snmp_rw) + if proto in SUGGEST_CLI: + for sc in cc.suggest_credential: + yield SuggestCLIConfig( + proto, + user=sc.user, + password=sc.password, + super_password=sc.super_password, + ) + if proto in SUGGEST_SNMP: + for ss in cc.suggest_snmp: + yield SuggestSNMPConfig(proto, snmp_ro=ss.snmp_ro, snmp_rw=ss.snmp_rw) - 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 - 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) - 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") - 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): - """ - - :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 + if unsupported_proto and suggest.protocol in unsupported_proto: + # Skip unsupported proto + continue + if success_proto and suggest.protocol in success_proto: + continue + if suggest in processed: + # Skip already checked credential + continue + self.logger.debug("Trying suggest: %s", suggest) + 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(suggest) + 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(suggest.protocol) + processed.add(suggest) + yield p_check + + def do_snmp_check(self, config: SuggestSNMPConfig) -> ProtocolResult: + oid = config.check_oids or CHECK_OIDS + status, message = self.check_oid( + oid[0], config.snmp_ro, f"{config.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=config.protocol, + status=status, + error=message, + credential=config.get_credential(), + ) - def do_cli_check(self): - """ - Iter CLI Credential - :return: user, password, enable_password - """ + def do_cli_check(self, config: SuggestCLIConfig) -> ProtocolResult: 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=config.protocol, status=True, skipped=True) + status, message = self.check_login( + config.user, config.password, config.super_password, protocol=config.protocol + ) + return ProtocolResult( + protocol=config.protocol, + status=status, + error=message, + credential=config.get_credential(), + ) def check_oid(self, oid: str, community: str, version="snmp_v2c_get") -> Tuple[bool, str]: """ @@ -391,7 +290,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: @@ -439,46 +338,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 03d90d8823..a4b8b5eef2 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/services/discovery/jobs/periodic/diagnostic.py b/services/discovery/jobs/periodic/diagnostic.py index f7316f4ad8..2fe1916098 100644 --- a/services/discovery/jobs/periodic/diagnostic.py +++ b/services/discovery/jobs/periodic/diagnostic.py @@ -143,9 +143,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") -- GitLab From 6bc3cc8850ee62c5e54a1620c358a64da0f34341 Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 10:41:10 +0500 Subject: [PATCH 2/9] Fix black. --- core/checkers/cliprotocolchecker.py | 14 ++++++++------ core/checkers/snmpprotocolchecker.py | 11 ++++++++++- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/core/checkers/cliprotocolchecker.py b/core/checkers/cliprotocolchecker.py index c94d70c6de..971830061a 100644 --- a/core/checkers/cliprotocolchecker.py +++ b/core/checkers/cliprotocolchecker.py @@ -25,12 +25,14 @@ class CLIProtocolChecker(ObjectChecker): def iter_result(self, checks=None) -> Iterable[CheckResult]: credential = None - if self.object.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 - )] + 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, diff --git a/core/checkers/snmpprotocolchecker.py b/core/checkers/snmpprotocolchecker.py index 8e2e7c08dd..e65348ac63 100644 --- a/core/checkers/snmpprotocolchecker.py +++ b/core/checkers/snmpprotocolchecker.py @@ -10,7 +10,7 @@ from typing import List, Iterable, Dict # NOC modules from .base import Check, ObjectChecker, CheckResult, CredentialSet -from ..script.credentialchecker import CredentialChecker as CredentialCheckerScript +from ..script.credentialchecker import CredentialChecker as CredentialCheckerScript, SNMPCredential from ..script.scheme import Protocol @@ -24,10 +24,19 @@ class SNMPProtocolChecker(ObjectChecker): 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, + credential=credential, logger=self.logger, calling_service=self.calling_service, ) -- GitLab From a9a1bf8ed23b7c65134141c5d5388834181ce93d Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 11:28:03 +0500 Subject: [PATCH 3/9] Fix typo. --- core/checkers/snmpprotocolchecker.py | 6 +++--- sa/migrations/0234_auth_profile_suggests_rule.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/checkers/snmpprotocolchecker.py b/core/checkers/snmpprotocolchecker.py index e65348ac63..db243dcd9c 100644 --- a/core/checkers/snmpprotocolchecker.py +++ b/core/checkers/snmpprotocolchecker.py @@ -36,7 +36,7 @@ class SNMPProtocolChecker(ObjectChecker): self.object.address, self.object.pool, self.object.effective_labels, - credential=credential, + credentials=credential, logger=self.logger, calling_service=self.calling_service, ) @@ -55,8 +55,8 @@ class SNMPProtocolChecker(ObjectChecker): action = CredentialSet( snmp_ro=proto_r.credential.snmp_ro, snmp_rw=proto_r.credential.snmp_rw ) - if action: - protocols.pop(proto_r.protocol, None) + if action and proto_r.protocol in protocols: + protocols.remove(proto_r.protocol) yield CheckResult( check=proto_r.protocol.config.check, status=proto_r.status, diff --git a/sa/migrations/0234_auth_profile_suggests_rule.py b/sa/migrations/0234_auth_profile_suggests_rule.py index 3341dfa8b3..41723dc35a 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' """ ) -- GitLab From 2b9027f8281d04636800d5b91ea9a3cbdd2d0380 Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 14:51:17 +0500 Subject: [PATCH 4/9] Fix duplicate checks. --- core/checkers/cliprotocolchecker.py | 13 +++++++++---- core/checkers/snmpprotocolchecker.py | 13 +++++++++---- core/script/credentialchecker.py | 2 +- services/discovery/jobs/periodic/diagnostic.py | 4 ++++ 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/core/checkers/cliprotocolchecker.py b/core/checkers/cliprotocolchecker.py index 971830061a..15d683ba24 100644 --- a/core/checkers/cliprotocolchecker.py +++ b/core/checkers/cliprotocolchecker.py @@ -50,9 +50,13 @@ class CLIProtocolChecker(ObjectChecker): 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, @@ -61,10 +65,11 @@ class CLIProtocolChecker(ObjectChecker): ) if action and proto_r.protocol in protocols: protocols.remove(proto_r.protocol) + for x in r.values(): yield CheckResult( - check=proto_r.protocol.config.check, - status=proto_r.status, - error=proto_r.error, - skipped=proto_r.skipped, + check=x.protocol.config.check, + status=x.status, + error=x.error, + skipped=x.skipped, action=action, ) diff --git a/core/checkers/snmpprotocolchecker.py b/core/checkers/snmpprotocolchecker.py index db243dcd9c..1fa5ad5c9b 100644 --- a/core/checkers/snmpprotocolchecker.py +++ b/core/checkers/snmpprotocolchecker.py @@ -48,19 +48,24 @@ class SNMPProtocolChecker(ObjectChecker): 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=proto_r.protocol.config.check, - status=proto_r.status, - error=proto_r.error, - skipped=proto_r.skipped, + 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 6e6cb8129e..997319dbd3 100644 --- a/core/script/credentialchecker.py +++ b/core/script/credentialchecker.py @@ -173,7 +173,7 @@ class CredentialChecker(object): ) elif isinstance(c, SNMPCredential) and proto in SUGGEST_SNMP: yield SuggestSNMPConfig( - proto=proto, + protocol=proto, snmp_ro=c.snmp_ro, snmp_rw=c.snmp_rw, ) diff --git a/services/discovery/jobs/periodic/diagnostic.py b/services/discovery/jobs/periodic/diagnostic.py index 2fe1916098..cda30467e6 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): @@ -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): -- GitLab From e29103e65c44ce1e2d431256202698b5e1576f32 Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 21:49:38 +0500 Subject: [PATCH 5/9] Fix credentialchecker logic. --- core/script/credentialchecker.py | 176 +++++++++++++++++-------------- 1 file changed, 99 insertions(+), 77 deletions(-) diff --git a/core/script/credentialchecker.py b/core/script/credentialchecker.py index 997319dbd3..649de3199c 100644 --- a/core/script/credentialchecker.py +++ b/core/script/credentialchecker.py @@ -35,6 +35,7 @@ CHECK_OIDS = [ class SNMPCredential(object): snmp_ro: str = None snmp_rw: Optional[str] = None + oids: Optional[List[str]] = None @dataclass(frozen=True) @@ -46,19 +47,19 @@ class CLICredential(object): @dataclass(frozen=True) class SuggestSNMPConfig(object): - protocol: Protocol + 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): - protocol: Protocol + protocols: Tuple[Protocol, ...] check_method: str = "cli_check" user: Optional[str] = None password: Optional[str] = None @@ -159,52 +160,61 @@ class CredentialChecker(object): (m_q(match__labels__in=self.labels, match__exclude_labels__nin=self.labels)) | m_q(match__labels__exists=False) ) - # For custom credential + # Try custom credential first for c in self.credentials: - for proto in self.iter_protocols( - SUGGEST_PROTOCOLS, protocols, order=protocols or SUGGEST_PROTOCOLS + if isinstance(c, CLICredential) and ( + Protocol(1) in protocols or Protocol(2) in protocols ): - if isinstance(c, CLICredential) and proto in SUGGEST_CLI: - yield SuggestCLIConfig( - protocol=proto, - user=c.user, - password=c.password, - super_password=c.super_password, - ) - elif isinstance(c, SNMPCredential) and proto in SUGGEST_SNMP: - yield SuggestSNMPConfig( - protocol=proto, - snmp_ro=c.snmp_ro, - snmp_rw=c.snmp_rw, - ) + yield SuggestCLIConfig( + protocols=(Protocol(1), Protocol(2)), + user=c.user, + password=c.password, + super_password=c.super_password, + ) + 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"): # 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 = tuple( + self.iter_protocols( + SUGGEST_SNMP, protocols, cc.suggest_protocols, order=protocol_order + ) + ) # CLI - for proto in self.iter_protocols( - SUGGEST_PROTOCOLS, protocols, cc.suggest_protocols, order=protocol_order - ): - for ap in cc.suggest_auth_profile: - if proto in SUGGEST_CLI and (ap.user or ap.password): - yield SuggestCLIConfig( - proto, - user=ap.user, - password=ap.password, - super_password=ap.super_password, - ) - if proto in SUGGEST_SNMP and (ap.snmp_ro or ap.snmp_rw): - yield SuggestSNMPConfig(proto, snmp_ro=ap.snmp_ro, snmp_rw=ap.snmp_rw) - if proto in SUGGEST_CLI: - for sc in cc.suggest_credential: - yield SuggestCLIConfig( - proto, - user=sc.user, - password=sc.password, - super_password=sc.super_password, - ) - if proto in SUGGEST_SNMP: - for ss in cc.suggest_snmp: - yield SuggestSNMPConfig(proto, snmp_ro=ss.snmp_ro, snmp_rw=ss.snmp_rw) + for ap in cc.suggest_auth_profile: + ap = ap.auth_profile + if cli and (ap.user or ap.password): + yield SuggestCLIConfig( + cli, + user=ap.user, + password=ap.password, + super_password=ap.super_password, + ) + if snmp and (ap.snmp_ro or ap.snmp_rw): + yield SuggestSNMPConfig(snmp, snmp_ro=ap.snmp_ro, snmp_rw=ap.snmp_rw) + if cli: + for sc in cc.suggest_credential: + yield SuggestCLIConfig( + cli, + user=sc.user, + password=sc.password, + super_password=sc.super_password, + ) + if snmp: + for ss in cc.suggest_snmp: + yield SuggestSNMPConfig(snmp, snmp_ro=ss.snmp_ro, snmp_rw=ss.snmp_rw) def iter_result( self, protocols: Optional[Iterable[Protocol]] = None, first_success: bool = True @@ -219,33 +229,39 @@ class CredentialChecker(object): success_proto = set() processed = set() for suggest in self.iter_suggests(protocols): - if unsupported_proto and suggest.protocol in unsupported_proto: - # Skip unsupported proto - continue - if success_proto and suggest.protocol in success_proto: - continue - if suggest in processed: - # Skip already checked credential - continue - self.logger.debug("Trying suggest: %s", suggest) - 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(suggest) - 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(suggest.protocol) - processed.add(suggest) - yield p_check - - def do_snmp_check(self, config: SuggestSNMPConfig) -> ProtocolResult: - oid = config.check_oids or CHECK_OIDS - status, message = self.check_oid( - oid[0], config.snmp_ro, f"{config.protocol.config.alias}_get" - ) + cred = suggest.get_credential() + for protocol in suggest.protocols: + if unsupported_proto and protocol in unsupported_proto: + # Skip unsupported proto + continue + if success_proto and protocol in success_proto: + continue + if (protocol, cred) in processed: + # Skip already checked credential + continue + 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: + """ + 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( @@ -254,24 +270,30 @@ class CredentialChecker(object): # config.protocol.config.snmp_version, # ) return ProtocolResult( - protocol=config.protocol, + protocol=protocol, status=status, error=message, - credential=config.get_credential(), + credential=cred, ) - def do_cli_check(self, config: SuggestCLIConfig) -> ProtocolResult: + def do_cli_check(self, protocol: Protocol, cred: CLICredential) -> ProtocolResult: + """ + Check suggest CLIT config + :param protocol: + :param cred: Credential for Check + :return: + """ if self.ignoring_cli: # Skipped - return ProtocolResult(protocol=config.protocol, status=True, skipped=True) + return ProtocolResult(protocol=protocol, status=True, skipped=True) status, message = self.check_login( - config.user, config.password, config.super_password, protocol=config.protocol + cred.user, cred.password, cred.super_password, protocol=protocol ) return ProtocolResult( - protocol=config.protocol, + protocol=protocol, status=status, error=message, - credential=config.get_credential(), + credential=cred, ) def check_oid(self, oid: str, community: str, version="snmp_v2c_get") -> Tuple[bool, str]: -- GitLab From 869de2c7783e86e5f1dcc83ed702443174e96b3e Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 22:00:15 +0500 Subject: [PATCH 6/9] Fix profile error size. --- core/checkers/profilechecker.py | 7 ++++++- core/text.py | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/checkers/profilechecker.py b/core/checkers/profilechecker.py index 675d45133c..9e3691da0f 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/text.py b/core/text.py index f82dd3805b..f1622a61d6 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)) -- GitLab From 282ae60e54b2de538d11a77a9b446c8824843c06 Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Tue, 27 Sep 2022 23:24:00 +0500 Subject: [PATCH 7/9] Add raise_privilege as credential param. --- core/checkers/cliprotocolchecker.py | 1 + core/script/credentialchecker.py | 45 ++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/core/checkers/cliprotocolchecker.py b/core/checkers/cliprotocolchecker.py index 15d683ba24..d31ee1dda9 100644 --- a/core/checkers/cliprotocolchecker.py +++ b/core/checkers/cliprotocolchecker.py @@ -41,6 +41,7 @@ class CLIProtocolChecker(ObjectChecker): 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 []: diff --git a/core/script/credentialchecker.py b/core/script/credentialchecker.py index 649de3199c..602acf2271 100644 --- a/core/script/credentialchecker.py +++ b/core/script/credentialchecker.py @@ -43,6 +43,7 @@ class CLICredential(object): user: Optional[str] = None password: Optional[str] = None super_password: Optional[str] = None + raise_privilege: bool = True @dataclass(frozen=True) @@ -64,10 +65,15 @@ class SuggestCLIConfig(object): 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, ) @@ -95,6 +101,7 @@ 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, ): @@ -110,6 +117,7 @@ class CredentialChecker(object): 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 @@ -170,6 +178,7 @@ class CredentialChecker(object): 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 @@ -201,9 +210,15 @@ class CredentialChecker(object): user=ap.user, password=ap.password, super_password=ap.super_password, + raise_privileges=self.raise_privilege, ) if snmp and (ap.snmp_ro or ap.snmp_rw): - yield SuggestSNMPConfig(snmp, snmp_ro=ap.snmp_ro, snmp_rw=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 cli: for sc in cc.suggest_credential: yield SuggestCLIConfig( @@ -211,10 +226,16 @@ class CredentialChecker(object): 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) + yield SuggestSNMPConfig( + snmp, + snmp_ro=ss.snmp_ro, + snmp_rw=ss.snmp_rw, + check_oids=cc.suggest_snmp_oids or None, + ) def iter_result( self, protocols: Optional[Iterable[Protocol]] = None, first_success: bool = True @@ -287,7 +308,11 @@ class CredentialChecker(object): # Skipped return ProtocolResult(protocol=protocol, status=True, skipped=True) status, message = self.check_login( - cred.user, cred.password, cred.super_password, protocol=protocol + cred.user, + cred.password, + cred.super_password, + protocol=protocol, + raise_privilege=cred.raise_privilege, ) return ProtocolResult( protocol=protocol, @@ -320,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 @@ -328,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) @@ -350,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"]) -- GitLab From c6c862fb96919a23dcdae29cdcc754cf53f3cad8 Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Wed, 28 Sep 2022 15:12:41 +0500 Subject: [PATCH 8/9] Disable diagnostic OK State on discovery problem sync. --- services/discovery/jobs/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/discovery/jobs/base.py b/services/discovery/jobs/base.py index a61ac50c70..491c59bec3 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) -- GitLab From addb8fe028404ded4617f2c275fc5c3dd1452e0e Mon Sep 17 00:00:00 2001 From: Andrey Vertiprahov Date: Wed, 28 Sep 2022 15:23:49 +0500 Subject: [PATCH 9/9] Fix catch error on config discovery. --- services/discovery/jobs/box/config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/discovery/jobs/box/config.py b/services/discovery/jobs/box/config.py index 384976611d..e10f30eac9 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") -- GitLab