Commit d54130d18295995378f55f86f534032dc8a40523

Authored by Frederik Lindenaar
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 +
... ...