def _fetch_from_io(self, mode: Mode) -> AgentRawData: if mode is not Mode.CHECKING: raise MKFetcherError( f"Refusing to fetch live data during {mode.name.lower()}") agent_data, protocol = self._get_agent_data() return self._validate_decrypted_data( self._decrypt(protocol, agent_data))
def _fetch_from_io(self, mode: Mode) -> AgentRawData: if self.use_only_cache: raise MKFetcherError( "Got no data: No usable cache file present at %s" % self.file_cache.base_path ) agent_data, protocol = self._get_agent_data() return self._validate_decrypted_data(self._decrypt(protocol, agent_data))
def _decrypt_agent_data(self, output: AgentRawData) -> AgentRawData: salt_start = len(OPENSSL_SALTED_MARKER) try: # simply check if the protocol is an actual number protocol = int(output[:2]) except ValueError: raise MKFetcherError( f"Unsupported protocol version: {output[:2]!r}") encrypted_pkg = output[2:] password = self.encryption_settings["passphrase"] if protocol == 3: return AgentRawData( decrypt_aes_256_cbc_pbkdf2( ciphertext=encrypted_pkg[salt_start:], password=password, )) if protocol == 2: return AgentRawData( decrypt_aes_256_cbc_legacy( ciphertext=encrypted_pkg, password=password, digest=sha256, )) if protocol == 0: return AgentRawData( decrypt_aes_256_cbc_legacy( ciphertext=encrypted_pkg, password=password, digest=md5, )) # Support encrypted agent data with "99" header. # This was not intended, but the Windows agent accidentally sent this header # instead of "00" up to 2.0.0p1, so we keep this for a while. # Caution: "99" for real-time check data means "unencrypted"! if protocol == 99: return AgentRawData( decrypt_aes_256_cbc_legacy( ciphertext=encrypted_pkg, password=password, digest=md5, )) raise MKFetcherError(f"Unsupported protocol version: {protocol}")
def read(self, mode: Mode) -> Optional[TRawData]: if not (self.simulation or self.cache_read(mode)): return None raw_data = self._read(self.make_path(mode)) if raw_data is None and self.simulation: raise MKFetcherError( "Got no data (Simulation mode enabled and no cachefile present)" ) return raw_data
def _detect_transport_protocol(self, raw_protocol: bytes) -> TransportProtocol: try: protocol = TransportProtocol(raw_protocol) self._logger.debug( "Detected transport protocol: {protocol} ({raw_protocol!r})") return protocol except ValueError: raise MKFetcherError( f"Unknown transport protocol: {raw_protocol!r}")
def _validate_protocol(self, protocol: TransportProtocol) -> None: if protocol is TransportProtocol.TLS: return enc_setting = self.encryption_settings["use_regular"] if enc_setting == "tls": raise MKFetcherError("Refused: TLS not supported by agent") if protocol is TransportProtocol.PLAIN: if enc_setting in ("disable", "allow"): return raise MKFetcherError( "Agent output is plaintext but encryption is enforced by configuration" ) if enc_setting == "disable": raise MKFetcherError( "Agent output is encrypted but encryption is disabled by configuration" )
def _fetch_from_io(self, mode: Mode) -> AgentRawData: if self._process is None: raise MKFetcherError("No process") # ? do they have the default byte type, because in open() none of the "text", "encoding", # "errors", "universal_newlines" were specified? stdout, stderr = self._process.communicate( input=self.stdin.encode() if self.stdin else None) if self._process.returncode == 127: exepath = self.cmdline.split()[ 0] # for error message, hide options! # ? exepath is AnyStr raise MKFetcherError("Program '%s' not found (exit code 127)" % ensure_str(exepath) # pylint: disable= six-ensure-str-bin-call ) if self._process.returncode: raise MKFetcherError("Agent exited with code %d: %s" % ( self._process.returncode, ensure_str(stderr).strip(), # pylint: disable= six-ensure-str-bin-call )) return stdout
def __enter__(self) -> 'Fetcher': """Prepare the data source.""" try: self.open() except MKFetcherError: raise except Exception as exc: if cmk.utils.debug.enabled(): raise raise MKFetcherError(repr(exc) if any(exc.args) else type(exc).__name__) from exc return self
def __enter__(self) -> "Fetcher": """Prepare the data source. Only needed if simulation mode is disabled""" if self.file_cache.simulation: return self try: self.open() except MKFetcherError: raise except Exception as exc: raise MKFetcherError(repr(exc) if any(exc.args) else type(exc).__name__) from exc return self
def _recvall(self, sock: socket.socket, flags: int = 0) -> bytes: self._logger.debug("Reading data from agent") buffer: List[bytes] = [] try: while True: data = sock.recv(4096, flags) if not data: break buffer.append(data) except socket.error as e: if cmk.utils.debug.enabled(): raise raise MKFetcherError("Communication failed: %s" % e) return b"".join(buffer)
def _real_decrypt(self, output: AgentRawData) -> AgentRawData: SALTED_MARKER = b"Salted__" salt_start = len(SALTED_MARKER) try: # simply check if the protocol is an actual number protocol = int(output[:2]) except ValueError: raise MKFetcherError("Unsupported protocol version: %r" % output[:2]) encrypted_pkg = output[2:] encryption_key = self.encryption_settings["passphrase"] if protocol == 3: return self._decrypt_aes_256_cbc_pbkdf2(encrypted_pkg[salt_start:], encryption_key) if protocol == 2: return self._decrypt_aes_256_cbc_legacy(encrypted_pkg, encryption_key, sha256) if protocol == 0: return self._decrypt_aes_256_cbc_legacy(encrypted_pkg, encryption_key, md5) raise MKFetcherError(f"Unsupported protocol version: {protocol}")
def open(self) -> None: self._logger.debug( "Connecting to %s:623 (User: %s, Privlevel: 2)", self.address or "local", self.username or "no user", ) # Performance: See header. import pyghmi.ipmi.command as ipmi_cmd # type: ignore[import] try: self._command = ipmi_cmd.Command( bmc=self.address, userid=self.username, password=self.password, privlevel=2, ) except IpmiException as exc: raise MKFetcherError("IPMI connection failed") from exc
def _decrypt(self, protocol: TransportProtocol, output: AgentRawData) -> AgentRawData: if not output: return output # nothing to to, validation will fail if protocol is TransportProtocol.PLAIN: return protocol.value + output # bring back stolen bytes self._logger.debug("Try to decrypt output") try: return AgentRawData( decrypt_by_agent_protocol( self.encryption_settings["passphrase"], protocol, output, ) ) except Exception as e: raise MKFetcherError("Failed to decrypt agent output: %s" % e) from e
def _firmware_section(self) -> AgentRawData: if self._command is None: raise MKFetcherError("Not connected") self._logger.debug("Fetching firmware information via UDP from %s:623", self._command.bmc) try: firmware_entries = self._command.get_firmware() except Exception as e: self._logger.log(VERBOSE, "Failed to fetch firmware information: %r", e) self._logger.debug("Exception", exc_info=True) return AgentRawData(b"") output = b"<<<mgmt_ipmi_firmware:sep(124)>>>\n" for entity_name, attributes in firmware_entries: for attribute_name, value in attributes.items(): output += b"|".join(f.encode("utf8") for f in (entity_name, attribute_name, value)) output += b"\n" return AgentRawData(output)
def open(self) -> None: verify_ipaddress(self.address[0]) self._logger.debug( "Connecting via TCP to %s:%d (%ss timeout)", self.address[0], self.address[1], self.timeout, ) self._opt_socket = socket.socket(self.family, socket.SOCK_STREAM) try: self._socket.settimeout(self.timeout) self._socket.connect(self.address) self._socket.settimeout(None) except socket.error as e: self._close_socket() if cmk.utils.debug.enabled(): raise raise MKFetcherError("Communication failed: %s" % e)
def _fetch(self, mode: Mode) -> TRawData: raw_data = self.file_cache.read(mode) if raw_data is not None: return raw_data self._logger.log(VERBOSE, "[%s] Execute data source", self.__class__.__name__) try: self.open() raw_data = self._fetch_from_io(mode) except MKFetcherError: raise except Exception as exc: raise MKFetcherError( repr(exc) if any(exc.args) else type(exc).__name__) from exc self.file_cache.write(raw_data, mode) return raw_data
def _get_agent_data(self) -> Tuple[AgentRawData, TransportProtocol]: try: raw_protocol = self._socket.recv(2, socket.MSG_WAITALL) except socket.error as e: raise MKFetcherError(f"Communication failed: {e}") from e protocol = self._detect_transport_protocol(raw_protocol) self._validate_protocol(protocol) if protocol is TransportProtocol.TLS: with self._wrap_tls() as ssock: agent_data = self._recvall(ssock) return AgentRawData( agent_data[2:]), self._detect_transport_protocol( agent_data[:2]) return AgentRawData(self._recvall(self._socket, socket.MSG_WAITALL)), protocol
def _raw_data(self) -> AgentRawData: self._logger.debug("Reading data from agent") if not self._socket: return AgentRawData(b"") def recvall(sock: socket.socket) -> bytes: buffer: List[bytes] = [] while True: data = sock.recv(4096, socket.MSG_WAITALL) if not data: break buffer.append(data) return b"".join(buffer) try: return AgentRawData(recvall(self._socket)) except socket.error as e: if cmk.utils.debug.enabled(): raise raise MKFetcherError("Communication failed: %s" % e)
def _sensors_section(self) -> AgentRawData: if self._command is None: raise MKFetcherError("Not connected") self._logger.debug("Fetching sensor data via UDP from %s:623", self._command.bmc) # Performance: See header. import pyghmi.ipmi.sdr as ipmi_sdr # type: ignore[import] try: sdr = ipmi_sdr.SDR(self._command) except NotImplementedError as e: self._logger.log(VERBOSE, "Failed to fetch sensor data: %r", e) self._logger.debug("Exception", exc_info=True) return AgentRawData(b"") sensors = [] has_no_gpu = not self._has_gpu() for ident in sdr.get_sensor_numbers(): sensor = sdr.sensors[ident] rsp = self._command.raw_command(command=0x2d, netfn=4, rslun=sensor.sensor_lun, data=(sensor.sensor_number, )) if 'error' in rsp: continue reading = sensor.decode_sensor_reading(rsp['data']) if reading is not None: # sometimes (wrong) data for GPU sensors is reported, even if # not installed if "GPU" in reading.name and has_no_gpu: continue sensors.append( IPMIFetcher._parse_sensor_reading(sensor.sensor_number, reading)) return AgentRawData(b"<<<ipmi_sensors:sep(124)>>>\n" + b"".join(b"|".join(sensor) + b"\n" for sensor in sensors))
def read(self, mode: Mode) -> Optional[TRawData]: if self.disabled: self._logger.debug("Not using cache (Cache usage disabled)") return None path = self.make_path(mode) if not path.exists(): self._logger.debug("Not using cache (Does not exist)") return None if not (self.simulation or mode is not Mode.FORCE_SECTIONS): self._logger.debug("Not using cache (Mode %s)", mode) return None raw_data = self._read(path) if raw_data is None and self.simulation: raise MKFetcherError( "Got no data (Simulation mode enabled and no cachefile present)" ) if raw_data is not None: self._logger.debug("Got %r bytes data from cache", len(raw_data)) return raw_data
def _validate_decrypted_data(self, output: AgentRawData) -> AgentRawData: if len(output) < 16: raise MKFetcherError( f"Too short payload from agent at {self.address[0]}:{self.address[1]}: {output!r}" ) return output
def _fetch_from_io(self, mode: Mode) -> AgentRawData: if self._command is None: raise MKFetcherError("Not connected") return AgentRawData(b"" + self._sensors_section() + self._firmware_section())
def _socket(self) -> socket.socket: if self._opt_socket is None: raise MKFetcherError("Not connected") return self._opt_socket
def read(self) -> Optional[TRawData]: raw_data = self._read() if raw_data is None and self.simulation: raise MKFetcherError("Got no data (Simulation mode enabled and no cachefile present)") return raw_data