Commit d54130d18295995378f55f86f534032dc8a40523
1 parent
8afdb5e3
- set HTTP User-Agent header to check_otp/VERSION
- added SSL validation options (--no-ssl-validation, --cacert, --capath) to deal with SSL validation issues
Showing
1 changed file
with
33 additions
and
7 deletions
plugins/check_otp
... | ... | @@ -2,10 +2,10 @@ |
2 | 2 | # |
3 | 3 | # check_otp - Nagios check plugin for LinOTP/PrivacyIDEA OTP validation |
4 | 4 | # |
5 | -# Version 1.0, latest version, documentation and bugtracker available at: | |
5 | +# Version 1.1, latest version, documentation and bugtracker available at: | |
6 | 6 | # https://gitlab.lindenaar.net/scripts/nagios-plugins |
7 | 7 | # |
8 | -# Copyright (c) 2016 Frederik Lindenaar | |
8 | +# Copyright (c) 2018 Frederik Lindenaar | |
9 | 9 | # |
10 | 10 | # This script is free software: you can redistribute and/or modify it under the |
11 | 11 | # terms of version 3 of the GNU General Public License as published by the Free |
... | ... | @@ -25,14 +25,18 @@ from hashlib import sha1 |
25 | 25 | from getpass import getpass |
26 | 26 | from urllib import urlencode |
27 | 27 | from urllib2 import Request, HTTPError, URLError, urlopen |
28 | +from ssl import CertificateError, \ | |
29 | + create_default_context as create_default_SSL_context, \ | |
30 | + _create_unverified_context as create_unverified_SSL_context | |
28 | 31 | from base64 import b16decode, b32decode, standard_b64decode |
29 | 32 | from argparse import ArgumentParser as StandardArgumentParser, FileType, \ |
30 | 33 | _StoreAction as StoreAction, _StoreConstAction as StoreConstAction |
31 | 34 | |
32 | 35 | # Constants (no need to change but allows for easy customization) |
33 | -VERSION="1.0" | |
36 | +VERSION="1.1" | |
34 | 37 | PROG_NAME=os.path.splitext(os.path.basename(__file__))[0] |
35 | 38 | PROG_VERSION=PROG_NAME + ' ' + VERSION |
39 | +HTTP_AGENT=PROG_NAME + '/' + VERSION | |
36 | 40 | URL_API_SUFFIX='/validate/check' |
37 | 41 | ENV_VAR_USER='USER_NAME' |
38 | 42 | ENV_VAR_PWD='USER_PASSWORD' |
... | ... | @@ -222,8 +226,18 @@ def parse_args(): |
222 | 226 | help='port number to connect to (only used with -H)') |
223 | 227 | parser.add_argument('-P', '--path', |
224 | 228 | help='URL path to be used (only used with -H)') |
229 | + | |
225 | 230 | parser.add_argument('-S', '--no-ssl', action='store_true', |
226 | 231 | help='connect WITHOUT SSL (only used with -H)') |
232 | + parser.add_argument('--no-ssl-validation', | |
233 | + action='store_const', dest='sslcontext', | |
234 | + const=create_unverified_SSL_context(), | |
235 | + default=create_default_SSL_context(), | |
236 | + help='Do not validate server SSL certificate (DANGEROUS)') | |
237 | + parser.add_argument('--cacert', | |
238 | + help='CA Certificate file for SSL server validation') | |
239 | + parser.add_argument('--capath', | |
240 | + help='CA Certificate directory for SSL server validation') | |
227 | 241 | |
228 | 242 | parser.add_argument('-n', '--name', default=envvar(ENV_VAR_NAME, PROG_NAME), |
229 | 243 | help="name in authentication request for logging " |
... | ... | @@ -319,6 +333,15 @@ def parse_args(): |
319 | 333 | if not isempty(args.path): |
320 | 334 | args.url+= args.path if args.path[0]=='/' else '/' + args.path |
321 | 335 | |
336 | + # Pass-on CA file/path to SSLContext if provided on the command line | |
337 | + if not isempty(args.cacert): | |
338 | + try: | |
339 | + args.sslcontext.load_verify_locations(cafile=args.cacert) | |
340 | + except IOError as e: | |
341 | + args.cmdparser.error("cafile '%s': %s" % (args.cacert, str(e))) | |
342 | + elif not isempty(args.capath): | |
343 | + args.sslcontext.load_verify_locations(capath=args.capath) | |
344 | + | |
322 | 345 | # We should now be ready to authenticate, fail if that's not the case |
323 | 346 | if args.func == Password: |
324 | 347 | if isempty(args.login) and isempty(args.serial) \ |
... | ... | @@ -336,7 +359,7 @@ def parse_args(): |
336 | 359 | return args |
337 | 360 | |
338 | 361 | |
339 | -def checkotp(url, subject, secret, isserial=False, nas=None): | |
362 | +def checkotp(url, subject, secret, isserial=False, nas=None, sslcontext=create_default_SSL_context()): | |
340 | 363 | """Check a subject (user or token) with secret against PrivacyIDEA / LinOTP. |
341 | 364 | |
342 | 365 | Args: |
... | ... | @@ -363,7 +386,9 @@ def checkotp(url, subject, secret, isserial=False, nas=None): |
363 | 386 | v if k!='pass' else '***MASKED***') for k,v in params.iteritems()])) |
364 | 387 | |
365 | 388 | # Perform the API authentication request |
366 | - response = json.load(urlopen(Request(url, data=urlencode(params)))) | |
389 | + response = json.load(urlopen(Request(url, data=urlencode(params), | |
390 | + headers={'User-Agent':HTTP_AGENT}), | |
391 | + context=sslcontext)) | |
367 | 392 | if logger.isEnabledFor(logging.DEBUG): |
368 | 393 | logger.debug('result: %s', json.dumps(response, indent=4)) |
369 | 394 | |
... | ... | @@ -407,10 +432,10 @@ if __name__ == '__main__': |
407 | 432 | response=checkotp(args.url, |
408 | 433 | args.login if isempty(args.serial) else args.serial, |
409 | 434 | secret, isserial=not isempty(args.serial), |
410 | - nas=args.name) | |
435 | + nas=args.name, sslcontext=args.sslcontext) | |
411 | 436 | endtime = time() |
412 | 437 | |
413 | - except (HTTPError, URLError) as e: | |
438 | + except (HTTPError, URLError, CertificateError) as e: | |
414 | 439 | nagios_exit(NAGIOS_CRITICAL,'%s request failed: %s' % (message, e)) |
415 | 440 | |
416 | 441 | except (KeyboardInterrupt, EOFError) as e: |
... | ... | @@ -478,3 +503,4 @@ if __name__ == '__main__': |
478 | 503 | |
479 | 504 | nagios_exit(nagiosresult, message, [ |
480 | 505 | ('time', [ elapse, args.warn, args.critical, 0, None ])]) |
506 | + | |
... | ... |