Collection of CheckMK checks (see https://checkmk.com/). All checks and plugins are provided as is. Absolutely no warranty. Send any comments to thl-cmk[at]outlook[dot]com

Skip to content
Snippets Groups Projects
Commit 0cda12f9 authored by thl-cmk's avatar thl-cmk :flag_na:
Browse files

update project

parent 152897e9
No related branches found
No related tags found
No related merge requests found
[PACKAGE]: ../../raw/master/mkp/check_radius-0.0.1-20240421.mkp "check_radius-0.0.1-20240421.mkp"
[PACKAGE]: ../../raw/master/mkp/check_radius-0.1.0-20240428.mkp "check_radius-0.1.0-20240428.mkp"
# Check RADIUS
This is a (very) basic active RADIUS check. Tests if a RADIUS server is responsive (accept/reject/timeout). There is (limited) support to add AV-pairs to the RADIUS request.
......
File added
......@@ -14,32 +14,53 @@
def check_radius_arguments(params):
args = []
if server := params.get('server'):
if (server := params.get('server')) is not None:
args.extend(['-H', server])
else:
args.append('-H $HOSTADDRESS$')
if auth_port := params.get("auth_port"):
args.extend(['--authport', auth_port])
if (auth_port := params.get("auth_port")) is not None:
args.extend(['--auth-port', auth_port])
if secret := params.get("secret"):
if (secret := params.get("secret")) is not None:
args.extend(["--secret", passwordstore_get_cmdline("%s", secret)])
if user_name := params.get("user_name"):
if (user_name := params.get("user_name")) is not None:
args.extend([f'--username', user_name])
if user_password := params.get("user_password"):
if (user_password := params.get("user_password")) is not None:
args.extend(["--password", passwordstore_get_cmdline("%s", user_password)])
if timeout := params.get('timeout'):
args.extend(['-timeout', timeout])
if (timeout := params.get('timeout')) is not None:
args.extend(['--timeout', timeout])
if (request_attributes := params.get('request_params').get('request_attributes')) is not None:
for av_name, av_value in request_attributes:
args.extend(['--request-attribute', f'{av_name}:{av_value}'])
if (expected_response := params.get('response_params').get('expected_response')) is not None:
args.extend(['--expected-response', expected_response])
if (state_not_expected_response := params.get('response_params').get('state_not_expected_response')) is not None:
args.extend(['--state-not-expected-response', state_not_expected_response])
if (num_resp_attributes := params.get('response_params').get('num_resp_attributes')) is not None:
args.extend(['--num-resp-attributes', num_resp_attributes])
if (state_wrong_number_of_response_attributes := params.get('response_params').get(
'state_wrong_number_of_response_attributes')) is not None:
args.extend(['--state-wrong-num-resp-attributes', state_wrong_number_of_response_attributes])
if (level_upper_response_time := params.get('response_params').get('level_upper_response_time')) is not None:
warn, crit = level_upper_response_time
args.extend(['--max-response-time', f'{warn}, {crit}'])
return args
def _check_description(params):
if 'description' in params:
return f'RADIUS server {params["description"]}'
return f'RADIUS {params["description"]}'
return 'RADIUS server'
......
......@@ -18,8 +18,8 @@ from cmk.gui.plugins.metrics.utils import (
perfometer_info
)
metric_info['radius_request_time'] = {
'title': _('Request time'),
metric_info['radius_response_time'] = {
'title': _('Response time'),
'unit': 's',
'color': '#9a52bf',
}
......@@ -27,17 +27,17 @@ metric_info['radius_request_time'] = {
graph_info['check_radius_time'] = {
'title': _('RADIUS request time'),
'metrics': [
('radius_request_time', 'area'),
('radius_response_time', 'area'),
],
'scalars': [
('radius_request_time:crit', _('Crit')),
('radius_request_time:warn', _('Warn')),
('radius_response_time:crit', _('Crit')),
('radius_response_time:warn', _('Warn')),
],
}
perfometer_info.append({
'type': 'logarithmic',
'metric': 'radius_request_time',
'metric': 'radius_response_time',
'half_value': 1.0,
'exponent': 10.0,
})
......@@ -12,15 +12,153 @@
# 2024-01-01: modified for cmk 2.2.x
from cmk.gui.i18n import _
from cmk.gui.plugins.wato.active_checks.common import RulespecGroupActiveChecks
from cmk.gui.plugins.wato.utils import HostRulespec, IndividualOrStoredPassword, rulespec_registry
from cmk.gui.valuespec import (
Alternative,
Dictionary,
DropdownChoice,
FixedValue,
Foldable,
IPv4Address,
Integer,
TextAscii,
ListOf,
MonitoringState,
TextInput,
Transform,
Tuple,
)
from cmk.gui.plugins.wato.active_checks.common import RulespecGroupActiveChecks
from cmk.gui.plugins.wato.utils import HostRulespec, rulespec_registry, IndividualOrStoredPassword
_called_station_id = Tuple(
title='Called-Station-Id',
orientation='horizontal',
elements=[
FixedValue('Called-Station-Id'), # , totext='' # add empty totext to remove "duplicate" attribute name
TextInput(
size=20,
placeholder='AA-BB-CC-DD-EE-FF',
allow_empty=False,
),
],
)
_calling_station_id = Tuple(
title='Calling-Station-Id',
orientation='horizontal',
elements=[
FixedValue('Calling-Station-Id'),
TextInput(
size=20,
placeholder='AA-BB-CC-DD-EE-FF',
allow_empty=False,
),
],
)
_framed_mtu = Tuple(
title='Framed-MTU',
orientation='horizontal',
elements=[
FixedValue('Framed-MTU'),
Integer(
size=5,
default_value=1500
),
],
)
_nas_identifier = Tuple(
title='NAS-Identifier',
orientation='horizontal',
elements=[
FixedValue('NAS-Identifier'),
TextInput(
size=20,
placeholder='NAS001',
allow_empty=False,
),
],
)
_nas_ip_address = Tuple(
title='NAS-IP-Address',
orientation='horizontal',
elements=[
FixedValue('NAS-IP-Address'),
IPv4Address(),
],
)
_nas_port_id = Tuple(
title='NAS-Port-Id',
orientation='horizontal',
elements=[
FixedValue('NAS-Port-Id'),
TextInput(
size=20,
placeholder='GigabitEthernet0/8',
allow_empty=False,
),
],
)
_nas_port_type = Tuple(
title='NAS-Port-Type',
orientation='horizontal',
elements=[
FixedValue('NAS-Port-Type'),
DropdownChoice(
choices=[
('0', 'Async'),
('1', 'Sync'),
('2', 'ISDN'),
('3', 'ISDN-V120'),
('4', 'ISDN-V110'),
('5', 'Virtual'),
('6', 'PIAFS'),
('7', 'HDLC-Clear-Channel'),
('8', 'X.25'),
('9', 'X.75'),
('10', 'G.3-Fax'),
('11', 'SDSL'),
('12', 'ADSL-CAP'),
('13', 'ADSL-DMT'),
('14', 'IDSL'),
('15', 'Ethernet'),
('16', 'xDSL'),
('17', 'Cable'),
('18', 'Wireless-Other'),
('19', 'Wireless-802.11'),
]
),
],
)
_nas_port = Tuple(
title='NAS-Port',
orientation='horizontal',
elements=[
FixedValue('NAS-Port'),
Integer(
size=7,
),
],
)
_service_type = Tuple(
title='Service-Type',
orientation='horizontal',
elements=[
FixedValue('Service-Type'),
DropdownChoice(
choices=[
('1', 'Login-User'),
('2', 'Framed-User'),
('3', 'Callback-Login-User'),
('4', 'Callback-Framed-User'),
('5', 'Outbound-User'),
('6', 'Administrative-User'),
('7', 'NAS-Prompt-User'),
('8', 'Authenticate-Only'),
('9', 'Callback-NAS-Prompt'),
('10', 'Call-Check'),
('11', 'Callback-Administrative'),
]
),
],
)
def _valuespec_active_checks_radius():
......@@ -30,49 +168,51 @@ def _valuespec_active_checks_radius():
help=_(''),
elements=[
('description',
TextAscii(
TextInput(
title=_('Service description'),
help=_(
'Must be unique for every host. The service description starts always with \"RADIUS server\".'),
'Must be unique for every host. The service description starts always with \"RADIUS server\".'
),
size=50,
placeholder='Item name for the service',
allow_empty=False,
)),
('server',
TextAscii(
TextInput(
title=_('Server IP-address or name'),
help=_(
'Hostname or IP-address to monitor. Default is the host name/IP-Address of the monitored host.'
),
size=50,
allow_empty=False,
placeholder='i.e 192.168.10.10 or srvrad01.company.intern'
)),
('auth_port',
Integer(
title=_('RADIUS authentication port'),
title=_('Authentication port'),
help=_('The RADIUS port to use for authentication. Default is 1812.'),
# size=5,
size=5,
default_value=1812,
minvalue=1,
maxvalue=65535,
)),
('secret',
IndividualOrStoredPassword(
title=_('Server secret'),
help=_('The RADIUS secret.'),
# size=50,
title=_('Shared secret'),
help=_('The shared secret.'),
allow_empty=False,
)),
('timeout',
Integer(
title=_('Server timeout'),
help=_('The user password.'),
title=_('Request timeout'),
help=_('The timeout for the RADIUS request.'),
default_value=2,
minvalue=1,
maxvalue=30,
unit='s',
)),
('user_name',
TextAscii(
TextInput(
title=_('User name'),
help=_('The user name to use in the request.'),
size=50,
......@@ -83,11 +223,85 @@ def _valuespec_active_checks_radius():
IndividualOrStoredPassword(
title=_('User password'),
help=_('The user password.'),
# size=50,
allow_empty=False
)),
('request_params',
Foldable(
Dictionary(
title="Hide/Show Request Parameters",
elements=[
('request_attributes',
ListOf(
Alternative(
orientation='horizontal',
elements=[
_called_station_id,
_calling_station_id,
_framed_mtu,
_nas_identifier,
_nas_ip_address,
_nas_port,
_nas_port_id,
_nas_port_type,
_service_type,
],
),
title=_('Request Attributes'),
add_label=_('add attribute'),
)),
],
),
title='Request paranmeters',
)),
('response_params',
Foldable(
Dictionary(
title="Hide/Show Response Parameters",
elements=[
('expected_response',
DropdownChoice(
choices=(
(2, 'Accepted'),
(3, 'Rejected'),
),
title=_('Expected response'),
help=_('Expected response from the RADIUS server.'),
)),
('state_not_expected_response',
MonitoringState(
title=_('Monitoring state not expected response'),
default_value=2,
)),
('level_upper_response_time',
Tuple(
elements=[
Integer(title=_('Warning at'), unit='ms', minvalue=0, maxvalue=10000),
Integer(title=_('Critical at'), unit='ms', minvalue=0, maxvalue=10000),
],
title=_('Max. response time'),
)),
('num_resp_attributes',
Integer(
title=_('# of expected attribues in response'),
help=_('The expected number of RADIUS attibutes in the response.'),
minvalue=0,
maxvalue=65535,
)),
('state_wrong_number_of_response_attributes',
MonitoringState(
title=_('Monitoring state on wrong # of response attributes'),
default_value=1,
)),
],
),
title='Response paranmeters',
))
],
required_keys=['secret']
required_keys=[
'secret',
'request_params',
'response_params',
]
),
)
......
......@@ -13,48 +13,146 @@
#
# https://github.com/pyradius/pyrad
#
import socket
from typing import Sequence
import sys
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, Namespace, ArgumentTypeError
from time import time_ns
from argparse import (
ArgumentDefaultsHelpFormatter,
ArgumentParser,
ArgumentTypeError,
Namespace,
)
from os import environ
from socket import error as socket_error
from sys import (
argv as sys_argv,
exit as sys_exit,
stdout as sys_stdout,
)
from time import time_ns
from typing import Sequence, Tuple
import cmk.utils.password_store
no_radiuslib = False
try:
from pyrad.client import Client as rad_client
from pyrad.dictionary import Dictionary as rad_dictionary
import pyrad.packet
from pyrad.client import Client as radClient
from pyrad.dictionary import Dictionary as radDictionary
from pyrad.packet import AccessAccept, AccessReject, AccessRequest
from pyrad.client import Timeout as pyTimeout
except ModuleNotFoundError:
no_radiuslib = True
def parse_arguments(argv: Sequence[str]) -> Namespace:
class Args(Namespace):
host: str
auth_port: int
secret: str
timeout: int
username: str
password: str
num_resp_attributes: int
state_wrong_num_resp_attributes: int
request_attribute: Tuple[str, str]
max_response_time: Tuple[int, int]
expected_response: int
state_not_expected_response: int
VERSION = '0.1.0-20240428'
cmk_state = {
0: '',
1: '(!)',
2: '(!!)',
3: '(?)',
}
response_str = {
2: 'accept',
3: 'reject',
}
def parse_arguments(argv: Sequence[str]) -> Args:
def _av_pair(s):
try:
name, value = s.split(':')
return name, value
except ValueError:
raise ArgumentTypeError("AV-Pairs must be in the form of name:vale")
def _levels(s) -> Tuple[int, int]:
try:
warn, crit = s.split(',')
warn = int(warn)
crit = int(crit)
return warn, crit
except ValueError:
raise ArgumentTypeError("Levels must be in the form 'warn,crit' value")
parser = ArgumentParser(
description='This is a (very) basic active RADIUS check for Check_mk. Tests if a RADIUS server is responsive '
'(accept/reject/timeout). There is (limited) support to add AV-pairs to the RADIUS request.',
formatter_class=ArgumentDefaultsHelpFormatter,
epilog=''
epilog=f'(c) thl-cmk[at]outlook[dot], Version: {VERSION}, For more information see: https://thl-cmk.hopto.org'
)
#
# required request parameters
#
parser.add_argument(
'-H', '--host', required=True,
help='Host/IP-Address of RADIUS server to query (required)')
help='Host/IP-Address of RADIUS server to query (required)',
)
parser.add_argument(
'--secret', required=True,
help='secret RADIUS key')
help='secret RADIUS key',
)
parser.add_argument(
'--username', default='dummyuser',
help='user name to test with')
'--username', default='dummyuser', required=True,
help='user name to test with',
)
parser.add_argument(
'--password', default='dummyuser',
help='user password to test with')
'--password', default='dummypassword', required=True,
help='user password to test with',
)
#
# optional request parameters
#
parser.add_argument(
'--authport', type=int, default=1812,
help='RADIUS authentication port to use.')
'--auth_port', type=int, default=1812,
help='RADIUS authentication port to use.',
)
parser.add_argument(
'--timeout', type=int, default=1,
help='RADIUS server timeout.')
help='RADIUS server timeout',
)
parser.add_argument(
'--request-attribute', nargs='*', type=_av_pair, action='append', default=[],
help='add request attribute in the form of "attribute-name:attribute-value" '
'ie: "Called-Station-Id:AA-BB-CC-DD-EE-FF". Repeat to add more attributes. '
'For valid attributes the dictionary file.',
)
#
# response parameters
#
parser.add_argument(
'--expected-response', type=int, choices=[2, 3],
help=' 2 -> Accepted, 3 -> Rejected',
)
parser.add_argument(
'--state-not-expected-response', type=int, choices=[0, 1, 2, 3], default=1,
help='Monitoring state: 0 -> OK, 1 -> WARN, 2 -> CRIT, 3 -> UNKNOWN',
)
parser.add_argument(
'--num-resp-attributes', type=int,
help='Expected number of response attributes',
)
parser.add_argument(
'--state-wrong-num-resp-attributes', type=int, choices=[0, 1, 2, 3], default=1,
help='Monitoring state: 0 -> OK, 1 -> WARN, 2 -> CRIT, 3 -> UNKNOWN',
)
parser.add_argument(
'--max-response-time', type=_levels,
help='Upper levels for response time in ms in the format WARN,CRIT time. ie: 10,50'
)
args = parser.parse_args(argv)
args.host = args.host.strip(' ')
......@@ -63,80 +161,130 @@ def parse_arguments(argv: Sequence[str]) -> Namespace:
def main(args=None):
if args is None:
args = sys.argv[1:] # without the path/plugin itself
args = sys_argv[1:] # without the path/plugin itself
args = parse_arguments(args)
if no_radiuslib:
sys.stdout.write(
'To use this check plugin you need to install the python pyrad lib in your CMK python environment.\n'
sys_stdout.write(
'To use this check plugin you need to install the python pyrad lib in your CMK python environment.(!!!)\n'
)
sys.exit(3)
sys_exit(3)
omd_root = environ["OMD_ROOT"]
info_text = ''
long_output = ''
perf_data = ''
info_text = []
long_output = []
perf_data = []
status = 0
rad_server = rad_client(
rad_server = radClient(
server=args.host,
authport=args.authport,
authport=args.auth_port,
secret=args.secret.encode('utf-8'),
dict=rad_dictionary(f'{omd_root}/local/lib/nagios/plugins/dictionary'),
# freeradius dictionaries are under /usr/share/freeradius/
dict=radDictionary(f'{omd_root}/local/lib/nagios/plugins/dictionary'),
timeout=args.timeout,
)
rad_req = rad_server.CreateAuthPacket(
code=pyrad.packet.AccessRequest,
code=AccessRequest,
User_Name=args.username,
NAS_Identifier=args.host,
)
rad_req["User-Password"] = rad_req.PwCrypt(args.password)
# add optional request attributes
for av_pair in args.request_attribute:
name, value = av_pair[0]
try:
rad_req[name] = value
except TypeError:
sys_stdout.write(
f'WARNING: attribute value must be the real value not the name of the '
f'value: {value}{cmk_state[1]}{cmk_state[1]}'
)
status = max(status, 1)
continue
before_request_time = time_ns()
try:
response = rad_server.SendPacket(rad_req)
except pyrad.client.Timeout as e:
status = 2
info_text = 'Radius request timeout'
long_output += f'\nRadius request timeout.\n{e}'
except socket.error as e:
status = 2
info_text = 'Network error'
long_output += f'\nNetwork error\n{e}'
except pyTimeout as e:
status = max(status, 2)
message = f'Radius request timeout{cmk_state[2]}'
info_text.append(message)
long_output.append(f'{message}\n{e}')
except socket_error as e:
status = max(status, 2)
message = f'Network error{cmk_state[2]}'
info_text.append(message)
long_output.append(f'{message}\n{e}')
else:
request_time = (time_ns() - before_request_time) / 1000 / 1000 / 1000 # -> ns to seconds
match response.code:
case pyrad.packet.AccessAccept:
info_text += 'Response: access accept'
long_output += '\nResponse: access accept'
long_output += f'\nResponse code: {response.code}'
if response.has_key:
long_output += f'\nNumber of attributes in response: {len(response.keys())}'
long_output += f'\n\nResponse attributes:'
for key in response.keys():
long_output += f'\n{key}: {response.get(key)}'
# first: calculate response time
response_time = (time_ns() - before_request_time) / 1000 / 1000 / 1000 # -> ns to seconds
#
# second: check response code
message = f'Response: access {response_str.get(response.code, f"unknown ({response.code})")}'
if args.expected_response and response.code != args.expected_response:
message += f' (expected: {response_str[args.expected_response]}{cmk_state[args.state_not_expected_response]})'
status = max(status, args.state_not_expected_response)
info_text.append(message)
long_output.append(message)
# third: check response time
message = f'Response time {response_time * 1000:.0f}ms'
if args.max_response_time:
warn, crit = args.max_response_time
warn = warn
crit = crit
if response_time >= warn / 1000:
message += f' (WARN/CRIT at {warn}/{crit}'
if response_time >= crit / 1000:
message += cmk_state[2]
status = max(status, 2)
else:
long_output += f'\nNo attributes in response: {len(response.keys())}'
case pyrad.packet.AccessReject:
info_text += 'Response: access reject'
long_output += '\nResponse: access reject'
long_output += f'\nResponse code: {response.code}'
case _:
info_text += f'Response: code unknown'
long_output += f'\nResponse: code unknown'
long_output += f'\nResponse code: {response.code}'
status = 3
perf_data += f'radius_request_time={request_time}'
info_text = info_text.strip(',').strip(' ')
sys.stdout.write(f'{info_text}\n{long_output} | {perf_data}\n')
message += cmk_state[1]
status = max(status, 1)
perf_data.append(f'radius_response_time={response_time};{warn};{crit};;')
else:
perf_data.append(f'radius_response_time={response_time}')
info_text.append(message)
long_output.append(message)
if response.code == AccessAccept:
#
# forth: check return attributes
if response.has_key:
message = f'Number of attributes in response: {len(response.keys())}'
else:
message = long_output.append('No return attributes in response')
if args.num_resp_attributes and len(response.keys()) != args.num_resp_attributes:
message += f' (expected {args.num_resp_attributes}{cmk_state[args.state_wrong_num_resp_attributes]})'
status = max(status, args.state_wrong_num_resp_attributes)
info_text.append(message)
long_output.append(message)
if response.has_key:
long_output.append('\nResponse attributes:')
for key in response.keys():
long_output.append(f'{key}: {response.get(key)}')
#
# format output data
info_text = ', '.join(info_text)
long_output = '\n'.join(long_output)
perf_data = '|'.join(perf_data)
if perf_data:
sys_stdout.write(f'{info_text}\n{long_output}|{perf_data}\n')
else:
sys_stdout.write(f'{info_text}\n{long_output}')
return status
if __name__ == '__main__':
cmk.utils.password_store.replace_passwords()
exitcode = main()
sys.exit(exitcode)
sys_exit(exitcode)
......@@ -230,7 +230,7 @@ ATTRIBUTE Autz-Type 1011 integer
# User Types
VALUE Service-Type Login-User 1
VALUE Service-Type Framed-User 2
VALUE Service-Type pdate 2
VALUE Service-Type Callback-Login-User 3
VALUE Service-Type Callback-Framed-User 4
VALUE Service-Type Outbound-User 5
......
File mode changed from 100644 to 100755
{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)',
'description': 'active RADIUS check\n',
'description': 'This is a (very) basic active RADIUS check:\n'
'\n'
' - tests if a RADIUS server is responsive '
'(accept/reject/timeout).\n'
' - (limited) support to add AV-pairs to the RADIUS request\n'
' - checks response code, response time and number of response '
'attributes\n'
'\n',
'download_url': 'https://thl-cmk.hopto.org',
'files': {'checks': ['check_radius'],
'gui': ['metrics/check_radius.py',
......@@ -9,7 +16,7 @@
'nagios/plugins/dictionary.freeradius']},
'name': 'check_radius',
'title': 'Check RADIUS',
'version': '0.0.1-20240421',
'version': '0.1.0-20240428',
'version.min_required': '2.2.0b1',
'version.packaged': '2.2.0p24',
'version.usable_until': None}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment