Merge pull request #86 from drewkerrigan/feature/verbose-http

Add flag to increase verbosity and flag to override unreachable state
This commit is contained in:
Markus Opolka 2024-05-14 16:55:46 +02:00 committed by GitHub
commit fa157753ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 227 additions and 100 deletions

1
.gitignore vendored
View File

@ -25,6 +25,7 @@ var/
.installed.cfg .installed.cfg
*.egg *.egg
.venv/ .venv/
venv/
# PyInstaller # PyInstaller
# Usually these files are written by a python script from a template # Usually these files are written by a python script from a template

View File

@ -6,7 +6,6 @@ import json
import argparse import argparse
import sys import sys
import ssl import ssl
from pprint import pprint
from urllib.error import HTTPError from urllib.error import HTTPError
from urllib.error import URLError from urllib.error import URLError
@ -69,20 +68,19 @@ class NagiosHelper:
code = UNKNOWN_CODE code = UNKNOWN_CODE
return code return code
def append_warning(self, warning_message): def append_message(self, code, msg):
self.warning_message += warning_message if code > 2 or code < 0:
self.unknown_message += msg
def append_critical(self, critical_message): if code == 1:
self.critical_message += critical_message self.warning_message += msg
if code == 2:
def append_unknown(self, unknown_message): self.critical_message += msg
self.unknown_message += unknown_message
def append_metrics(self, metrics): def append_metrics(self, metrics):
(performance_data, warning_message, critical_message) = metrics (performance_data, warning_message, critical_message) = metrics
self.performance_data += performance_data self.performance_data += performance_data
self.append_warning(warning_message) self.append_message(WARNING_CODE, warning_message)
self.append_critical(critical_message) self.append_message(CRITICAL_CODE, critical_message)
class JsonHelper: class JsonHelper:
@ -423,6 +421,9 @@ def parseArgs(args):
parser.add_argument('-d', '--debug', action='store_true', parser.add_argument('-d', '--debug', action='store_true',
help='debug mode') help='debug mode')
parser.add_argument('-v', '--verbose', action='count', default=0,
help='Verbose mode. Multiple -v options increase the verbosity')
parser.add_argument('-s', '--ssl', action='store_true', parser.add_argument('-s', '--ssl', action='store_true',
help='use TLS to connect to remote host') help='use TLS to connect to remote host')
parser.add_argument('-H', '--host', dest='host', parser.add_argument('-H', '--host', dest='host',
@ -444,6 +445,8 @@ def parseArgs(args):
parser.add_argument('-p', '--path', dest='path', help='Path') parser.add_argument('-p', '--path', dest='path', help='Path')
parser.add_argument('-t', '--timeout', type=int, parser.add_argument('-t', '--timeout', type=int,
help='Connection timeout (seconds)') help='Connection timeout (seconds)')
parser.add_argument('--unreachable-state', type=int, default=3,
help='Exit with specified code if URL unreachable. Examples: 1 for Warning, 2 for Critical, 3 for Unknown (default: 3)')
parser.add_argument('-B', '--basic-auth', dest='auth', parser.add_argument('-B', '--basic-auth', dest='auth',
help='Basic auth string "username:password"') help='Basic auth string "username:password"')
parser.add_argument('-D', '--data', dest='data', parser.add_argument('-D', '--data', dest='data',
@ -516,33 +519,30 @@ def parseArgs(args):
return parser.parse_args(args) return parser.parse_args(args)
def debugPrint(debug_flag, message, pretty_flag=False): def debugPrint(debug_flag, message):
""" """
Print debug messages if -d (debug_flat ) is set. Print debug messages if -d is set.
""" """
if not debug_flag:
return
if debug_flag:
if pretty_flag:
pprint(message)
else:
print(message) print(message)
def verbosePrint(verbose_flag, when, message):
def main(cliargs):
""" """
Main entrypoint for CLI Print verbose messages if -v is set.
Since -v can be used multiple times, the when parameter sets the required amount before printing
""" """
if not verbose_flag:
return
if verbose_flag >= when:
print(message)
args = parseArgs(cliargs) def prepare_context(args):
"""
Prepare TLS Context
"""
nagios = NagiosHelper() nagios = NagiosHelper()
context = None
if args.version:
print('Version: %s - Date: %s' % (__version__, __version_date__))
sys.exit(0)
if args.ssl:
url = "https://%s" % args.host
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.options |= ssl.OP_NO_SSLv2 context.options |= ssl.OP_NO_SSLv2
@ -558,38 +558,27 @@ def main(cliargs):
try: try:
context.load_verify_locations(args.cacert) context.load_verify_locations(args.cacert)
except ssl.SSLError: except ssl.SSLError:
nagios.append_unknown( nagios.append_message(UNKNOWN_CODE, 'Error loading SSL CA cert "%s"!' % args.cacert)
'Error loading SSL CA cert "%s"!'
% args.cacert)
if args.cert: if args.cert:
try: try:
context.load_cert_chain(args.cert, keyfile=args.key) context.load_cert_chain(args.cert, keyfile=args.key)
except ssl.SSLError: except ssl.SSLError:
if args.key: if args.key:
nagios.append_unknown( nagios.append_message(UNKNOWN_CODE, 'Error loading SSL cert. Make sure key "%s" belongs to cert "%s"!' % (args.key, args.cert))
'Error loading SSL cert. Make sure key "%s" belongs to cert "%s"!'
% (args.key, args.cert))
else: else:
nagios.append_unknown( nagios.append_message(UNKNOWN_CODE, 'Error loading SSL cert. Make sure "%s" contains the key as well!' % (args.cert))
'Error loading SSL cert. Make sure "%s" contains the key as well!'
% (args.cert))
if nagios.getCode() != OK_CODE: if nagios.getCode() != OK_CODE:
print(nagios.getMessage()) print(nagios.getMessage())
sys.exit(nagios.getCode()) sys.exit(nagios.getCode())
else: return context
url = "http://%s" % args.host
if args.port:
url += ":%s" % args.port
if args.path:
url += "/%s" % args.path
debugPrint(args.debug, "url:%s" % url)
json_data = ''
try: def make_request(args, url, context):
"""
Performs the actual request to the given URL
"""
req = urllib.request.Request(url, method=args.method) req = urllib.request.Request(url, method=args.method)
req.add_header("User-Agent", "check_http_json") req.add_header("User-Agent", "check_http_json")
if args.auth: if args.auth:
@ -615,31 +604,63 @@ def main(cliargs):
# pylint: disable=consider-using-with # pylint: disable=consider-using-with
response = urllib.request.urlopen(req, context=context) response = urllib.request.urlopen(req, context=context)
json_data = response.read() return response.read()
def main(cliargs):
"""
Main entrypoint for CLI
"""
args = parseArgs(cliargs)
nagios = NagiosHelper()
context = None
if args.version:
print('Version: %s - Date: %s' % (__version__, __version_date__))
sys.exit(0)
if args.ssl:
url = "https://%s" % args.host
context = prepare_context(args)
else:
url = "http://%s" % args.host
if args.port:
url += ":%s" % args.port
if args.path:
url += "/%s" % args.path
debugPrint(args.debug, "url: %s" % url)
json_data = ''
try:
json_data = make_request(args, url, context)
except HTTPError as e: except HTTPError as e:
# Try to recover from HTTP Error, if there is JSON in the response # Try to recover from HTTP Error, if there is JSON in the response
if "json" in e.info().get_content_subtype(): if "json" in e.info().get_content_subtype():
json_data = e.read() json_data = e.read()
else: else:
nagios.append_unknown(" HTTPError[%s], url:%s" % (str(e.code), url)) nagios.append_message(UNKNOWN_CODE, " Could not find JSON in HTTP body. HTTPError[%s], url:%s" % (str(e.code), url))
except URLError as e: except URLError as e:
nagios.append_critical(" URLError[%s], url:%s" % (str(e.reason), url)) # Some users might prefer another exit code if the URL wasn't reached
exit_code = args.unreachable_state
nagios.append_message(exit_code, " URLError[%s], url:%s" % (str(e.reason), url))
# Since we don't got any data, we can simply exit
print(nagios.getMessage())
sys.exit(nagios.getCode())
try: try:
data = json.loads(json_data) data = json.loads(json_data)
except ValueError as e: except ValueError as e:
nagios.append_unknown(" Parser error: %s" % str(e)) nagios.append_message(UNKNOWN_CODE, " JSON Parser error: %s" % str(e))
else: else:
debugPrint(args.debug, 'json:') verbosePrint(args.verbose, 1, json.dumps(data, indent=2))
debugPrint(args.debug, data, True)
# Apply rules to returned JSON data # Apply rules to returned JSON data
processor = JsonRuleProcessor(data, args) processor = JsonRuleProcessor(data, args)
nagios.append_warning(processor.checkWarning()) nagios.append_message(WARNING_CODE, processor.checkWarning())
nagios.append_critical(processor.checkCritical()) nagios.append_message(CRITICAL_CODE, processor.checkCritical())
nagios.append_metrics(processor.checkMetrics()) nagios.append_metrics(processor.checkMetrics())
nagios.append_unknown(processor.checkUnknown()) nagios.append_message(UNKNOWN_CODE, processor.checkUnknown())
# Print Nagios specific string and exit appropriately # Print Nagios specific string and exit appropriately
print(nagios.getMessage()) print(nagios.getMessage())

View File

@ -1,2 +1,2 @@
coverage==6.4.4 coverage==6.5.0
pylint==2.15.2 pylint==2.17.7

View File

@ -84,10 +84,10 @@ class UtilTest(unittest.TestCase):
data = json.loads(jsondata) data = json.loads(jsondata)
nagios = NagiosHelper() nagios = NagiosHelper()
processor = JsonRuleProcessor(data, args) processor = JsonRuleProcessor(data, args)
nagios.append_warning(processor.checkWarning()) nagios.append_message(WARNING_CODE, processor.checkWarning())
nagios.append_critical(processor.checkCritical()) nagios.append_message(CRITICAL_CODE, processor.checkCritical())
nagios.append_metrics(processor.checkMetrics()) nagios.append_metrics(processor.checkMetrics())
nagios.append_unknown(processor.checkUnknown()) nagios.append_message(UNKNOWN_CODE, processor.checkUnknown())
self.assertEqual(code, nagios.getCode()) self.assertEqual(code, nagios.getCode())
def test_metrics(self): def test_metrics(self):

View File

@ -9,6 +9,7 @@ import os
sys.path.append('..') sys.path.append('..')
from check_http_json import debugPrint from check_http_json import debugPrint
from check_http_json import verbosePrint
class CLITest(unittest.TestCase): class CLITest(unittest.TestCase):
@ -31,10 +32,13 @@ class CLITest(unittest.TestCase):
debugPrint(True, 'debug') debugPrint(True, 'debug')
mock_print.assert_called_once_with('debug') mock_print.assert_called_once_with('debug')
def test_debugprint_pprint(self): def test_verbose(self):
with mock.patch('check_http_json.pprint') as mock_pprint: with mock.patch('builtins.print') as mock_print:
debugPrint(True, 'debug', True) verbosePrint(0, 3, 'verbose')
mock_pprint.assert_called_once_with('debug') mock_print.assert_not_called()
verbosePrint(3, 3, 'verbose')
mock_print.assert_called_once_with('verbose')
def test_cli_without_params(self): def test_cli_without_params(self):

View File

@ -95,3 +95,37 @@ class MainTest(unittest.TestCase):
main(args) main(args)
self.assertEqual(test.exception.code, 0) self.assertEqual(test.exception.code, 0)
@mock.patch('builtins.print')
def test_main_with_tls(self, mock_print):
args = ['-H', 'localhost',
'--ssl',
'--cacert',
'test/tls/ca-root.pem',
'--cert',
'test/tls/cert.pem',
'--key',
'test/tls/key.pem']
with self.assertRaises(SystemExit) as test:
main(args)
self.assertTrue('https://localhost' in str(mock_print.call_args))
self.assertEqual(test.exception.code, 3)
@mock.patch('builtins.print')
def test_main_with_tls_wrong_ca(self, mock_print):
args = ['-H', 'localhost',
'--ssl',
'--cacert',
'test/tls/key.pem',
'--cert',
'test/tls/cert.pem',
'--key',
'test/tls/key.pem']
with self.assertRaises(SystemExit) as test:
main(args)
self.assertTrue('Error loading SSL CA' in str(mock_print.call_args))
self.assertEqual(test.exception.code, 3)

21
test/tls/ca-root.pem Normal file
View File

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDbTCCAlWgAwIBAgIUB6EZDl3ajJgJsoLzyC9DrOQQpKowDQYJKoZIhvcNAQEN
BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yNDAzMTgwODE5MDhaGA8yMDUx
MDgwMzA4MTkwOFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBALVxioj+6zw6Snr+B1JOivC8Of6YptVYym5ICiHX
wjpbSVVe+Py/P2LDb/uQ1QkAENlpvChFqSaRBZU5keXYS/DaFb2Evb2/zf5qIdWU
2ju8B5V13gXSeaNNetyEn1Ivvk0lOCQo2RwEZXuStpLS4Q32rkRBvkoL+RXDc1NX
c3RwcU1p9ybgBqAC7FYdV82sgHGugIrbzkjfFREJXp1AnqvKAdk39b1CnPxfmPZC
nzPPetfr3iivH8yVO5rodU/LDtQNph22JR94YvPB89QO+bZ9bw2GHtPdAKFew9HF
UxM1fmy381Mq2iS3KUq5vsC1jMe8slUAIFYEDzoPvOz+MpcCAwEAAaNTMFEwHQYD
VR0OBBYEFOmCb+JnMzX29hwgtXSzrN+m6mTDMB8GA1UdIwQYMBaAFOmCb+JnMzX2
9hwgtXSzrN+m6mTDMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQENBQADggEB
AAkTD8K4UO4uO4i6p2BCofbhVm9LYA0ulmLO8Uro0U491TeEDOQpgMFAK+b2gZIU
zvDHoCMn3UPVxHKl7XzDgLZVkYYEc2s9vArxk5vSnFmh3XvlDu2SO5gSLB2sf68A
2+Jz2x6z9tjWWdZCGJWU/iwMbG2Y3JMHyv1NMF8cyOclJaSDNBAwF5c5sdlGTLKb
WHGXzVqHSAFlGcHtQrcEKclHiuzw2G3LZzwghGk0XzxwvyKrnAEy408RY0mfNLtz
32KHqYtrip0RYlGWKP7/7q6i0D8muEFW/I4emFI0z0I/1CcYZZS8tQkWaPf/wCN0
llTD1kKJACsIMaqkkyy+EZM=
-----END CERTIFICATE-----

19
test/tls/cert.pem Normal file
View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDDzCCAfcCFBOrBcHIH2x9xcUyUeDid0cvBxWtMA0GCSqGSIb3DQEBDQUAMEUx
CzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl
cm5ldCBXaWRnaXRzIFB0eSBMdGQwIBcNMjQwMzE4MDgxOTM1WhgPMjA1MTA4MDMw
ODE5MzVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYD
VQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEeMA0GCSqGSIb3DQEBAQUA
A4IBCwAwggEGAoH+ALuzyIhEATF5YyAOsXKfr2mttF2HyJvEscGcoA7YetT57bjJ
5lg944kc3QH/wTEdrGda3cwh3OXdUuyR7Wrm9jPw38hMArx/fWPkiISOShrUSHGd
Qyy2bT+zxBaUo+pomyrlqlgwGlbxuwTAlTSFcI+i7yXrckl2HRj40EW4FNsYpPzv
maxRXs0kg0J2JLTYF+fHlqlYbSX/hRU9wz2DYfkRSS0+OYJNSmqK0jayUsdZYurG
gbPwOCgQ0QxLLh7P8z4sOanRowqUzqTI77cyUugEJRyoi+LJr4r0EwMTBX3STgPh
S9B78+LNvwOrLrZFUhr144RfO9QPLnz0uWcCAwEAATANBgkqhkiG9w0BAQ0FAAOC
AQEAeIR21PfLzgpL7WDBE2KgwI78nVc1wY9nwoAxSBzHjS0Olve3r9MaVzAKn5ZS
xHtv8oroXjhTcczCIzxii6Imp6u0iIr3QVBIceofxrH3aWmICURcC9l+dIiY6sk9
Ct8P8gm/Erv2iF/7bnsARwDnw0f41fC9eXtHZ7WLRQrc7tLHpjL0Z7bT77ysQJVK
C1SWtBnq3afmwH3R1wVHENn0JVFQpBp+vqWU5KIlvjcz49yPU+aNODk1rJsHMlgS
x2iddwF31GNOxNfXtw8fdw4UDUl2wYoZ45w2e2pXt4pbN43m0Wys1eQZdk3tyR6G
AZOLP05073mLtbVlFRmcTdXIGg==
-----END CERTIFICATE-----

27
test/tls/key.pem Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN PRIVATE KEY-----
MIIEqAIBADANBgkqhkiG9w0BAQEFAASCBJIwggSOAgEAAoH+ALuzyIhEATF5YyAO
sXKfr2mttF2HyJvEscGcoA7YetT57bjJ5lg944kc3QH/wTEdrGda3cwh3OXdUuyR
7Wrm9jPw38hMArx/fWPkiISOShrUSHGdQyy2bT+zxBaUo+pomyrlqlgwGlbxuwTA
lTSFcI+i7yXrckl2HRj40EW4FNsYpPzvmaxRXs0kg0J2JLTYF+fHlqlYbSX/hRU9
wz2DYfkRSS0+OYJNSmqK0jayUsdZYurGgbPwOCgQ0QxLLh7P8z4sOanRowqUzqTI
77cyUugEJRyoi+LJr4r0EwMTBX3STgPhS9B78+LNvwOrLrZFUhr144RfO9QPLnz0
uWcCAwEAAQKB/UQAYzMy5/fDkWzoxdLQFV3E56ZG7h+4x+lr0/Ts6rtD/KLIyqHH
ciqXgV4bCSPBK1eabOZqkjvYzhUU3R2wpRu2NWy8VPVzfrr07ZyQbDqCE+jNX6vQ
P44nk2/W0/e1hBmrcOZYLwK2utmC58tKWLhBAEENpq8EkpAcfF/1y9aRHKYwNnH7
vouoQibN5NTs5m8s0VyjRTDwRZja98eWnn5NfU3orqYO8fSlF6CyzDtoyhMco6zR
0skBgMzRYCRTuJpV+KekC7XFYyiJ6XZN5DKLbbqP6Y7YR8wjyFEruoGCS0mZH2H0
9/rhTsJram1B2zohXHPsHJGGGv12/7kCfw5C7yda+8Yv0NmRp1F+EJYb75SCAWIP
kzN/xvjP2bMKa6oSzU0DOga3Wc4ijJHDaND8rqdPqQe3zXFr1nPdBrybLSJ6k5CN
4Dd6ENJWVWino0L460kpLtlBG6TsgmB8bkwhjWVE6Vgt4Vila+a3TGRXeniaRzdw
icNOtMrjYlUCfw0pWEvO2uFq0DbNZbmzC2j5ClFcU96CAl4AqKG2PiGnuSy9TKVZ
c5OiXFmyoig7v4LJzaKLSqVIN4hVBU80/MlhvG+dpeimvLaQKNtlZQethIs5hXlB
R1XfaPhq6BQiYmQ3tufyS/0Es2OY+Cs3LU1uDB8qVzonlmnIi69OwMsCfwRPISfJ
C+4UIIy8v8uVxbk1c6xxo61Xe2jCIQKo+uRoL6PRzoqIgQ3qdI4eTk70tkT/NF6F
aVNVrBOrO78Cd7ihQn/6fX/d/nOExHRpdaELlf70a1NNyEQIsiug8rvonQMP2ENT
ERZ9tmssgG/Tzpc6/1xVcVNFA7spmuL61YkCfwnu2zGTc0PO7kd96rkktIbL9YqD
6NQ0QH8bdildtjSGNc3bLB5ajUytq48Sryk4NogJr8Vt5K8q+qZMrE4kCmgd+C4w
x4b3V9Ncp0k1k/MgdLjyd5aUurbHfpyFapPPg3xpRAR3q/vP8WdIintrECiw1jsr
JFvChtVdQnbTM9MCfw41RcjNwCaIG+uXc8bD6Yf+NyXD8zP6ZDywmBlkMWlGSzx4
xM8J+wQiQsNWthDBbF7inJc+lbtJiEe4YOPkbjCYVZRHribL65HKJlEUv6M9bvQo
3P1DS5tDrwo6z9UPs4tD1SgF9fDu/xA7fwPF1RTvuW07MhFJWlDo4FSWS9c=
-----END PRIVATE KEY-----