diff --git a/README.md b/README.md index 6d6b581bd918709a6420e4f8691b8d7332eb8f8c..91cbd9e7387cbdbe9dc60d9894c6cfaccc5cf77a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[PACKAGE]: ../../raw/master/packagee-0.1.2-20230706.mkp "package-0.1.2-20230706.mkp" +[PACKAGE]: ../../raw/master/snmp_version-0.0.2-20230709.mkp "snmp_version-0.0.2-20230709.mkp" # Title A short description about the plugin diff --git a/agent_based/snmp_version.py b/agent_based/snmp_version.py new file mode 100644 index 0000000000000000000000000000000000000000..2925178d0f41a5f7c19bbfdf65875fdc9272e903 --- /dev/null +++ b/agent_based/snmp_version.py @@ -0,0 +1,184 @@ +#!/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 : 2023-06-28 +# File : snmp_version.py + +# +# Monitor snmp version used by CMK for a host object +# +# 2023-06-28: initial release +# 2023-07-09: added WATO parameters +# 2023-07-11: added check key length, key complexity, use of default keys + +# ToDo: check for repeated chars in key + +import re +from typing import List +from cmk.base.check_api import host_name +from cmk.base.config import get_config_cache +from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import ( + DiscoveryResult, + CheckResult, + StringTable, +) + +from cmk.base.plugins.agent_based.agent_based_api.v1 import ( + register, + Service, + SNMPTree, + Result, + State, + exists, +) + + +def _check_key_length(key: str, name: str, min_length: int, state: int): + if len(key) < min_length: + yield Result(state=State(state), notice=f'{name} length: {len(key)} - below {min_length}') + else: + yield Result(state=State.OK, notice=f'{name} length: {len(key)}') + + +def _check_default_key(key: str, name: str, default_keys: List[str], state: int): + if key.lower() in default_keys: + yield Result(state=State(state), notice=f'{name} uses default value') + + +def _check_expected(value, message: str, excepted: List, state: int): + if value in excepted: + yield Result(state=State.OK, summary=f'{message}: {value}') + else: + yield Result(state=State(state), summary=f'{message} {value}') + + +def _check_key_complexity(value, message: str, regex: List[str], state: int): + _regex = ''.join(regex) + if re.search(_regex, value) is not None: + yield Result(state=State.OK, notice=f'{message} complexity is met.') + else: + yield Result(state=State(state), notice=f'{message} complexity is not met') + + +def parse_snmp_version(string_table: StringTable): + return string_table + + +def discovery_snmp_version(section) -> DiscoveryResult: + yield Service() + + +def check_snmp_version(params, section) -> CheckResult: + config_cache = get_config_cache() + snmp_host = False + + try: + # test for CMK 2.2.x + snmp_host = config_cache.is_snmp_host(host_name()) + snmp_credentials = config_cache._snmp_credentials(host_name()) + snmp_backend = config_cache.get_snmp_backend(host_name()).value + snmp_version1 = config_cache._is_host_snmp_v1(host_name()) + except AttributeError: + # try cmk 2.0.x - 2.1.x + host_config = config_cache.get_host_config(host_name()) + snmp_config = host_config.snmp_config(None) + snmp_credentials = snmp_config.credentials + snmp_backend = snmp_config.snmp_backend.value + snmp_version1 = host_config._is_host_snmp_v1() + if snmp_credentials: + snmp_host = True + + if snmp_host: + snmp_version = '1' if snmp_version1 else '2c' if type(snmp_credentials) is str else '3' + + excepted, state = params['snmp_version'] + yield from _check_expected(snmp_version, 'Version', excepted, state) + + excepted, state = params['snmp_backend'] + yield from _check_expected(snmp_backend.lower(), 'Backend', excepted, state) + + if snmp_version == '3': + excepted, state = params['v3_level'] + yield from _check_expected(snmp_credentials[0].lower(), 'Level', excepted, state) + + if len(snmp_credentials) > 2: + excepted, state = params['v3_authentication'] + excepted = list(map(str.upper, excepted)) + yield from _check_expected(snmp_credentials[1].upper(), 'Authentication', excepted, state) + + try: + snmp_encryption = snmp_credentials[4] + except IndexError: + pass + else: + excepted, state = params['v3_encryption'] + excepted = list(map(str.upper, excepted)) + yield from _check_expected(snmp_encryption.upper(), 'Encryption', excepted, state) + + min_key_length, state_min_key = params['min_key_length'] + default_keys, state_default_key = params['default_keys'] + default_keys = list(map(str.lower, default_keys)) + key_complexity, state_key_complexity = params['key_complexity'] + print(key_complexity) + print(snmp_credentials) + if snmp_version != '3': + message = 'Community string' + yield from _check_key_length(snmp_credentials, message, min_key_length, state_min_key) + yield from _check_default_key(snmp_credentials, message, default_keys, state_default_key) + yield from _check_key_complexity(snmp_credentials, message, key_complexity, state_key_complexity) + else: + if len(snmp_credentials) > 2: + message = 'Authentication key' + yield from _check_key_length(snmp_credentials[3], message, min_key_length, state_min_key) + yield from _check_default_key(snmp_credentials[3], message, default_keys, state_default_key) + yield from _check_key_complexity(snmp_credentials[3], message, key_complexity, state_key_complexity) + if len(snmp_credentials) == 6: + message = 'Encryption key' + yield from _check_key_length(snmp_credentials[5], message, min_key_length, state_min_key) + yield from _check_default_key(snmp_credentials[5], message, default_keys, state_default_key) + yield from _check_key_complexity(snmp_credentials[5], message, key_complexity, state_key_complexity) + if snmp_credentials[3] != snmp_credentials[5]: + yield Result(state=State.OK, notice=F'Authentication and Encryption key are different') + else: + yield Result( + state=State(params['auth_enc_key_identical']), + notice=F'Authentication and Encryption key are identical' + ) + + else: + yield Result(state=State.OK, summary='No SNMP host') + + +register.snmp_section( + name='snmp_version', + parse_function=parse_snmp_version, + fetch=SNMPTree( + base='.1.3.6.1.2.1.1', # + oids=[ + '1', # sysDescr + ]), + detect=exists('.1.3.6.1.2.1.1.1.0', ), # sysDescr +) + +register.check_plugin( + name='snmp_version', + service_name='SNMP Version', + discovery_function=discovery_snmp_version, + check_function=check_snmp_version, + check_default_parameters={ + 'snmp_version': (['3', 1]), + 'v3_level': (['authpriv'], 1), + 'v3_authentication': (['sha', 'sha-224', 'sha-256', 'sha-384', 'sha-512'], 1), + 'v3_encryption': (['aes'], 1), + 'snmp_backend': (['inline', 'classic'], 1), + 'default_keys': (['Public', 'pRivate'], 1), + 'min_key_length': (10, 1), + 'auth_enc_key_identical': 1, + 'key_complexity': (['(?=.*\\d)', '(?=.*[a-z])', '(?=.*[A-Z])', '(?=.*\\W)', '(?=.{10,})'], 1), + }, + check_ruleset_name='snmp_version', +) diff --git a/gui/wato/check_parameters/snmp_version.py b/gui/wato/check_parameters/snmp_version.py new file mode 100644 index 0000000000000000000000000000000000000000..3e79a5c1a64f5382d584c9b62ccd74e2de70532c --- /dev/null +++ b/gui/wato/check_parameters/snmp_version.py @@ -0,0 +1,228 @@ +#!/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 : 2023-07-09 +# File : snmp_version.py +# +# WATO file for snmp_version check plugin +# +# 2023-07-09: initial release + +from cmk.gui.i18n import _ +from cmk.gui.valuespec import ( + Dictionary, + MonitoringState, + Tuple, + ListChoice, + Integer, + ListOfStrings +) + +from cmk.gui.plugins.wato.utils import ( + CheckParameterRulespecWithoutItem, + rulespec_registry, + RulespecGroupCheckParametersNetworking, +) + + +def _parameter_valuespec_snmp_version(): + return Dictionary( + elements=[ + ('snmp_version', + Tuple( + orientation='horizontal', + title=_('SNMP Version'), + elements=[ + ListChoice( + title=_('excepted SNMP Versions'), + choices=[ + ('1', 'Version 1'), + ('2c', 'Version 2c'), + ('3', 'Version 3') + ], + allow_empty=False, + help=_('Select excepted SNMP versions. Default is "Version 3".'), + default_value=['3'], + ), + MonitoringState( + title=_('Monitoring state if version not in excepted versions'), + default_value=1, + ) + ])), + ('v3_level', + Tuple( + orientation='horizontal', + title=_('SNMP v3 level'), + elements=[ + ListChoice( + title=_('excepted level'), + choices=[ + ('authpriv', 'authentication and privacy'), + ('authnopriv', 'authentication and no privacy'), + ('noauthnopriv', 'no authentication and no privacy'), + ], + allow_empty=False, + help=_('Select excepted SNMP v3 protocol level. Default is "authentication and privacy".'), + default_value=['authpriv'], + ), + MonitoringState( + title=_('Monitoring state if level is not in excepted list'), + default_value=1, + ) + ])), + ('v3_authentication', + Tuple( + orientation='horizontal', + title=_('SNMP v3 authenticatio'), + elements=[ + ListChoice( + title=_('excepted authentication'), + choices=[ + ('md5', 'MD5'), + ('sha', 'SHA'), + ('sha-224', 'SHA-224'), + ('sha-256', 'SHA-256'), + ('sha-384', 'SHA-384'), + ('sha-512', 'SHA-512'), + ], + allow_empty=False, + help=_('Select excepted SNMP v3 authentication. Default is "SHA*".'), + default_value=['sha', 'sha-224', 'sha-256', 'sha-384', 'sha-512'], + ), + MonitoringState( + title=_('Monitoring state if authentication is not in excepted list'), + default_value=1, + ) + ])), + ('v3_encryption', + Tuple( + orientation='horizontal', + title=_('SNMP v3 encryption'), + elements=[ + ListChoice( + title=_('excepted encryption'), + choices=[ + ('des', 'DES'), + ('aes', 'AES-128'), + ], + allow_empty=False, + help=_('Select excepted encryption. Default is "AES-128".'), + default_value=['aes'], + ), + MonitoringState( + title=_('Monitoring state if SNMP v3 encryption is not in excepted list'), + default_value=1, + ) + ])), + ('snmp_backend', + Tuple( + orientation='horizontal', + title=_('SNMP backend'), + elements=[ + ListChoice( + title=_('excepted SNMP backend'), + choices=[ + ('inline', 'Inline'), + ('classic', 'Classic'), + ('storedwalk', 'Stored walk') + ], + allow_empty=False, + help=_('Select excepted SNMP backend. Default is "Inline/Classic".'), + default_value=['inline', 'classic'], + ), + MonitoringState( + title=_('Monitoring state if backend not in excepted backends'), + default_value=1, + ) + ])), + ('min_key_length', + Tuple( + orientation='horizontal', + title=_('Check key length'), + elements=[ + Integer( + title=_('minimal key length'), + minvalue=1, + + help=_('Minimal expected Community string/key length. Default is "10".'), + default_value=10, + ), + MonitoringState( + title=_('Monitoring state if key length below minimal key length'), + help=_( + 'Set the monitoring state when the length of the Community string (SNMP v1/2c) ' + 'or the authentication/encryption key (SNMP v3) is below min. length. Default is WARN' + ), + default_value=1, + ) + ])), + ('auth_enc_key_identical', + MonitoringState( + title=_('Monitoring state when authentication and encryption key are identical'), + help=_( + 'Set the monitoring state when authentication key and encryption key are identical. ' + 'This setting is for SNMP v3 only. Default is WARN.' + ), + default_value=1, + )), + ('default_keys', + Tuple( + orientation='horizontal', + title=_('Check default key values'), + elements=[ + ListOfStrings( + title=_('List of default keys'), + allow_empty=False, + help=_( + 'List of default Community strings/keys to check against. Default is "public, private".' + ), + default_value=['public', 'private'], + ), + MonitoringState( + title=_('Monitoring state if key length below minimal key length'), + help=_( + 'Set the monitoring state when the length of the Community string (SNMP v1/2c) ' + 'or the authentication/encryption key (SNMP v3) is below min. length. Default is WARN' + ), + default_value=1, + ) + ])), + ('key_complexity', + Tuple( + orientation='horizontal', + title=_('Key complexity'), + elements=[ + ListChoice( + title=_('excepted characters in the key'), + choices=[ + ('(?=.*\\d)', 'Number'), + ('(?=.*[a-z])', 'Lowercase'), + ('(?=.*[A-Z])', 'Uppercase'), + ('(?=.*\\W)', 'Special') + ], + allow_empty=False, + help=_( + 'Select expected character types in the Community string or authentication/encryption key.' + ' Default is "Number, Lower case, Upper case, Special".'), + default_value=['(?=.*\\d)', '(?=.*[a-z])', '(?=.*[A-Z])', '(?=.*\\W)'], + ), + MonitoringState( + title=_('Monitoring state if key complexity not met'), + default_value=1, + ) + ])), + ]) + + +rulespec_registry.register( + CheckParameterRulespecWithoutItem( + check_group_name='snmp_version', + group=RulespecGroupCheckParametersNetworking, + match_type='dict', + parameter_valuespec=_parameter_valuespec_snmp_version, + title=lambda: _('SNMP Version'), + )) diff --git a/packages/snmp_version b/packages/snmp_version new file mode 100644 index 0000000000000000000000000000000000000000..6159cf2d7dd153fbf2afc38d9249c945a0ba9bd6 --- /dev/null +++ b/packages/snmp_version @@ -0,0 +1,14 @@ +{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)', + 'description': 'Monitors SNMP version used by CheckMK to access monitored ' + 'hosts.\n' + 'This check is intended to create an alert if old SNMP ' + 'versions (or bad parameters) are in use.\n', + 'download_url': 'https://thl-cmk.hopto.org', + 'files': {'agent_based': ['snmp_version.py'], + 'gui': ['wato/check_parameters/snmp_version.py']}, + 'name': 'snmp_version', + 'title': 'SNMP Version', + 'version': '0.0.2-20230709', + 'version.min_required': '2.1.0b1', + 'version.packaged': '2.2.0p5', + 'version.usable_until': None} diff --git a/snmp_version-0.0.2-20230709.mkp b/snmp_version-0.0.2-20230709.mkp new file mode 100644 index 0000000000000000000000000000000000000000..820417d1213eeeb551841c9d55d77a1f9f305ceb Binary files /dev/null and b/snmp_version-0.0.2-20230709.mkp differ