From f8def7da4f3d75f616bfc76e6e279fe9671fb47d Mon Sep 17 00:00:00 2001 From: thl-cmk <thl-cmk@outlook.com> Date: Sat, 21 Oct 2023 17:30:14 +0000 Subject: [PATCH] Delete curl.py --- agent_based/curl.py | 561 -------------------------------------------- 1 file changed, 561 deletions(-) delete mode 100644 agent_based/curl.py diff --git a/agent_based/curl.py b/agent_based/curl.py deleted file mode 100644 index 34030d6..0000000 --- a/agent_based/curl.py +++ /dev/null @@ -1,561 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# Author: thl-cmk[at]outlook[dot]com -# URL : https://thl-cmk.hopto.org -# Date : 2022-02-15 - -# -# based on the work by Christian Wirtz doc[at]snowheaven[dot]de -# -# 2022-02-15: rewritten for cmk 2.0 -# 2022-02-20: removed url_effective from format file (bad output on linux: "url_effective":http://"https//checkmk.com") -# added num_connects, num_redirects, size_download, size_header, speed_download -# added redirect_url, remote_ip, scheme, http_version, http_connect -# 2022-02-22: added params for all values -# 2022-02-23: changed items on service info line -# 2022-02-28: added expected_strings and state_item_not_found option -# 2022-03-05: added support for search in header strings -# 2022-03-06: added support for tls/certificate information -# 2022-03-10: optimized code for perfdata -# 2022-03-12: added cafile and capath to Cert info section -# added max_age -# 2022-03-18: added regex pattern match -# 2022-03-22: added curl_error_code_to_ignore and http_error_code_to_ignore options -# 2022-04-06: removed .replace('://', ': //')) from json.loads() so service name is without space -# 2022-04-26: made http(s) URLs clickable -# 2022-05-15: added workaround for raise ValueError("Cannot render negative timespan") -# 2022-05-17: fixed wrong import path for _TIME_UNITS and _gen_timespan_chunks -# 2023-06-07: moved gui files to ~/local/lib/chek_mk/gui/plugins/... -# - -# Example output from agent: -# -# <<<curl>>> -# { -# "thl-cmk.hopto.org": { -# "url": "https://thl-cmk.hopto.org", -# "data": { -# "ssl_verify_result": "0", -# "http_return_code": "000", -# "time_namelookup": "0,000000", -# "time_connect": "0,000000", -# "time_appconnect": "0,000000", -# "time_pretransfer": "0,000000", -# "time_redirect": "0,000000", -# "time_starttransfer": "0,000000", -# "time_total": "0,000000", -# "http_connect": "000", -# "http_version": "0", -# "num_connects": "0", -# "num_redirects": "0", -# "redirect_url": "", -# "remote_ip": "", -# "scheme": "(nil)", -# "size_download": "0", -# "size_header": "0", -# "speed_download": "0,000", -# }, -# "error_level": "6" -# } -# } -# - -import json -import time -from typing import Dict, Any, List, Iterable -from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( - DiscoveryResult, - CheckResult, -) - -from cmk.base.plugins.agent_based.agent_based_api.v1 import ( - register, - Service, - State, - check_levels, - Result, - render, -) - -# -# start workaround for : raise ValueError("Cannot render negative timespan") -# -from cmk.base.api.agent_based.render import ( - _TIME_UNITS, - _gen_timespan_chunks, -) - - -def timespan(seconds: float) -> str: - """Render a time span in seconds - - Example: - >>> timespan(1606721) - '18 days 14 hours' - >>> timespan(0.0001) - '100 microseconds' - - """ - if seconds >= 0: - ts = " ".join(_gen_timespan_chunks(float(seconds), nchunks=2)) - else: - seconds = -1 * seconds - ts = " ".join(_gen_timespan_chunks(float(seconds), nchunks=2)) - ts = f"-{ts}" - if ts == "0 %s" % _TIME_UNITS[-1][0]: - ts = "0 seconds" - return ts - -# -# end workaround for : raise ValueError("Cannot render negative timespan") -# - - -_curl_error_codes = { - 1: 'Unsupported protocol. This build of curl has no support for this protocol.', - 2: 'Failed to initialize.', - 3: 'URL malformed. The syntax was not correct.', - 4: 'A feature or option that was needed to perform the desired request was not enabled or was explicitly ' - 'disabled at build-time. To make curl able to do this, you probably need another build of libcurl.', - 5: 'Could not resolve proxy. The given proxy host could not be resolved.', - 6: 'Could not resolve host. The given remote host could not be resolved.', - 7: 'Failed to connect to host.', - 8: 'Weird server reply. The server sent data curl could not parse.', - 9: 'FTP access denied. The server denied login or denied access to the particular resource or directory you ' - 'wanted to reach. Most often you tried to change to a directory that does not exist on the server.', - 10: 'FTP accept failed. While waiting for the server to connect back when an active FTP session is used, ' - 'an error code was sent over the control connection or similar.', - 11: 'FTP weird PASS reply. Curl could not parse the reply sent to the PASS request.', - 12: 'During an active FTP session while waiting for the server to connect back to curl, the timeout expired.', - 13: 'FTP weird PASV reply, Curl could not parse the reply sent to the PASV request.', - 14: 'FTP weird 227 format. Curl could not parse the 227-line the server sent.', - 15: 'FTP cannot use host. Could not resolve the host IP we got in the 227-line.', - 16: 'HTTP/2 error. A problem was detected in the HTTP2 framing layer. This is somewhat generic and can be one ' - 'out of several problems, see the error message for details.', - 17: 'FTP could not set binary. Could not change transfer method to binary.', - 18: 'Partial file. Only a part of the file was transferred.', - 19: 'FTP could not download/access the given file, the RETR (or similar) command failed.', - 21: 'FTP quote error. A quote command returned error from the server.', - 22: 'HTTP page not retrieved. The requested url was not found or returned another error with the HTTP error ' - 'code being 400 or above. This return code only appears if --fail is used.', - 23: 'Write error. Curl could not write data to a local filesystem or similar.', - 25: 'FTP could not STOR file. The server denied the STOR operation, used for FTP uploading.', - 26: 'Read error. Various reading problems.', - 27: 'Out of memory. A memory allocation request failed.', - 28: 'Operation timeout. The specified time-out period was reached according to the conditions.', - 30: 'FTP PORT failed. The PORT command failed. Not all FTP servers support the PORT command, ' - 'try doing a transfer using PASV instead!', - 31: 'FTP could not use REST. The REST command failed. This command is used for resumed FTP transfers.', - 33: 'HTTP range error. The range "command" did not work.', - 34: 'HTTP post error. Internal post-request generation error.', - 35: 'SSL connect error. The SSL handshaking failed.', - 36: 'Bad download resume. Could not continue an earlier aborted download.', - 37: 'FILE could not read file. Failed to open the file. Permissions?', - 38: 'LDAP cannot bind. LDAP bind operation failed.', - 39: 'LDAP search failed.', - 41: 'Function not found. A required LDAP function was not found.', - 42: 'Aborted by callback. An application told curl to abort the operation.', - 43: 'Internal error. A function was called with a bad parameter.', - 45: 'Interface error. A specified outgoing interface could not be used.', - 47: 'Too many redirects. When following redirects, curl hit the maximum amount.', - 48: 'Unknown option specified to libcurl. This indicates that you passed a weird option to curl that was' - ' passed on to libcurl and rejected. Read up in the manual!', - 49: 'Malformed telnet option.', - 51: 'The peer\'s SSL certificate or SSH MD5 fingerprint was not OK.', - 52: 'The server did not reply anything, which here is considered an error.', - 53: 'SSL crypto engine not found.', - 54: 'Cannot set SSL crypto engine as default.', - 55: 'Failed sending network data.', - 56: 'Failure in receiving network data.', - 58: 'Problem with the local certificate.', - 59: 'Could not use specified SSL cipher.', - 60: 'Peer certificate cannot be authenticated with known CA certificates.', - 61: 'Unrecognized transfer encoding.', - 62: 'Invalid LDAP URL.', - 63: 'Maximum file size exceeded.', - 64: 'Requested FTP SSL level failed.', - 65: 'Sending the data requires a rewind that failed.', - 66: 'Failed to initialise SSL Engine.', - 67: 'The user name, password, or similar was not accepted and curl failed to log in.', - 68: 'File not found on TFTP server.', - 69: 'Permission problem on TFTP server.', - 70: 'Out of disk space on TFTP server.', - 71: 'Illegal TFTP operation.', - 72: 'Unknown TFTP transfer ID.', - 73: 'File already exists (TFTP).', - 74: 'No such user (TFTP).', - 75: 'Character conversion failed.', - 76: 'Character conversion functions required.', - 77: 'Problem reading the SSL CA cert (path? access rights?).', - 78: 'The resource referenced in the URL does not exist.', - 79: 'An unspecified error occurred during the SSH session.', - 80: 'Failed to shut down the SSL connection.', - 82: 'Could not load CRL file, missing or wrong format.', - 83: 'Issuer check failed.', - 84: 'The FTP PRET command failed.', - 85: 'Mismatch of RTSP CSeq numbers.', - 86: 'Mismatch of RTSP Session Identifiers.', - 87: 'Unable to parse FTP file list.', - 88: 'FTP chunk callback reported error.', - 89: 'No connection available, the session will be queued.', - 90: 'SSL public key does not matched pinned public key.', - 91: 'Invalid SSL certificate status.', - 92: 'Stream error in HTTP/2 framing layer.', - 93: 'An API function was called from inside a callback.', - 94: 'An authentication function returned an error.', - 95: 'A problem was detected in the HTTP/3 layer. This is somewhat generic and can be one out of ' - 'several problems, see the error message for details.', - 96: 'QUIC connection error. This error may be caused by an SSL library error. QUIC is the ' - 'protocol used for HTTP/3 transfers.', -} - - -def _get_tls_info(tls_lines: List[str]): - tls_infos = {} - alpn_offerings = [] - for line in tls_lines: - line = line.strip() - if line.startswith('subject: '): - tls_infos.update({'subject': line[9:]}) - elif line.startswith('start date: '): - tls_infos.update({'start_date': line[12:]}) - elif line.startswith('expire date: '): - tls_infos.update({'expire_date': line[13:]}) - time_left = time.mktime( # 'Feb 16 10:18:08 2022 GMT' - time.strptime(tls_infos['expire_date'], '%b %d %H:%M:%S %Y %Z') - ) - time.mktime(time.gmtime()) - tls_infos.update({'time_left': time_left}) - elif line.startswith('subjectAltName: '): - tls_infos.update({'subject_alternate_name': line[21:]}) - elif line.startswith('issuer: '): - tls_infos.update({'issuer': line[8:]}) - elif line.startswith('ALPN, offering '): - alpn_offerings.append(line[15:]) - elif line.startswith('ALPN, server accepted to use '): - tls_infos.update({'alpn_accepted': line[29:]}) - elif line.startswith('SSL certificate verify result: '): - tls_infos.update({'ssl_verify_result': line[31:]}) - elif line.startswith('SSL connection using '): - tls_version, cipher = line[21:].split(' / ') - tls_infos.update({'tls_version_used': tls_version}) - tls_infos.update({'cipher_used': cipher}) - elif line.startswith('CAfile'): - tls_infos.update({'ca_file': line[8:]}) - elif line.startswith('CApath'): - tls_infos.update({'ca_path': line[8:]}) - - tls_infos.update({'alpn_offerings': alpn_offerings}) - - return tls_infos - - -def parse_curl(string_table): - try: - # section = json.loads(string_table[0][0].replace('://', ': //')) # removed so service name is without space - section = json.loads(string_table[0][0]) - except (IndexError, TypeError, json.JSONDecodeError): - return {} - if 'ERROR' not in section.keys(): - if len(string_table) > 1: - # insert splitter, split (and implicit remove splitter), avoid removal of '}{' - for line in string_table[1][0].replace('}{', '}|||{').split('|||'): - # '{'thl-cmk.hopto.org': {'expected_strings': [['string1', 1], ['string2', 1], ['html', 0]]}}' - try: - # line = line.replace('://', ': //') # removed so service name is without space - line = json.loads(line) - except json.JSONDecodeError: - continue - # get first key from line - key = list(line.keys())[0] - # update section data for key - section[key].update(line[key]) - for key in section.keys(): - if section[key].get('TLS_INFO'): - tls_info = _get_tls_info(section[key]['TLS_INFO']) - section[key].update({'tls_info': tls_info}) - # section[key].pop('TLS_INFO') - return section - - -def discovery_curl(section: Dict[str, Any]) -> DiscoveryResult: - for key in section.keys(): - yield Service(item=key) - - -def check_curl(item, params, section: Dict[str, Any]) -> CheckResult: - try: - _data = section[item] - except KeyError: - yield Result(state=State(params['state_item_not_found']), notice='Item not found in agent data') - return - - # url = _data['url'].replace('://', ': //') # ugly workaround to stop cmk from replacing the url - http_return_code = _data['data']['http_code'] - ssl_verify_result = _data['data']['ssl_verify_result'] - curl_error_code = int(_data['data']['exitcode']) - - # yield Result(state=State.OK, notice=f'URL from cfg: {url}') - for key, label in [ - ('url', 'URL'), - ('redirect_url', 'URL redirect'), - ('url_effective', 'URL effective'), - ('referer', 'Referer'), - ]: - if _data['data'].get(key): - value = _data['data'][key] - if value.startswith('http'): - if params.get('clickable_url'): - value = f'<a href={value} target="blank">{value}</a>' - else: - value = f'{value.replace("://", ": //")}' # ugly workaround to stop cmk from replacing the url - yield Result(state=State.OK, notice=f'{label}: {value}') - - if curl_error_code != 0: - if curl_error_code in params['curl_error_code_to_ignore']: - yield Result( - state=State.OK, - notice=f'curl error code: {curl_error_code}, {_curl_error_codes.get(curl_error_code, "N/A")}') - else: - yield Result( - state=State(params['state_curl_result_not_0']), - summary=f'curl error code: {curl_error_code} see details', - details=f'curl error code: {curl_error_code}, {_curl_error_codes.get(curl_error_code, "N/A")}' - ) - - if http_return_code < 400: # no connect, Ok, Redirect - yield Result(state=State.OK, notice=f'HTTP Return code: {http_return_code}') - else: - if http_return_code in params['http_error_code_to_ignore']: - yield Result( - state=State.OK, notice=f'HTTP Return code: {http_return_code}' - ) - else: - yield Result( - state=State(params['state_http_result_not_200']), - notice=f'HTTP Return code: {http_return_code}' - ) - - if ssl_verify_result == 0: - yield Result(state=State.OK, notice=f'SSL verify result: {ssl_verify_result}') - else: - yield Result(state=State(params['state_verify_sll_not_0']), summary=f'SSL verify result: {ssl_verify_result}') - - for key, label in [ - ('scheme', 'Scheme (protocol)'), - ('http_version', 'HTTP version'), - ('method', 'Method'), - ('content_type', 'Content type'), - ('remote_ip', 'Remote IP'), - ('http_connect', 'Proxy connect code'), - ('errormsg', 'Error message'), - ('curl_version', 'cURL version'), - ]: - if _data['data'].get(key): - yield Result(state=State.OK, notice=f'{label}: {_data["data"][key]}') - - if _data.get('curl_options'): - yield Result(state=State.OK, notice=f'cURL options: {_data["curl_options"]}') - - if _data.get('expected_response'): - for expected_string, result in _data['expected_response']: - if result == 0: - yield Result(state=State.OK, notice=f'Response string: "{expected_string}" found') - else: - yield Result( - state=State(params['state_expected_str_not_found']), - notice=f'Response string: "{expected_string}" not found' - ) - - if _data.get('expected_header'): - for header_strings, result in _data['expected_header']: - if result == 0: - yield Result(state=State.OK, notice=f'Header string: "{header_strings}" found') - else: - yield Result( - state=State(params['state_header_str_not_found']), - notice=f'Header string: "{header_strings}" not found' - ) - - regex_match, regex_no_match, regex_missing = params['state_for_regex'] - if _data.get('regex') == 0: # match - yield Result(state=State(regex_match), notice='Regex state: pattern matches') - elif _data.get('regex') == 1: # no match - yield Result(state=State(regex_no_match), notice='Regex state: pattern don\'t matches') - elif not _data.get('regex'): # missing info - yield Result(state=State(regex_missing), notice='Regex state: missing pattern match info') - - max_age_warn, max_age_crit, max_age_state = params['max_age'] - if max_age_warn: - max_age = None - if _data.get('RESPONSE_HEADER'): - for line in _data['RESPONSE_HEADER']: - if line.startswith('Last-Modified: '): - max_age = time.mktime(time.gmtime()) - time.mktime( - time.strptime(line[15:], '%a, %d %b %Y %H:%M:%S %Z')) # 'Fri, 18 Jun 2021 18:17:33 GMT' - if max_age: - yield from check_levels( - label='Last modified', - value=max_age, - render_func=render.timespan, - notice_only=True, - levels_upper=(max_age_warn, max_age_crit), - ) - else: - yield Result(state=State(max_age_state), notice='Last-Modified: Document modification date unknown') - - if _data.get('tls_info', {}).get('subject'): - yield Result(state=State.OK, notice=f' ') - yield Result(state=State.OK, notice=f'Certificate info:') - - for key, label in [ - ('subject', 'Subject'), - ('issuer', 'Issuer'), - ('subject_alternate_name', 'Subject alternate name'), - ('start_date', 'Start date'), - ('expire_date', 'Expire date'), - ('tls_version_used', 'TLS Version used'), - ('cipher_used', 'Cipher used'), - ('ssl_verify_result', 'SSL verify result'), - ('ca_file', 'CA file'), - ('ca_path', 'CA path'), - ]: - if _data['tls_info'].get(key): - yield Result(state=State.OK, notice=f'{label}: {_data["tls_info"][key]}') - - if _data['tls_info'].get('time_left'): - if params['cert_time_left'].get('upper'): - warn, crit = params['cert_time_left'].get('upper') - upper = (warn * 86400, crit * 86400) - else: - upper = None - - if params['cert_time_left'].get('lower'): - warn, crit = params['cert_time_left'].get('lower') - lower = (warn * 86400, crit * 86400) - else: - lower = None - - yield from check_levels( - label='Certificate time left', - value=_data['tls_info']['time_left'], - metric_name='cert_time_left', - render_func=timespan, - notice_only=True, - levels_upper=upper, - levels_lower=lower, - ) - - yield Result(state=State.OK, notice=f' ') - yield Result(state=State.OK, notice=f'Performance data:') - - def _render_string(v): - return f'{v}' - - for key, label, notice_only, render_func in [ - # times - ('time_namelookup', 'Time name lookup', True, render.timespan,), - ('time_connect', 'Time connect', True, render.timespan,), - ('time_appconnect', 'Time app connect', True, render.timespan,), - ('time_pretransfer', 'Time pre transfer', True, render.timespan,), - ('time_redirect', 'Time redirect', True, render.timespan,), - ('time_starttransfer', 'Time start transfer', True, render.timespan,), - ('time_total', 'Time total', False, render.timespan,), - # counts - ('num_connects', '# of connects', True, _render_string, ), - ('num_redirects', '# of redirects', True, _render_string, ), - ('num_headers', '# of headers', True, _render_string, ), - # sizes - ('size_download', 'Size download', False, render.bytes,), - ('size_upload', 'Size upload', True, render.bytes,), - ('size_header', 'Size header', True, render.bytes,), - ('size_request', 'Size request', True, render.bytes,), - # speed - ('speed_download', 'Speed download', False, render.networkbandwidth,), - ('speed_upload', 'Speed upload', True, render.networkbandwidth,), - ]: - if _data['data'].get(key): - value = _data['data'][key] - if value != 0: - yield from check_levels( - label=label, - value=value, - metric_name=key, - notice_only=notice_only, - render_func=render_func, - levels_lower=params[key].get('lower'), - levels_upper=params[key].get('upper'), - ) - - show_request_header, show_response_headers, show_session_info, show_raw_data = params['show_additional_info'] - if show_request_header and _data.get('REQUEST_HEADER'): - yield Result(state=State.OK, notice=f' ') - yield Result(state=State.OK, notice=f'Request headers:') - for line in _data['REQUEST_HEADER']: - if line: - yield Result(state=State.OK, notice=f'{line}') - - if show_response_headers and _data.get('RESPONSE_HEADER'): - yield Result(state=State.OK, notice=f' ') - yield Result(state=State.OK, notice=f'Response headers:') - for line in _data['RESPONSE_HEADER']: - if line: - yield Result(state=State.OK, notice=f'{line}') - - if show_session_info and _data.get('TLS_INFO'): - yield Result(state=State.OK, notice=f' ') - yield Result(state=State.OK, notice=f'TLS/SSL/SSH info:') - for line in _data['TLS_INFO']: - if line: - yield Result(state=State.OK, notice=f'{line}') - - if show_raw_data: - yield Result(state=State.OK, notice=f' ') - yield Result(state=State.OK, notice=f'RAW data:') - yield Result(state=State.OK, notice=f'{_data}') - - -register.agent_section( - name="curl", - parse_function=parse_curl, -) - -register.check_plugin( - name='curl', - service_name='cURL %s', - discovery_function=discovery_curl, - check_function=check_curl, - check_default_parameters={ - 'show_additional_info': (None, None, None, None), - 'max_age': (None, None, None), - 'state_item_not_found': 3, - 'state_http_result_not_200': 1, - 'http_error_code_to_ignore': [], - 'state_curl_result_not_0': 1, - 'curl_error_code_to_ignore': [], - 'state_verify_sll_not_0': 1, - 'state_expected_str_not_found': 1, - 'state_header_str_not_found': 1, - 'state_for_regex': (0, 1, 0), - 'time_namelookup': {}, - 'time_connect': {}, - 'time_appconnect': {}, - 'time_pretransfer': {}, - 'time_redirect': {}, - 'time_starttransfer': {}, - 'time_total': {}, - 'num_connects': {}, - 'num_redirects': {}, - 'num_headers': {}, - 'size_download': {}, - 'size_upload': {}, - 'size_header': {}, - 'size_request': {}, - 'speed_download': {}, - 'speed_upload': {}, - 'cert_time_left': {}, - }, - check_ruleset_name='curl' -) -- GitLab