diff --git a/README.md b/README.md index c7d46b782436c22b69aa894710dab8d44ba2be88..67d51496e8801381758d25918796db628bca03eb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[PACKAGE]: ../../raw/master/mkp/agent_ssllabs-2.0.2-20240105.mkp "agent_ssllabs-2.0.2-20240105.mkp" # Qualys SSL Labs REST API special agent **Note: this package is for CheckMK version 2.x.** diff --git a/agent_based/ssllabs_grade.py b/agent_based/ssllabs_grade.py deleted file mode 100644 index 1a5e11e390dfe58ac0a943bc923ad3de53a8c8c0..0000000000000000000000000000000000000000 --- a/agent_based/ssllabs_grade.py +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# License: GNU General Public License v2 -# -# 2015 Karsten Schoeke karsten.schoeke@geobasis-bb.de -# -# 2021-05-15: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com -# -# -# Example output from agent: -# servername;status;time;agent_state;last_grade_result -# <<<ssllabs_grade:sep(0)>>> -# server1.de;A+;1435565830118;0;A+ -# server2.de;A;1435565830118;0;B -# <<<<>>>> - - -import time, re - -from typing import Dict, NamedTuple, Optional - -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, - Result, - get_value_store, -) - - -class SSLLabsGrade(NamedTuple): - lastcheck: str - time_diff: int - grade: Optional[str] - agent_state: Optional[int] - status_detail: str - - -def parse_ssllabs_grade(string_table): - ssl_hosts = {} - - for line in string_table: - line = line[0].split(';') - if len(line) == 5: - ssl_host, status_detail, lastcheck, agent_state, grade = line - lastcheck = time.strftime("%d.%m.%Y %H:%M", time.localtime(int(lastcheck[:10]))) - time_diff = int(time.time() - int(line[2][:10])) - ssl_hosts.update({ssl_host: SSLLabsGrade( - lastcheck=lastcheck, - time_diff=time_diff, - grade=grade, - agent_state=int(agent_state), - status_detail=status_detail - )}) - if len(line) == 4: - ssl_host, status_detail, lastcheck, agent_state = line - lastcheck = time.strftime("%d.%m.%Y %H:%M", time.localtime(int(lastcheck[:10]))) - time_diff = int(time.time() - int(line[2][:10])) - ssl_hosts.update({ssl_host: SSLLabsGrade( - lastcheck=lastcheck, - time_diff=time_diff, - grade=None, - agent_state=None, - status_detail=status_detail - )}) - - return ssl_hosts - - -def discovery_ssllabs_grade(section: Dict) -> DiscoveryResult: - for ssl_host in section.keys(): - yield Service(item=ssl_host) - - -def check_ssllabs_grade(item, params, section: Dict[str, SSLLabsGrade]) -> CheckResult: - #value_store = get_value_store() - #print(f'value_store: {value_store}') - #if not value_store[item][0] == 'last_run': - # value_store[item] = ('last_run', {'grade': 'A+'}) - #grade = value_store[item][1].get('grade') - #print(f'value_store: {grade}') - - try: - ssllabsgrade = section.get(item) - except KeyError: - return None - - ok, warn, crit = params["score"] - warn_last_run, crit_last_run = params["age"] - re_ok = re.compile(ok) - re_warn = re.compile(warn) - re_crit = re.compile(crit) - re_error = re.compile('(HTTP|JSON|unknow)') # API Errors - - if ssllabsgrade.agent_state == 0: # test done - if re_crit.match(ssllabsgrade.grade): - state = State.CRIT - elif re_warn.match(ssllabsgrade.grade): - state = State.WARN - elif re_ok.match(ssllabsgrade.grade): - state = State.OK - else: - state = State.UNKNOWN - yield Result(state=state, summary=f'Grade "{ssllabsgrade.status_detail}"') - - if ssllabsgrade.time_diff > crit_last_run: - state = State.CRIT - elif ssllabsgrade.time_diff > warn_last_run: - state = State.WARN - else: - state = State.OK - yield Result(state=state, summary=f'Last check at {ssllabsgrade.lastcheck}') - elif ssllabsgrade.agent_state == 1: # test in progress - state = State.WARN - yield Result(state=state, summary=f'Server check is in progress, status was "{ssllabsgrade.status_detail}"') - elif ssllabsgrade.agent_state == 2: # API error - state = State.CRIT - yield Result(state=state, summary=f'API error, status was "{ssllabsgrade.status_detail}"') - else: # unknown error - state = State.UNKNOWN - yield Result(state=state, - summary=f'Server check status was "{ssllabsgrade.status_detail}", last check at {ssllabsgrade.lastcheck}') - - yield Result(state=State.OK, notice=f'For details go to https://www.ssllabs.com/ssltest/analyze.html?d={item}') - - -register.agent_section( - name="ssllabs_grade", - parse_function=parse_ssllabs_grade, -) - -register.check_plugin( - name='ssllabs_grade', - service_name='SSL Labs %s', - discovery_function=discovery_ssllabs_grade, - check_function=check_ssllabs_grade, - check_default_parameters={ - "score": ("A", "B|C", "D|E|F|M|T"), - "age": (604800, 864000), - }, - check_ruleset_name='ssllabs_grade' -) diff --git a/agent_ssllabs.mkp b/agent_ssllabs.mkp deleted file mode 100644 index eadc333bcafc203183b78d15dee5c9f2b2fbf0f1..0000000000000000000000000000000000000000 Binary files a/agent_ssllabs.mkp and /dev/null differ diff --git a/checkman/agent_ssllabs b/checkman/agent_ssllabs deleted file mode 100644 index 08ef898bcf6eb7a7979de60090d54f9719bbbc5b..0000000000000000000000000000000000000000 --- a/checkman/agent_ssllabs +++ /dev/null @@ -1,45 +0,0 @@ -title: Dummy check man page - used as template for new check manuals -agents: linux, windows, aix, solaris, hpux, vms, freebsd, snmp -catalog: see modules/catalog.py for possible values -license: GPL -distribution: check_mk -description: - Describe here: (1) what the check actually does, (2) under which - circumstances it goes warning/critical, (3) which devices are supported - by the check, (4) if the check requires a separated plugin or - tool or separate configuration on the target host. - -item: - Describe the syntax and meaning of the check's item here. Provide all - information one needs if coding a manual check with {checks +=} in {main.mk}. - Give an example. If the check uses {None} as sole item, - then leave out this section. - -examples: - # Give examples for configuration in {main.mk} here. If the check has - # configuration variable, then give example for them here. - - # set default levels to 40 and 60 percent: - foo_default_values = (40, 60) - - # another configuration variable here: - inventory_foo_filter = [ "superfoo", "superfoo2" ] - -perfdata: - Describe precisely the number and meaning of performance variables - the check sends. If it outputs no performance data, then leave out this - section. - -inventory: - Describe how the inventory for the check works. Which items - will it find? Describe the influence of check specific - configuration parameters to the inventory. - -[parameters] -foofirst(int): describe the first parameter here (if parameters are grouped - as tuple) -fooother(string): describe another parameter here. - -[configuration] -foo_default_levels(int, int): Describe global configuration variable of - foo here. Important: also tell the user how they are preset. diff --git a/checks/agent_ssllabs b/checks/agent_ssllabs deleted file mode 100644 index 325b21ea85348ea4842264e40cef9d549b9f5853..0000000000000000000000000000000000000000 --- a/checks/agent_ssllabs +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# { -# 'sslhosts': ['ssl.test.com', 'sslserver2.test.com'] -# 'timeout': '180', -# } - -# 'sslhost', timeout - -def agent_ssllabs_arguments(params, _hostname, _ipaddress): - args = [] - - if 'sslhosts' in params: - args += ['--sslhosts', ','.join(params['sslhosts'])] - - if 'timeout' in params: - args += ['--timeout', params['timeout']] - - if 'proxy' in params: - args += ['--proxy', params['proxy']] - - if 'publishresults' in params: - args += ['--publish', params['publishresults']] - - if 'maxage' in params: - args += ['--maxage', params['maxage']] - - return args - - -special_agent_info['ssllabs'] = agent_ssllabs_arguments diff --git a/doc/.gitkeep b/img/.gitkeep similarity index 100% rename from doc/.gitkeep rename to img/.gitkeep diff --git a/doc/sample.png b/img/sample.png similarity index 100% rename from doc/sample.png rename to img/sample.png diff --git a/doc/wato-options-agent.png b/img/wato-options-agent.png similarity index 100% rename from doc/wato-options-agent.png rename to img/wato-options-agent.png diff --git a/doc/wato-options.png b/img/wato-options.png similarity index 100% rename from doc/wato-options.png rename to img/wato-options.png diff --git a/lib/check_mk/special_agent/agent_ssllabs.py b/lib/check_mk/special_agent/agent_ssllabs.py deleted file mode 100755 index a9bee538f97a0c86ffd4ae85d21ebf1ce6e8f8eb..0000000000000000000000000000000000000000 --- a/lib/check_mk/special_agent/agent_ssllabs.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# Karsten Schoeke <karsten.schoeke@geobasis-bb.de> -# -# 2021-05-15: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com -# changed cache file name form host_address to ssl_host_address -# 2021-05-16: changed arguments to argparse -# added options for publish results and max cache age -# -# check_mk is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation in version 2. check_mk is distributed -# in the hope that it will be useful, but WITHOUT ANY WARRANTY; with- -# out even the implied warranty of MERCHANTABILITY or FITNESS FOR A -# PARTICULAR PURPOSE. See the GNU General Public License for more de- -# ails. You should have received a copy of the GNU General Public -# License along with GNU Make; see the file COPYING. If not, write -# to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, -# Boston, MA 02110-1301 USA. - -import argparse -import json -import os -import requests -import sys -import time -from typing import Optional, Sequence - -from cmk.utils.paths import tmp_dir - - -def parse_arguments(argv: Sequence[str]) -> argparse.Namespace: - ''''Parse arguments needed to construct an URL and for connection conditions''' - parser = argparse.ArgumentParser() - parser.add_argument('--sslhosts', required=True, type=str, help='Comma separated list of FQDNs to test for') - parser.add_argument('--proxy', required=False, help='URL to HTTPS Proxy i.e.: https://192.168.1.1:3128') - parser.add_argument('--timeout', '-t', type=float, default=60, help='API call timeout in seconds', ) - parser.add_argument('--publish', type=str, default='off', help='Publish test results on ssllabs.com', choices=['on', 'off'] ) - parser.add_argument('--maxage', type=int, default=167, help='Maximum report age, in hours, if retrieving from "ssllabs.com" cache', ) - - return parser.parse_args(argv) - - -def connect_ssllabs_api(ssl_host_address: str, host_cache: str, args: argparse.Namespace, ): - server = 'api.ssllabs.com' - uri = 'api/v3/analyze' - maxAge = args.maxage # default 167 (1 week minus 1 hour) - publish = args.publish # default off - fromCache = 'on' - now = time.time() - - # url for request webservice (&startNew={startNew}&all={all}) - url = f'https://{server}/{uri}?host={ssl_host_address}&publish={publish}&fromCache={fromCache}&maxAge={maxAge}' - proxies = {} - if args.proxy is not None: - proxies = {'https': args.proxy.split('/')[-1]} # remove 'https://' from proxy string - - try: - response = requests.get( - url=url, - timeout=args.timeout, - proxies=proxies, - ) - - jsonData = json.loads(response.text) - - except Exception as err: - sys.stdout.write(f'{ssl_host_address} Connection error: {err} on {url}') - # print(f'{ssl_host_address};{err};{now};2') - return - - print(response.text) - - try: - if jsonData['status'] == 'READY': - # if test finish and json data ok --> write data in cache file - with open(host_cache, 'w') as outfile: - json.dump(jsonData, outfile) - - except (ValueError, KeyError, TypeError): - print(f'{ssl_host_address};request JSON format error;{now};2') # ;{grade_cache} - - -def read_cache(host_cache: str): - # read cache file - with open(host_cache) as json_file: - # check if cache file contains valid json data - try: - jsonData = json.load(json_file) - if jsonData['status'] == 'READY': - print(json.dumps(jsonData)) - except (ValueError, KeyError, TypeError): - # print(f'{ssl_host_address};cache JSON format error;{now};2;{grade_cache}') - return - - -def main(argv: Optional[Sequence[str]] = None) -> None: - args = parse_arguments(argv) - - VERSION = '2.0.1' - now = time.time() - cache_dir = tmp_dir + '/agents/agent_ssllabs' - cache_age = args.maxage - ssl_hosts = args.sslhosts.split(',') - - # Output general information about the agent - sys.stdout.write('<<<check_mk>>>\n') - sys.stdout.write('Version: %s\n' % VERSION) - sys.stdout.write('AgentOS: linux\n') - - # create cache directory, if not exists - if not os.path.exists(cache_dir): - os.makedirs(cache_dir) - - print('<<<ssllabs_grade:sep(0)>>>') - - for ssl_host_address in ssl_hosts: - # changed cache file form host_address to ssl_host_address - host_cache = '%s/%s' % (cache_dir, ssl_host_address) - - # check if cache file exists and is not older as cache_age - if not os.path.exists(host_cache): - connect_ssllabs_api( - ssl_host_address=ssl_host_address, - host_cache=host_cache, - args=args) - else: - json_cache_file_age = os.path.getmtime(host_cache) - cache_age_sec = now - json_cache_file_age - if cache_age_sec < cache_age: - read_cache( - host_cache=host_cache, - ) - else: - connect_ssllabs_api( - ssl_host_address=ssl_host_address, - host_cache=host_cache, - args=args - ) - - -if __name__ == '__main__': - main() diff --git a/mkp/agent_ssllabs-2.0.2-20240105.mkp b/mkp/agent_ssllabs-2.0.2-20240105.mkp new file mode 100644 index 0000000000000000000000000000000000000000..3f792b1793e82d82e5f993b29dcf8cf559bd0a40 Binary files /dev/null and b/mkp/agent_ssllabs-2.0.2-20240105.mkp differ diff --git a/source/agent_based/ssllabs_grade.py b/source/agent_based/ssllabs_grade.py new file mode 100644 index 0000000000000000000000000000000000000000..f2985459db5541d6c6d3428ffe8322cbbda3a771 --- /dev/null +++ b/source/agent_based/ssllabs_grade.py @@ -0,0 +1,412 @@ +#!/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 : 2024-04-29 +# File : ssllabs_grade.py (check plugin) + +# based on the ssllabs plugin from Karsten Schoeke karsten.schoeke@geobasis-bb.de +# see https://exchange.checkmk.com/p/ssllabs + +# 2021-05-15: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com +# moved to ~/local/lib/check_mk/base/plugins/agent_based + + +# sample string_table: +# [ +# { +# "host": "thl-cmk.hopto.org", +# "port": 443, +# "protocol": "http", +# "isPublic": false, +# "status": "READY", +# "startTime": 1714559152230, +# "testTime": 1714559237958, +# "engineVersion": "2.3.0", +# "criteriaVersion": "2009q", +# "endpoints": [ +# { +# "ipAddress": "91.4.75.201", +# "serverName": "p5b044bc9.dip0.t-ipconnect.de", +# "statusMessage": "Ready", +# "grade": "A+", +# "gradeTrustIgnored": "A+", +# "hasWarnings": false, +# "isExceptional": true, +# "progress": 100, +# "duration": 85530, +# "delegation": 1 +# } +# ] +# }, +# { +# "host": "checkmk.com", +# "port": 443, +# "protocol": "http", +# "isPublic": false, +# "status": "IN_PROGRESS", +# "startTime": 1714563744895, +# "engineVersion": "2.3.0", +# "criteriaVersion": "2009q", +# "endpoints": [ +# { +# "ipAddress": "2a0a:51c1:0:5:0:0:0:4", +# "serverName": "www.checkmk.com", +# "statusMessage": "Ready", +# "grade": "A+", +# "gradeTrustIgnored": "A+", +# "hasWarnings": false, +# "isExceptional": true, +# "progress": 100, +# "duration": 72254, +# "delegation": 1 +# }, +# { +# "ipAddress": "45.133.11.28", +# "serverName": "www.checkmk.com", +# "statusMessage": "In progress", +# "statusDetails": "TESTING_SESSION_RESUMPTION", +# "statusDetailsMessage": "Testing session resumption", "delegation": 1 +# } +# ] +# } +# ] +# + + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from json import loads as json_loads, JSONDecodeError +from typing import Tuple +from re import compile as re_compile, match as re_match +from time import localtime, time as now_time, strftime + +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + CheckResult, + DiscoveryResult, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + Result, + Service, + State, + check_levels, + register, + render, + get_value_store, +) + + +def get_str(field: str, data: Mapping[str: object]) -> str | None: + return str(data[field]) if data.get(field) is not None else None + + +def get_bool(field: str, data: Mapping[str: object]) -> bool | None: + return bool(data[field]) if data.get(field) is not None else None + + +def get_int(field: str, data: Mapping[str: object]) -> bool | None: + return int(data[field]) if data.get(field) is not None else None + + +@dataclass(frozen=True) +class SSLLabsEndpoint: + ip_address: str | None + server_name: str | None + status_message: str | None + grade: str | None + grade_trust_ignored: str | None + has_warnings: bool | None + is_exceptional: bool | None + progress: int | None + duration: int | None + delegation: int | None + statusDetails: str | None + statusDetailsMessage: str | None + + @classmethod + def parse(cls, end_point: Mapping): + return cls( + ip_address=get_str('ipAddress', end_point), + server_name=get_str('serverName', end_point), + status_message=get_str('statusMessage', end_point), + grade=get_str('grade', end_point), + grade_trust_ignored=get_str('gradeTrustIgnored', end_point), + has_warnings=get_bool('hasWarnings', end_point), + is_exceptional=get_bool('isExceptional', end_point), + progress=get_int('progress', end_point), + duration=get_int('duration', end_point), + delegation=get_int('delegation', end_point), + statusDetails=get_str('statusDetails', end_point), + statusDetailsMessage=get_str('statusDetailsMessage', end_point), + ) + + +@dataclass(frozen=True) +class SSLLabsHost: + host: str + port: int + protocol: str + is_public: bool + status: str + start_time: int + test_time: int | None + engine_version: str + criteria_version: str + status_message: str | None + cache_expiry_time: int | None + from_agent_cache: bool | None + end_points: Sequence[SSLLabsEndpoint] + + @classmethod + def parse(cls, ssl_host): + return cls( + host=get_str('host', ssl_host), + port=get_int('port', ssl_host), + protocol=get_str('protocol', ssl_host), + is_public=get_bool('isPublic', ssl_host), + status=get_str('status', ssl_host), + start_time=get_int('startTime', ssl_host), + test_time=get_int('testTime', ssl_host), + engine_version=get_str('engineVersion', ssl_host), + criteria_version=get_str('criteriaVersion', ssl_host), + status_message=get_str('statusMessage', ssl_host), + cache_expiry_time=get_int('cacheExpiryTime', ssl_host), + from_agent_cache=get_bool('from_agent_cache', ssl_host), + end_points=[SSLLabsEndpoint.parse(endpoint) for endpoint in ssl_host.get('endpoints', [])] + ) + + +SECTION = Mapping[str: SSLLabsHost] + + +# _CMK_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%m %Z' + + +def parse_ssllabs_grade(string_table) -> SECTION | None: + try: + data = json_loads(string_table[0][0]) + except JSONDecodeError: + return + + ssl_hosts = {host['host']: SSLLabsHost.parse(host) for host in data if host.get('host') is not None} + return ssl_hosts + + +def discovery_ssllabs_grade(section: SECTION) -> DiscoveryResult: + for ssl_host in section: + yield Service(item=ssl_host) + + +def collect_has_warnings(end_points: Sequence[SSLLabsEndpoint]) -> Sequence[bool]: + return list(set([end_point.has_warnings for end_point in end_points if end_point.has_warnings is not None])) + + +def collect_is_exceptional(end_points: Sequence[SSLLabsEndpoint]) -> Sequence[bool]: + return list(set([end_point.is_exceptional for end_point in end_points if end_point.is_exceptional is not None])) + + +def check_grade(score: Tuple, grade: str, name: str, notice_only: bool) -> Result: + re_ok = re_compile(score[0]) + re_warn = re_compile(score[1]) + re_crit = re_compile(score[2]) + + if re_match(re_ok, grade): + state = State.OK + elif re_match(re_warn, grade): + state = State.WARN + elif re_match(re_crit, grade): + state = State.CRIT + else: + state = State.UNKNOWN + + message = f'{name} Grade: {grade}'.strip() + if notice_only: + yield Result(state=state, notice=message) + else: + yield Result(state=state, summary=message) + + +def check_grades(params: Mapping[str: any], ssl_host: SSLLabsHost, value_store): + grades = list(set([end_point.grade for end_point in ssl_host.end_points if end_point.grade is not None])) + if len(grades) == 1: + yield from check_grade(score=params['score'], name='', grade=grades[0], notice_only=False) + if (last_grade := value_store.get(ssl_host.host)) is not None: + yield Result(state=State.OK, summary=f'Last grade: {last_grade}') + value_store[ssl_host.host] = grades[0] + elif len(grades) == 0: + yield Result(state=State(params.get('no_grade', 1)), notice=f'No grade information found') + else: + end_points: Sequence[SSLLabsEndpoint] = ssl_host.end_points + for end_point in end_points: + name = f'{end_point.server_name}/{end_point.ip_address}' + last_grade = value_store.get(name) + if end_point.grade is not None: + yield from check_grade( + score=params['score'], + name=name, + grade=end_point.grade, + notice_only=True, + ) + yield Result(state=State.OK, notice=f'{name} last grade: {last_grade}') + value_store[name] = end_point.grade + elif last_grade is not None: + yield from check_grade( + score=params['score'], + name=f'{name} Last', + grade=last_grade, + notice_only=True, + ) + + +def check_has_warning(params: Mapping[str: any], end_points: Sequence[SSLLabsEndpoint]): + has_warnings = list(set([ + end_point.has_warnings for end_point in end_points if end_point.has_warnings is not None + ])) + if len(has_warnings) == 1 and has_warnings[0] is True: + yield Result(state=State(params.get('has_warnings', 1)), notice=f'Has warnings') + else: + for end_point in end_points: + name = f'{end_point.server_name}/{end_point.ip_address}' + if end_point.has_warnings is True: + yield Result(state=State(params.get('has_warnings', 1)), notice=f'{name}: has warnings') + + +def check_is_exceptional(params: Mapping[str: any], end_points: Sequence[SSLLabsEndpoint]): + is_exceptional = list(set([ + end_point.is_exceptional for end_point in end_points if end_point.is_exceptional is not None + ])) + if len(is_exceptional) == 1 and is_exceptional[0] is not True: + yield Result(state=State(params.get('is_exceptional', 1)), notice=f'Is not exceptional') + else: + for end_point in end_points: + name = f'{end_point.server_name}/{end_point.ip_address}' + if end_point.has_warnings is True: + yield Result(state=State(params.get('is_exceptional', 1)), notice=f'{name}: is not exceptional') + + +def check_status(params: Mapping[str: any], end_points: Sequence[SSLLabsEndpoint]): + for end_point in end_points: + name = f'{end_point.server_name}/{end_point.ip_address}' + + if end_point.status_message.lower() not in ['ready', 'in progress']: + yield Result(state=State.WARN, notice=f'Status {name}: {end_point.status_message}') + + +def check_ssllabs_grade(item: str, params: Mapping[str: any], section: SECTION) -> CheckResult: + try: + ssl_host: SSLLabsHost = section[item] + except KeyError: + return None + + value_store = get_value_store() + + match ssl_host.status: + case 'READY': + levels_upper = None + if params.get('age') is not None: + warn, crit = params.get('age') + levels_upper = (warn * 86400, crit * 86400) # change to days + + yield from check_levels( + value=now_time() - (ssl_host.test_time / 1000), + label='Last tested', + render_func=render.timespan, + levels_upper=levels_upper, + # notice_only=True, + ) + yield from check_grades(params, ssl_host, value_store) + yield from check_has_warning(params, ssl_host.end_points) + yield from check_is_exceptional(params, ssl_host.end_points) + yield from check_status(params, ssl_host.end_points) + + case 'DNS': + yield Result(state=State(params.get('state_dns', 0)), summary=f'DNS: {ssl_host.status_message}') + yield Result( + state=State.OK, + summary=f'Started {render.timespan(now_time() - (ssl_host.start_time / 1000))} before' + ) + case 'ERROR': + yield Result(state=State(params.get('state_error'), 1), notice=f'Error: {ssl_host.status_message}') + if ssl_host.cache_expiry_time: + yield Result( + state=State.OK, + notice=f'Cache expiry time: {render.datetime(ssl_host.cache_expiry_time / 1000)}' + ) + case 'IN_PROGRESS': + yield Result( + state=State(params.get('state_in_progress', 0)), + summary=f'Test is in progress, started ' + f'{render.timespan(now_time() - (ssl_host.start_time / 1000))} before' + ) + yield from check_grades(params, ssl_host, value_store) + yield from check_has_warning(params, ssl_host.end_points) + yield from check_is_exceptional(params, ssl_host.end_points) + yield from check_status(params, ssl_host.end_points) + case _: + yield Result(state=State.UNKNOWN, notice=f'Unknown test status: {ssl_host.status}') + + yield Result(state=State.OK, notice=f'For full details go to https://www.ssllabs.com/ssltest/analyze.html?d={item}') + + if params.get('details'): + yield Result(state=State.OK, notice=f'\nHost details') + yield Result(state=State.OK, notice=f'Host: {ssl_host.host}') + yield Result(state=State.OK, notice=f'Port: {ssl_host.port}') + yield Result(state=State.OK, notice=f'Protocol: {ssl_host.protocol}') + yield Result(state=State.OK, notice=f'Start Time: {render.datetime(ssl_host.start_time / 1000)}') + if ssl_host.test_time is not None: + yield Result(state=State.OK, notice=f'Test Time: {render.datetime(ssl_host.test_time / 1000)}') + yield Result(state=State.OK, notice=f'Engine version: {ssl_host.engine_version}') + yield Result(state=State.OK, notice=f'Criteria version: {ssl_host.criteria_version}') + yield Result(state=State.OK, notice=f'Status: {ssl_host.status}') + if ssl_host.from_agent_cache is not None: + yield Result(state=State.OK, notice=f'From agent cache: {ssl_host.from_agent_cache}') + else: + yield Result(state=State.WARN, notice=f'Live data') + + if ssl_host.end_points: + yield Result(state=State.OK, notice=f'\nEndpoints') + for end_point in ssl_host.end_points: + yield Result(state=State.OK, notice=f'Server name: {end_point.server_name}') + yield Result(state=State.OK, notice=f'IP-Address: {end_point.ip_address}') + yield Result(state=State.OK, notice=f'Status Message: {end_point.status_message}') + if end_point.grade is not None: + yield Result(state=State.OK, notice=f'Grade: {end_point.grade}') + + name = f'{end_point.server_name}/{end_point.ip_address}' + if (last_grade := value_store.get(name)) is not None: + yield Result(state=State.OK, notice=f'Last grade: {last_grade}') + + if end_point.grade_trust_ignored is not None: + yield Result(state=State.OK, notice=f'Grade Trust Ignored: {end_point.grade_trust_ignored}') + if end_point.has_warnings is not None: + yield Result(state=State.OK, notice=f'has warnings: {end_point.has_warnings}') + if end_point.is_exceptional is not None: + yield Result(state=State.OK, notice=f'is exceptional: {end_point.is_exceptional}') + if end_point.progress is not None: + yield Result(state=State.OK, notice=f'progress: {end_point.progress}') + if end_point.duration is not None: + yield Result(state=State.OK, notice=f'duration: {render.timespan(end_point.duration / 1000)}s') + yield Result(state=State.OK, notice=f'delegation: {end_point.delegation}') + yield Result(state=State.OK, notice=f'\n') + + +register.agent_section( + name="ssllabs_grade", + parse_function=parse_ssllabs_grade, +) + +register.check_plugin( + name='ssllabs_grade', + service_name='SSL Labs %s', + discovery_function=discovery_ssllabs_grade, + check_function=check_ssllabs_grade, + check_default_parameters={ + "score": ("A", "B|C", "D|E|F|M|T"), + }, + check_ruleset_name='ssllabs_grade' +) diff --git a/agents/special/agent_ssllabs b/source/agents/special/agent_ssllabs similarity index 90% rename from agents/special/agent_ssllabs rename to source/agents/special/agent_ssllabs index c28031c21a3972d6d4cb9584627d2e501dfed988..d1dc7252b84697634f648041a4e8c6fadb5574f9 100755 --- a/agents/special/agent_ssllabs +++ b/source/agents/special/agent_ssllabs @@ -8,7 +8,7 @@ # if the file in special_agents it will not work, don't now why, it looks in the wrong directory # # from cmk.special_agents.agent_cisco_ise import main -from cmk.special_agent.agent_ssllabs import main +from cmk.special_agents.agent_ssllabs import main if __name__ == '__main__': main() \ No newline at end of file diff --git a/checkman/ssllabs_grade b/source/checkman/ssllabs_grade similarity index 100% rename from checkman/ssllabs_grade rename to source/checkman/ssllabs_grade diff --git a/source/checks/agent_ssllabs b/source/checks/agent_ssllabs new file mode 100644 index 0000000000000000000000000000000000000000..57e1c5dcc237062abb1bcb50bd71e8c022b6571c --- /dev/null +++ b/source/checks/agent_ssllabs @@ -0,0 +1,37 @@ +#!/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 : 2024-04-29 +# File : agent_ssllabs.py (params stub) +# + +# based on the ssllabs plugin from Karsten Schoeke karsten.schoeke@geobasis-bb.de +# see https://exchange.checkmk.com/p/ssllabs + +def agent_ssllabs_arguments(params, _hostname, _ipaddress): + args = [] + + if (ssl_hosts := params.get('ssl_hosts')) is not None: + args += ['--ssl-hosts', ','.join(ssl_hosts)] + + if (timeout := params.get('timeout')) is not None: + args += ['--timeout', timeout] + + if (proxy := params.get('proxy')) is not None: + args += ['--proxy', proxy] + + if (publish_results := params.get('publish_results')) is not None: + args += ['--publish', publish_results] + + if (max_age := params.get('max_age')) is not None: + args += ['--max-age', max_age] + + return args + + +special_agent_info['ssllabs'] = agent_ssllabs_arguments diff --git a/source/gui/wato/check_parameters/ssllabs_grade.py b/source/gui/wato/check_parameters/ssllabs_grade.py new file mode 100644 index 0000000000000000000000000000000000000000..67406f028739219e41db3591a825f2eee2f6e73e --- /dev/null +++ b/source/gui/wato/check_parameters/ssllabs_grade.py @@ -0,0 +1,126 @@ +#!/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 : 2024-04-29 +# File : ssllabs_grade.py (wato check plugin) + +# based on the ssllabs plugin from Karsten Schoeke karsten.schoeke@geobasis-bb.de +# see https://exchange.checkmk.com/p/ssllabs + +# +# 2021-05-15: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com +# 2024-05-01: modified for CMK 2.2.x +# moved to ~/local/lib/check_mk/gui/plugins/wato/check_parameters +# 2024-05-01: changed age to days + +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + FixedValue, + Integer, + RegExp, + RegExpUnicode, + TextAscii, + Tuple, + MonitoringState, +) + +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithItem, + rulespec_registry, + RulespecGroupCheckParametersNetworking, +) + + +def _parameter_valuespec_ssllabs_grade(): + return Dictionary(elements=[ + ('age', + Tuple( + title=_('Maximum age of ssllabs scan'), + help=_('The maximum age of the last ssllabs check.'), + elements=[ + Integer(title=_('Warning at'), default_value=2, minvalue=1), + Integer(title=_('Critical at'), default_value=3, minvalue=1), + ])), + ('score', + Tuple( + title=_('grade level for ssllabs scan'), + help=_('Put here the Integerttern (regex) for ssllabs grade check level.'), + elements=[ + RegExpUnicode( + title=_('Pattern (regex) Ok level'), + mode=RegExp.prefix, + default_value='A', + ), + RegExpUnicode( + title=_('Pattern (regex) Warning level'), + mode=RegExp.prefix, + default_value='B|C', + ), + RegExpUnicode( + title=_('Pattern (regex) Critical level'), + mode=RegExp.prefix, + default_value='D|E|F|M|T', + ), + ])), + ('no_grade', + MonitoringState( + title=_('Monitoring state if no grade was found'), + default_value=1, + help=_('Set the monitoring state no grade information was found the result. Default is WARN.'), + )), + ('has_warnings', + MonitoringState( + title=_('Monitoring state if host has warnings'), + default_value=1, + help=_('Set the monitoring state if "hasWarnings" in the result is true. Default is WARN.'), + )), + ('is_exceptional', + MonitoringState( + title=_('Monitoring state if host is not exceptional'), + default_value=1, + help=_('Set the monitoring state if "isExceptional" in the result is not true. Default is WARN.'), + )), + ('state_dns', + MonitoringState( + title=_('Monitoring state if the check is in "DNS resolving" state'), + default_value=0, + help=_('Set the monitoring state if the ssllabs scan is in "DNS resolving" state. Default is OK.'), + )), + ('state_error', + MonitoringState( + title=_('Monitoring state if the check is in "ERROR" state'), + default_value=1, + help=_('Set the monitoring state if the ssllabs scan is reporting an "ERROR". Default is WARN.'), + )), + ('state_in_progress', + MonitoringState( + title=_('Monitoring state if the check is in "IN_PROGRESS" state'), + default_value=0, + help=_('Set the monitoring state if the ssllabs scan is "IN_PROGRESS". Default is OK.'), + )), + ('details', + FixedValue( + value=True, + title=_('Show result detail in the service details'), + totext='', + )) + ], + title=_('SSL Server check via ssllabs API'), + ) + + +rulespec_registry.register( + CheckParameterRulespecWithItem( + check_group_name='ssllabs_grade', + group=RulespecGroupCheckParametersNetworking, + item_spec=lambda: TextAscii(title=_('The FQDN on ssl server to check'), ), + match_type='dict', + parameter_valuespec=_parameter_valuespec_ssllabs_grade, + title=lambda: _('SSL Server via ssllabs API.'), + )) diff --git a/source/lib/python3/cmk/special_agents/agent_ssllabs.py b/source/lib/python3/cmk/special_agents/agent_ssllabs.py new file mode 100644 index 0000000000000000000000000000000000000000..62aaed5ca32b4bed6ab6dacfcc7f4824a6331910 --- /dev/null +++ b/source/lib/python3/cmk/special_agents/agent_ssllabs.py @@ -0,0 +1,251 @@ +#!/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 : 2024-04-29 +# File : ssllabs_grade.py (wato check plugin) +# +# based on the ssllabs plugin from Karsten Schoeke karsten.schoeke@geobasis-bb.de +# see https://exchange.checkmk.com/p/ssllabs +# + +# 2021-05-15: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com +# changed cache file name form host_address to ssl_host_address +# 2021-05-16: changed arguments to argparse +# added options for publish results and max cache age +# 2024-05-01: refactoring + +# sample agent output (formatted) +# <<<check_mk>>> +# Version: 2.0.2 +# AgentOS: linux +# +# <<<ssllabs_grade:sep(0)>>> +# [ +# { +# "host": "thl-cmk.hopto.org", +# "port": 443, +# "protocol": "http", +# "isPublic": false, +# "status": "READY", +# "startTime": 1714559152230, +# "testTime": 1714559237958, +# "engineVersion": "2.3.0", +# "criteriaVersion": "2009q", +# "endpoints": [ +# {"ipAddress": "91.4.75.201", +# "serverName": "p5b044bc9.dip0.t-ipconnect.de", +# "statusMessage": "Ready", +# "grade": "A+", +# "gradeTrustIgnored": "A+", +# "hasWarnings": false, +# "isExceptional": true, +# "progress": 100, +# "duration": 85530, +# "delegation": 1 +# } +# ] +# }, +# { +# "host": "checkmk.com", +# "port": 443, +# "protocol": "http", +# "isPublic": false, +# "status": "IN_PROGRESS", +# "startTime": 1714563744895, +# "engineVersion": "2.3.0", +# "criteriaVersion": "2009q", +# "endpoints": [ +# { +# "ipAddress": "2a0a:51c1:0:5:0:0:0:4", +# "serverName": "www.checkmk.com", +# "statusMessage": "Ready", +# "grade": "A+", +# "gradeTrustIgnored": "A+", +# "hasWarnings": false, +# "isExceptional": true, +# "progress": 100, +# "duration": 72254, +# "delegation": 1 +# }, +# { +# "ipAddress": "45.133.11.28", +# "serverName": "www.checkmk.com", +# "statusMessage": "In progress", +# "statusDetails": "TESTING_SESSION_RESUMPTION", +# "statusDetailsMessage": "Testing session resumption", "delegation": 1 +# } +# ] +# } +# ] +# <<<>>> + +from argparse import Namespace +from collections.abc import Sequence +from json import dumps as json_dumps, loads as json_loads, JSONDecodeError +from pathlib import Path +from requests import get +from requests.exceptions import ConnectionError +from sys import stdout as sys_stdout +from time import time as now_time + +from cmk.special_agents.utils.agent_common import special_agent_main +from cmk.special_agents.utils.argument_parsing import create_default_argument_parser +from cmk.utils.paths import tmp_dir + +VERSION = '2.0.2' + + +class Args(Namespace): + max_age: int + proxy: str + publish: str + ssl_hosts: str + timeout: float + + +def write_section(section: dict): + sys_stdout.write('\n<<<ssllabs_grade:sep(0)>>>\n') + sys_stdout.write(json_dumps(section)) + sys_stdout.write('\n<<<>>>\n') + + +def parse_arguments(argv: Sequence[str] | None) -> Args: + """'Parse arguments needed to construct a URL and for connection conditions""" + parser = create_default_argument_parser(__doc__) + parser.description = 'This is a CKK special agent for the Qualys SSL Labs API to monitor SSL Certificate status' + parser.add_argument( + '--ssl-hosts', required=True, type=str, + help='Comma separated list of FQDNs to test for', + ) + parser.add_argument( + '--proxy', required=False, + help='URL to HTTPS Proxy i.e.: https://192.168.1.1:3128', + ) + parser.add_argument( + '--timeout', '-t', type=float, default=60, + help='API call timeout in seconds', + ) + parser.add_argument( + '--publish', type=str, default='off', choices=['on', 'off'], + help='Publish test results on ssllabs.com', + ) + parser.add_argument( + '--max-age', type=int, default=167, + help='Maximum report age, in hours, if retrieving from "ssllabs.com" cache', + ) + parser.epilog = ( + '\n\nAcnowlegement:\n' + ' This agent is based on the work by Karsten Schoeke karsten[dot]schoeke[at]geobasis-bb[dot]de\n' + ' see https://exchange.checkmk.com/p/ssllabs\n\n' + f'written by thl-cmk[at]outlook[dot], Version: {VERSION}, ' + f'For more information see: https://thl-cmk.hopto.org\n' + ) + return parser.parse_args(argv) + + +def connect_ssllabs_api(ssl_host_address: str, host_cache: str, args: Args, ) -> dict | None: + # + # https://github.com/ssllabs/ssllabs-scan + # + server = 'api.ssllabs.com' + uri = 'api/v3/analyze' # change to api v4 (?) + max_age = args.max_age * 24 # default 1 day (1 week minus 1 hour) + publish = args.publish # default off + from_cache = 'on' # on | off + # all_data = 'done' # on | done + ignore_mismatch = 'on' # on | off + start_new = 'on' + + # url for request webservice (&startNew={startNew}&all={all}) + url = ( + f'https://{server}/{uri}' + f'?host={ssl_host_address}' + f'&publish={publish}' + f'&fromCache={from_cache}' + f'&maxAge={max_age}' + # f'&all={all_data}' + f'&ignoreMismatch={ignore_mismatch}' + # f'&startNew={start_new}' + ) + proxies = {} + if args.proxy is not None: + proxies = {'https': args.proxy.split('/')[-1]} # remove 'https://' from proxy string + + try: + response = get( + url=url, + timeout=args.timeout, + proxies=proxies, + headers={ + 'User-Agent': f'CMK SSL Labs special agent {VERSION}', + }, + ) + except ConnectionError as e: + host_data = {'host': ssl_host_address, 'status': 'ConnectionError', 'error': str(e)} + else: + try: + host_data = response.json() + except JSONDecodeError as e: + host_data = {'host': ssl_host_address, 'status': 'JSONDecodeError', 'error': str(e)} + if host_data.get('status') == 'READY': + Path(host_cache).write_text(response.text) + + return host_data + + +def read_cache(host_cache: str) -> dict | None: + try: + data: dict = json_loads(Path(host_cache).read_text()) + except JSONDecodeError: + return + + data.update({'from_agent_cache': True}) + return data + + +def agent_ssllsbs_main(args: Args) -> int: + now = now_time() + cache_dir = f'{tmp_dir}/agents/agent_ssllabs' + cache_age = args.max_age + 86400 + ssl_hosts = args.ssl_hosts.split(',') + + # Output general information about the agent + sys_stdout.write('<<<check_mk>>>\n') + sys_stdout.write(f'Version: {VERSION}\n') + sys_stdout.write('AgentOS: linux\n') + + # create cache directory, if it not exists + Path(cache_dir).mkdir(parents=True, exist_ok=True) + + data = [] + for ssl_host_address in ssl_hosts: + host_cache = f'{cache_dir}/{ssl_host_address}' + + # check if cache file exists and is not older as cache_age + if Path(host_cache).exists() and now - Path(host_cache).stat().st_mtime < cache_age: + if host_data := read_cache(host_cache=host_cache): + data.append(host_data) + else: + if host_data := connect_ssllabs_api( + ssl_host_address=ssl_host_address, + host_cache=host_cache, + args=args, + ): + data.append(host_data) + + if data: + write_section(data) + return 0 + + +def main() -> int: + return special_agent_main(parse_arguments, agent_ssllsbs_main) + + +if __name__ == '__main__': + main() diff --git a/packages/agent_ssllabs b/source/packages/agent_ssllabs similarity index 82% rename from packages/agent_ssllabs rename to source/packages/agent_ssllabs index 6f8dc48bbcc48a3b800ec7847d2c7fb62f61d05e..698d35e3fcdfbac89f36c685ac2d87c980d1bf11 100644 --- a/packages/agent_ssllabs +++ b/source/packages/agent_ssllabs @@ -26,13 +26,12 @@ 'agents': ['special/agent_ssllabs'], 'checkman': ['ssllabs_grade'], 'checks': ['agent_ssllabs'], - 'lib': ['check_mk/special_agent/agent_ssllabs.py'], - 'web': ['plugins/wato/agent_ssllabs.py', - 'plugins/wato/ssllabs_grade.py']}, + 'gui': ['wato/check_parameters/ssllabs_grade.py'], + 'lib': ['python3/cmk/special_agents/agent_ssllabs.py'], + 'web': ['plugins/wato/agent_ssllabs.py']}, 'name': 'agent_ssllabs', - 'num_files': 7, 'title': 'ssllabs api check', - 'version': '2.0.1', - 'version.min_required': '2.0.0', - 'version.packaged': '2021.04.10', - 'version.usable_until': None} \ No newline at end of file + 'version': '2.0.2-20240105', + 'version.min_required': '2.2.0b1', + 'version.packaged': '2.2.0p24', + 'version.usable_until': None} diff --git a/source/web/plugins/wato/agent_ssllabs.py b/source/web/plugins/wato/agent_ssllabs.py new file mode 100644 index 0000000000000000000000000000000000000000..55fb88332d720994d7ea6610cba7860956f06d09 --- /dev/null +++ b/source/web/plugins/wato/agent_ssllabs.py @@ -0,0 +1,103 @@ +#!/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 : 2024-04-29 +# File : ssllabs.py (wato special agent) +# + +# based on the ssllabs plugin from Karsten Schoeke karsten.schoeke@geobasis-bb.de +# see https://exchange.checkmk.com/p/ssllabs + +# 2024-05-01: modified for CMK 2.2.x + +from cmk.gui.i18n import _ +from cmk.gui.plugins.wato.utils import ( + HostRulespec, + rulespec_registry, +) +from cmk.gui.valuespec import ( + Dictionary, + Integer, + TextAscii, + ListOfStrings, + FixedValue, +) + +from cmk.gui.plugins.wato.special_agents.common import RulespecGroupDatasourceProgramsOS + + +def _valuespec_special_agents_ssllabs(): + return Dictionary( + elements=[ + ('ssl_hosts', + ListOfStrings( + title=_('SSL hosts to check'), + orientation='vertical', + allow_empty=False, + size=50, + empty_text='www.checkmk.com', + max_entries=10, + help=_( + 'List of server names to check. Add the host names without "http(s)://". Ie: www.checkmk.com. ' + 'The list is limited to 10 entries. If you need more than 10 entries create another rule.' + ), + )), + ('timeout', + Integer( + title=_('Connect Timeout'), + help=_( + 'The network timeout in seconds when communicating via HTTPS. The default is 30 seconds.' + ), + default_value=30, + minvalue=1, + unit=_('seconds') + )), + ('proxy', + TextAscii( + title=_('proxy server, if required'), + help=_('proxy in the format: https://ip-addres|servername:port'), + )), + ('publish_results', + FixedValue( + value='on', + title=_('Publish results'), + totext=_('Results will be published'), + help=_( + 'By default test results will not be published. If you enable this option the test' + ' results will by public visible on https://www.ssllabs.com/ssltest' + ), + default_value='off', + )), + ('max_age', + Integer( + title=_('Max Age for ssllbas.com cache'), + help=_( + 'Maximum report age, in hours, if retrieving from "ssllabs.com" cache. ' + 'After this time a new test will by initiated. The default (and minimum) is 1 Day' + ), + default_value=1, + minvalue=1, + unit=_('Days') + )), + ], + title=_('Qualys SSL Labs server test'), + help=_( + 'This rule selects the ssllabs agent, which fetches SSL Server status from api.ssllabs.com.' + 'For more details about the SSL server check see https://www.ssllabs.com/ssltest/index.html.' + 'For mor information about the SSL Labs API see: ' + 'https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v3.md.' + ), + ) + + +rulespec_registry.register( + HostRulespec( + group=RulespecGroupDatasourceProgramsOS, + name='special_agents:ssllabs', + valuespec=_valuespec_special_agents_ssllabs, + )) diff --git a/web/plugins/wato/agent_ssllabs.py b/web/plugins/wato/agent_ssllabs.py deleted file mode 100644 index 40f4d13fe7956ae18f0ca505298ab3f6ad02d9d7..0000000000000000000000000000000000000000 --- a/web/plugins/wato/agent_ssllabs.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# - - -from cmk.gui.i18n import _ -from cmk.gui.plugins.wato import ( - HostRulespec, - rulespec_registry, -) -from cmk.gui.valuespec import ( - Dictionary, - Integer, - TextAscii, - ListOfStrings, - FixedValue, -) - -from cmk.gui.plugins.wato.datasource_programs import ( - RulespecGroupDatasourceProgramsOS, -) - - -def _valuespec_special_agents_ssllabs(): - return Dictionary( - elements=[ - ('sslhosts', - ListOfStrings( - title=_('SSL hosts to check'), - orientation='vertical', - allow_empty=False, - ) - ), - ('timeout', - Integer( - title=_('Connect Timeout'), - help=_('The network timeout in seconds when communicating via HTTPS. ' - 'The default is 60 seconds.'), - default_value=60, - minvalue=1, - unit=_('seconds') - ) - ), - ('proxy', - TextAscii( - title=_('proxy server, if required'), - help=_('proxy in the format: https://ip-addres|servername:port'), - ), - ), - ('publishresults', - FixedValue( - 'on', - title=_('Publish results'), - totext=_('Results will be published'), - help=_('By default test results will not be published. If you enable this option the test' - ' results will by public visible on https://www.ssllabs.com/ssltest'), - default_value='off', - )), - ('maxage', - Integer( - title=_('Max Age for ssllbas.com cache'), - help=_('Maximum report age, in hours, if retrieving from "ssllabs.com" cache. ' - 'After this time a new test will by initiated. The default (and minimum) is 167 hours'), - default_value=167, - minvalue=167, - )), - ], - title=_('Qualys SSL Labs server test'), - help=_('This rule selects the ssllabs agent, which fetches SSL Server status from api.ssllabs.com.' - 'For more details about the SSL server check see https://www.ssllabs.com/ssltest/index.html.' - 'For mor information about the SSL Labs API see: ' - 'https://github.com/ssllabs/ssllabs-scan/blob/master/ssllabs-api-docs-v3.md.'), - ) - - -rulespec_registry.register( - HostRulespec( - group=RulespecGroupDatasourceProgramsOS, - name='special_agents:ssllabs', - valuespec=_valuespec_special_agents_ssllabs, - )) diff --git a/web/plugins/wato/ssllabs_grade.py b/web/plugins/wato/ssllabs_grade.py deleted file mode 100644 index 9e9df0b7a25e9080e2bba4efb0d37b1e89b51e02..0000000000000000000000000000000000000000 --- a/web/plugins/wato/ssllabs_grade.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# -# -# 2015 Karsten Schoeke karsten.schoeke@geobasis-bb.de -# -# 2021-05-15: rewritten for CMK 2.0 by thl-cmk[at]outlook[dot]com -# - -from cmk.gui.i18n import _ -from cmk.gui.valuespec import ( - Dictionary, - TextAscii, - Age, - Tuple, - RegExpUnicode, - RegExp, -) - -from cmk.gui.plugins.wato import ( - CheckParameterRulespecWithItem, - rulespec_registry, - RulespecGroupCheckParametersNetworking, -) - - -def _parameter_valuespec_ssllabs_grade(): - return Dictionary(elements=[ - ('age', - Tuple( - title=_('Maximum age of ssllabs scan'), - help=_('The maximum age of the last ssllabs check.'), - elements=[ - Age(title=_('Warning at'), default_value=604800, minvalue=604800), - Age(title=_('Critical at'), default_value=864000, minvalue=691200), - ] - ), - ), - ('score', - Tuple( - title=_('grade level for ssllabs scan'), - help=_('Put here the Integerttern (regex) for ssllabs grade check level.'), - elements=[ - RegExpUnicode( - title=_('Pattern (regex) Ok level'), - mode=RegExp.prefix, - default_value='A', - ), - RegExpUnicode( - title=_('Pattern (regex) Warning level'), - mode=RegExp.prefix, - default_value='B|C', - ), - RegExpUnicode( - title=_('Pattern (regex) Critical level'), - mode=RegExp.prefix, - default_value='D|E|F|M|T', - ), - ] - ), - ), - ], - title=_('SSL Server check via ssllabs API'), - ) - - -rulespec_registry.register( - CheckParameterRulespecWithItem( - check_group_name='ssllabs_grade', - group=RulespecGroupCheckParametersNetworking, - item_spec=lambda: TextAscii(title=_('The FQDN on ssl server to check'), ), - match_type='dict', - parameter_valuespec=_parameter_valuespec_ssllabs_grade, - title=lambda: _('SSL Server via ssllabs API.'), - ))