- set HTTP User-Agent header to check_otp/VERSION
- added SSL validation options (--no-ssl-validation, --cacert, --capath) to deal with SSL validation issues
This commit is contained in:
@@ -2,10 +2,10 @@
|
|||||||
#
|
#
|
||||||
# check_otp - Nagios check plugin for LinOTP/PrivacyIDEA OTP validation
|
# check_otp - Nagios check plugin for LinOTP/PrivacyIDEA OTP validation
|
||||||
#
|
#
|
||||||
# Version 1.0, latest version, documentation and bugtracker available at:
|
# Version 1.1, latest version, documentation and bugtracker available at:
|
||||||
# https://gitlab.lindenaar.net/scripts/nagios-plugins
|
# https://gitlab.lindenaar.net/scripts/nagios-plugins
|
||||||
#
|
#
|
||||||
# Copyright (c) 2016 Frederik Lindenaar
|
# Copyright (c) 2018 Frederik Lindenaar
|
||||||
#
|
#
|
||||||
# This script is free software: you can redistribute and/or modify it under the
|
# This script is free software: you can redistribute and/or modify it under the
|
||||||
# terms of version 3 of the GNU General Public License as published by the Free
|
# terms of version 3 of the GNU General Public License as published by the Free
|
||||||
@@ -25,14 +25,18 @@ from hashlib import sha1
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
from urllib2 import Request, HTTPError, URLError, urlopen
|
from urllib2 import Request, HTTPError, URLError, urlopen
|
||||||
|
from ssl import CertificateError, \
|
||||||
|
create_default_context as create_default_SSL_context, \
|
||||||
|
_create_unverified_context as create_unverified_SSL_context
|
||||||
from base64 import b16decode, b32decode, standard_b64decode
|
from base64 import b16decode, b32decode, standard_b64decode
|
||||||
from argparse import ArgumentParser as StandardArgumentParser, FileType, \
|
from argparse import ArgumentParser as StandardArgumentParser, FileType, \
|
||||||
_StoreAction as StoreAction, _StoreConstAction as StoreConstAction
|
_StoreAction as StoreAction, _StoreConstAction as StoreConstAction
|
||||||
|
|
||||||
# Constants (no need to change but allows for easy customization)
|
# Constants (no need to change but allows for easy customization)
|
||||||
VERSION="1.0"
|
VERSION="1.1"
|
||||||
PROG_NAME=os.path.splitext(os.path.basename(__file__))[0]
|
PROG_NAME=os.path.splitext(os.path.basename(__file__))[0]
|
||||||
PROG_VERSION=PROG_NAME + ' ' + VERSION
|
PROG_VERSION=PROG_NAME + ' ' + VERSION
|
||||||
|
HTTP_AGENT=PROG_NAME + '/' + VERSION
|
||||||
URL_API_SUFFIX='/validate/check'
|
URL_API_SUFFIX='/validate/check'
|
||||||
ENV_VAR_USER='USER_NAME'
|
ENV_VAR_USER='USER_NAME'
|
||||||
ENV_VAR_PWD='USER_PASSWORD'
|
ENV_VAR_PWD='USER_PASSWORD'
|
||||||
@@ -222,8 +226,18 @@ def parse_args():
|
|||||||
help='port number to connect to (only used with -H)')
|
help='port number to connect to (only used with -H)')
|
||||||
parser.add_argument('-P', '--path',
|
parser.add_argument('-P', '--path',
|
||||||
help='URL path to be used (only used with -H)')
|
help='URL path to be used (only used with -H)')
|
||||||
|
|
||||||
parser.add_argument('-S', '--no-ssl', action='store_true',
|
parser.add_argument('-S', '--no-ssl', action='store_true',
|
||||||
help='connect WITHOUT SSL (only used with -H)')
|
help='connect WITHOUT SSL (only used with -H)')
|
||||||
|
parser.add_argument('--no-ssl-validation',
|
||||||
|
action='store_const', dest='sslcontext',
|
||||||
|
const=create_unverified_SSL_context(),
|
||||||
|
default=create_default_SSL_context(),
|
||||||
|
help='Do not validate server SSL certificate (DANGEROUS)')
|
||||||
|
parser.add_argument('--cacert',
|
||||||
|
help='CA Certificate file for SSL server validation')
|
||||||
|
parser.add_argument('--capath',
|
||||||
|
help='CA Certificate directory for SSL server validation')
|
||||||
|
|
||||||
parser.add_argument('-n', '--name', default=envvar(ENV_VAR_NAME, PROG_NAME),
|
parser.add_argument('-n', '--name', default=envvar(ENV_VAR_NAME, PROG_NAME),
|
||||||
help="name in authentication request for logging "
|
help="name in authentication request for logging "
|
||||||
@@ -319,6 +333,15 @@ def parse_args():
|
|||||||
if not isempty(args.path):
|
if not isempty(args.path):
|
||||||
args.url+= args.path if args.path[0]=='/' else '/' + args.path
|
args.url+= args.path if args.path[0]=='/' else '/' + args.path
|
||||||
|
|
||||||
|
# Pass-on CA file/path to SSLContext if provided on the command line
|
||||||
|
if not isempty(args.cacert):
|
||||||
|
try:
|
||||||
|
args.sslcontext.load_verify_locations(cafile=args.cacert)
|
||||||
|
except IOError as e:
|
||||||
|
args.cmdparser.error("cafile '%s': %s" % (args.cacert, str(e)))
|
||||||
|
elif not isempty(args.capath):
|
||||||
|
args.sslcontext.load_verify_locations(capath=args.capath)
|
||||||
|
|
||||||
# We should now be ready to authenticate, fail if that's not the case
|
# We should now be ready to authenticate, fail if that's not the case
|
||||||
if args.func == Password:
|
if args.func == Password:
|
||||||
if isempty(args.login) and isempty(args.serial) \
|
if isempty(args.login) and isempty(args.serial) \
|
||||||
@@ -336,7 +359,7 @@ def parse_args():
|
|||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
def checkotp(url, subject, secret, isserial=False, nas=None):
|
def checkotp(url, subject, secret, isserial=False, nas=None, sslcontext=create_default_SSL_context()):
|
||||||
"""Check a subject (user or token) with secret against PrivacyIDEA / LinOTP.
|
"""Check a subject (user or token) with secret against PrivacyIDEA / LinOTP.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -363,7 +386,9 @@ def checkotp(url, subject, secret, isserial=False, nas=None):
|
|||||||
v if k!='pass' else '***MASKED***') for k,v in params.iteritems()]))
|
v if k!='pass' else '***MASKED***') for k,v in params.iteritems()]))
|
||||||
|
|
||||||
# Perform the API authentication request
|
# Perform the API authentication request
|
||||||
response = json.load(urlopen(Request(url, data=urlencode(params))))
|
response = json.load(urlopen(Request(url, data=urlencode(params),
|
||||||
|
headers={'User-Agent':HTTP_AGENT}),
|
||||||
|
context=sslcontext))
|
||||||
if logger.isEnabledFor(logging.DEBUG):
|
if logger.isEnabledFor(logging.DEBUG):
|
||||||
logger.debug('result: %s', json.dumps(response, indent=4))
|
logger.debug('result: %s', json.dumps(response, indent=4))
|
||||||
|
|
||||||
@@ -407,10 +432,10 @@ if __name__ == '__main__':
|
|||||||
response=checkotp(args.url,
|
response=checkotp(args.url,
|
||||||
args.login if isempty(args.serial) else args.serial,
|
args.login if isempty(args.serial) else args.serial,
|
||||||
secret, isserial=not isempty(args.serial),
|
secret, isserial=not isempty(args.serial),
|
||||||
nas=args.name)
|
nas=args.name, sslcontext=args.sslcontext)
|
||||||
endtime = time()
|
endtime = time()
|
||||||
|
|
||||||
except (HTTPError, URLError) as e:
|
except (HTTPError, URLError, CertificateError) as e:
|
||||||
nagios_exit(NAGIOS_CRITICAL,'%s request failed: %s' % (message, e))
|
nagios_exit(NAGIOS_CRITICAL,'%s request failed: %s' % (message, e))
|
||||||
|
|
||||||
except (KeyboardInterrupt, EOFError) as e:
|
except (KeyboardInterrupt, EOFError) as e:
|
||||||
@@ -478,3 +503,4 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
nagios_exit(nagiosresult, message, [
|
nagios_exit(nagiosresult, message, [
|
||||||
('time', [ elapse, args.warn, args.critical, 0, None ])])
|
('time', [ elapse, args.warn, args.critical, 0, None ])])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user