├── .coveragerc ├── MANIFEST.in ├── Makefile ├── release.cfg ├── docs ├── CONTRIBUTORS.rst ├── CHANGES.rst └── LICENSE.txt ├── setup.cfg ├── src └── checkpkgaudit │ ├── __init__.py │ ├── checkpkgaudit.py │ └── tests │ └── test_checkauditpkg.py ├── .travis.yml ├── .gitignore ├── setup.py ├── README.rst └── bootstrap-buildout.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = src 3 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt *.cfg *.rst 2 | 3 | recursive-include docs * 4 | recursive-include src * 5 | 6 | global-exclude *pyc .DS_Store .installed.cfg 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | test: nosetests flake8 2 | 3 | nosetests: 4 | @echo "==== Running nosetests ====" 5 | @bin/test 6 | 7 | flake8: 8 | @echo "==== Running Flake8 ====" 9 | @bin/flake8 src 10 | -------------------------------------------------------------------------------- /release.cfg: -------------------------------------------------------------------------------- 1 | [buildout] 2 | extends = buildout.cfg 3 | parts += release 4 | 5 | [release] 6 | recipe = zc.recipe.egg 7 | eggs = zest.releaser 8 | 9 | versions = versions 10 | 11 | [versions] 12 | zest.releaser = 3.55 13 | -------------------------------------------------------------------------------- /docs/CONTRIBUTORS.rst: -------------------------------------------------------------------------------- 1 | Contributors 2 | ============== 3 | Mathias : Lcaracol 4 | 5 | Damien LACOSTE : Dam64 6 | 7 | Thomas BALDAQUIN : blQn 8 | 9 | Simon RECHER : voileux 10 | 11 | Steffen Brandemann : StbX 12 | 13 | Nicolas RAHIR : nox 14 | 15 | Jean-Philippe Camguilhem, Author 16 | 17 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | # 2 | [nosetests] 3 | match = ^test 4 | with-coverage = 1 5 | cover-package = checkpkgaudit 6 | cover-min-percentage = 100 7 | cover-erase = 1 8 | with-doctest = 1 9 | doctest-extension=rst 10 | doctest-options=+ELLIPSIS,+NORMALIZE_WHITESPACE 11 | 12 | [flake8] 13 | exclude = bootstrap-buildout.py 14 | -------------------------------------------------------------------------------- /src/checkpkgaudit/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | check_pkgaudit 6 | ------------------------------ 7 | Check FreeBSD pkg audit Nagios|Icinga|shinken|etc plugin. 8 | 9 | """ 10 | 11 | from .checkpkgaudit import main # NOQA 12 | 13 | # vim:set et sts=4 ts=4 tw=80: 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 2.7 4 | - 3.3 5 | - 3.4 6 | - 3.5 7 | - 3.6 8 | - pypy 9 | - pypy3 10 | matrix: 11 | include: 12 | - python: 3.7 13 | dist: xenial 14 | sudo: true 15 | install: 16 | - python bootstrap-buildout.py --setuptools-version=33.1.1 --buildout-version=2.5.2 17 | - bin/buildout -N 18 | script: 19 | - make -k test 20 | after_success: 21 | - easy_install coveralls 22 | - coveralls 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | bin/ 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .cache 41 | nosetests.xml 42 | coverage.xml 43 | 44 | # Translations 45 | *.mo 46 | *.pot 47 | 48 | # Django stuff: 49 | *.log 50 | 51 | # Sphinx documentation 52 | docs/_build/ 53 | 54 | # PyBuilder 55 | target/ 56 | 57 | # vim 58 | *.swp 59 | -------------------------------------------------------------------------------- /docs/CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 0.7.3 (unreleased) 5 | ------------------ 6 | 7 | - Nothing changed yet. 8 | 9 | 10 | 0.7.2 (2017-06-05) 11 | ------------------ 12 | 13 | - fix python3 support https://github.com/jpcw/checkpkgaudit/issues/10 14 | 15 | 16 | 0.7.1 (2017-03-08) 17 | ------------------ 18 | 19 | - README improvment -- Lcaracol 20 | 21 | 22 | 0.7 (2017-03-07) 23 | ---------------- 24 | 25 | - fix missing ip jls output with vnet jails https://github.com/jpcw/checkpkgaudit/issues/4 -- blQn 26 | - remove py2.6, py32 and add py3.6 support 27 | 28 | 29 | 0.6 (2016-03-14) 30 | ---------------- 31 | 32 | - add exclusion for hastd -- voileux 33 | 34 | 35 | 0.5 (2016-03-11) 36 | ---------------- 37 | 38 | - add support for jails with different jails and hostnames -- StbX 39 | 40 | 41 | 0.4 (2015-03-21) 42 | ---------------- 43 | 44 | - improve README with possible pypi ssl certificate problem, provide a workaround 45 | 46 | 47 | 0.3 (2015-03-21) 48 | ---------------- 49 | 50 | - fix install README typo -- Nicolas RAHIR nox 51 | 52 | - add NRPE conf sample -- Nicolas RAHIR nox 53 | 54 | 55 | 0.2 (2015-03-06) 56 | ---------------- 57 | 58 | - fix badges 59 | 60 | 61 | 0.1 (2015-03-06) 62 | ---------------- 63 | 64 | - Jean-Philippe Camguilhem 65 | 66 | -------------------------------------------------------------------------------- /docs/LICENSE.txt: -------------------------------------------------------------------------------- 1 | checkpkgaudit Copyright (c) 2015, Jean-Philippe Camguilhem 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 3. The name of the author may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | import os 5 | 6 | version = '0.7.3.dev0' 7 | 8 | here = os.path.abspath(os.path.dirname(__file__)) 9 | 10 | 11 | def read_file(*pathes): 12 | path = os.path.join(here, *pathes) 13 | if os.path.isfile(path): 14 | with open(path, 'r') as desc_file: 15 | return desc_file.read() 16 | else: 17 | return '' 18 | 19 | 20 | desc_files = (('README.rst',), ('docs', 'CHANGES.rst'), 21 | ('docs', 'CONTRIBUTORS.rst')) 22 | 23 | long_description = '\n\n'.join([read_file(*pathes) for pathes in desc_files]) 24 | 25 | install_requires = ['nagiosplugin'] 26 | 27 | extras_require = {'test': ['setuptools', 'mock']} 28 | 29 | 30 | setup(name='checkpkgaudit', 31 | version=version, 32 | description="Check FreeBSD pkg audit Nagios|Icinga|shinken|etc plugin.", 33 | long_description=long_description, 34 | platforms=["any"], 35 | # Get more strings from 36 | # http://pypi.python.org/pypi?%3Aaction=list_classifiers 37 | classifiers=[ 38 | "Programming Language :: Python", 39 | "License :: OSI Approved :: BSD License", 40 | 'Development Status :: 5 - Production/Stable', 41 | 'Environment :: Plugins', 42 | 'Intended Audience :: Developers', 43 | 'Intended Audience :: System Administrators', 44 | 'Operating System :: POSIX :: BSD :: FreeBSD', 45 | 'Programming Language :: Python :: 2.7', 46 | 'Programming Language :: Python :: 3.3', 47 | 'Programming Language :: Python :: 3.4', 48 | 'Programming Language :: Python :: 3.5', 49 | 'Programming Language :: Python :: 3.6', 50 | 'Programming Language :: Python :: 3.7', 51 | 'Programming Language :: Python :: Implementation :: CPython', 52 | 'Programming Language :: Python :: Implementation :: PyPy', 53 | 'Programming Language :: Python :: Implementation :: PyPy3', 54 | 'Topic :: Software Development :: Libraries :: Python Modules', 55 | 'Topic :: System :: Monitoring', 56 | ], 57 | keywords="Nagios Icinga plugin check pkg audit monitoring", 58 | author="Jean-Philippe Camguilhem", 59 | author_email="jpcw__at__camguilhem.net", 60 | url="https://github.com/jpcw/check_pkgaudit", 61 | license="BSD", 62 | packages=find_packages("src"), 63 | package_dir={"": "src"}, 64 | include_package_data=True, 65 | zip_safe=False, 66 | install_requires=install_requires, 67 | test_suite='checkpkgaudit.tests', 68 | extras_require=extras_require, 69 | entry_points=""" 70 | # -*- Entry points: -*- 71 | [console_scripts] 72 | check_pkgaudit = checkpkgaudit:main 73 | """, 74 | ) 75 | 76 | # vim:set et sts=4 ts=4 tw=80: 77 | -------------------------------------------------------------------------------- /src/checkpkgaudit/checkpkgaudit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Check FreeBSD pkg audit plugin. 5 | """ 6 | 7 | import argparse 8 | import logging 9 | import platform 10 | import subprocess 11 | 12 | import nagiosplugin 13 | 14 | __docformat__ = 'restructuredtext en' 15 | 16 | _log = logging.getLogger('nagiosplugin') 17 | 18 | 19 | def _popen(cmd): # pragma: no cover 20 | """Try catched subprocess.popen.""" 21 | try: 22 | proc = subprocess.Popen(cmd, 23 | stdin=subprocess.PIPE, 24 | stdout=subprocess.PIPE, 25 | stderr=subprocess.PIPE) 26 | stdout, stderr = proc.communicate() 27 | return stdout, stderr 28 | 29 | except OSError as e: 30 | message = "%s" % e 31 | raise nagiosplugin.CheckError(message) 32 | 33 | 34 | def _get_jails(): 35 | """Provides running jails.""" 36 | jailargs = [] 37 | jls = subprocess.check_output('jls') 38 | if not isinstance(jls, str): # pragma: no cover 39 | jls = jls.decode() 40 | jails = jls.splitlines()[1:] 41 | if jails: 42 | jailargs = list() 43 | for jail in jails: 44 | host_idx = 1 if len(jail.split()) == 3 else 2 45 | if not jail.split()[host_idx].startswith('hastd:'): 46 | jailargs.append({'jid': jail.split()[0], 47 | 'hostname': jail.split()[host_idx]}) 48 | return jailargs 49 | 50 | 51 | class CheckPkgAudit(nagiosplugin.Resource): 52 | """Check FreeBSD pkg audit plugin.""" 53 | 54 | hostname = platform.node() 55 | 56 | def pkg_audit(self, jail=None): 57 | """Run pkg audit. 58 | 59 | We choose here to raise UNKNOWN status if we encoutered a host|jail 60 | which in pkg audit -F has not been runned. 61 | """ 62 | self.audit_cmd = 'pkg audit' 63 | if jail is not None: 64 | self.audit_cmd = 'pkg -j %s audit' % jail 65 | self.hostname = jail 66 | 67 | _log.debug('querying system with "%s" command', self.audit_cmd) 68 | 69 | stdout, stderr = _popen(self.audit_cmd.split()) 70 | 71 | if not isinstance(stderr, str): # pragma: no cover 72 | stderr = stderr.decode() 73 | if not isinstance(stdout, str): # pragma: no cover 74 | stdout = stdout.decode() 75 | 76 | if stderr: 77 | message = stderr.splitlines()[-1] 78 | 79 | if message.startswith('pkg: vulnxml file'): 80 | # message = "Try running 'pkg audit -F' first" 81 | message = stderr.split('.')[-1] 82 | message = "%s %s" % (self.hostname, message) 83 | _log.info(message) 84 | raise nagiosplugin.CheckError(message) 85 | 86 | else: 87 | stdout = stdout.splitlines()[-1] 88 | problems = int(stdout.split()[0]) 89 | 90 | return problems 91 | 92 | def probe(self): 93 | """Runs pkg audit over host and running jails.""" 94 | 95 | yield nagiosplugin.Metric(self.hostname, self.pkg_audit(), 96 | min=0, context="pkg_audit") 97 | # yield running jails 98 | jails = _get_jails() 99 | if jails: 100 | for jail in jails: 101 | yield nagiosplugin.Metric(jail['hostname'], 102 | self.pkg_audit(jail['jid']), 103 | min=0, context="pkg_audit") 104 | 105 | 106 | class AuditSummary(nagiosplugin.Summary): 107 | """Status line conveying pkg audit informations. 108 | 109 | We specialize the `ok` method to present all figures (hostname and jails) 110 | in one handy tagline. 111 | 112 | In case of UKNOWN raised by "pkg audit -F first": 113 | the single-load text from the context works well. 114 | 115 | In case of problems : we sum pkg problems and list each concerned host. 116 | """ 117 | 118 | def ok(self, results): 119 | """Summarize OK(s).""" 120 | return '0 vulnerabilities found !' 121 | 122 | def problem(self, results): 123 | """Summarize UNKNOWN(s) or CRITICAL(s).""" 124 | 125 | if results.most_significant_state.code == 3: 126 | return results.first_significant.hint 127 | 128 | else: 129 | problems = sum(result.metric.value for result 130 | in results.most_significant) 131 | hosts = ', '.join(sorted((result.metric.name for result 132 | in results.most_significant))) 133 | return 'found %d vulnerable(s) pkg(s) in : %s' % (problems, hosts) 134 | 135 | 136 | def parse_args(): # pragma: no cover 137 | """Arguments parser.""" 138 | argp = argparse.ArgumentParser(description=__doc__) 139 | argp.add_argument('-v', '--verbose', action='count', default=0, 140 | help='increase output verbosity (use up to 3 times)') 141 | 142 | return argp.parse_args() 143 | 144 | 145 | @nagiosplugin.guarded 146 | def main(): # pragma: no cover 147 | """Runs check. 148 | 149 | Critical argument is volontary hardcoded here, one pkg vulnerability 150 | is enough to have a problem, isn't it ? 151 | 152 | Debug me with: check.main(verbose=args.verbose, timeout=0) 153 | default timeout (10s) is inherited from nagiosplugin. 154 | """ 155 | 156 | args = parse_args() 157 | check = nagiosplugin.Check(CheckPkgAudit(), 158 | nagiosplugin.ScalarContext('pkg_audit', None, 159 | '@1:'), 160 | AuditSummary()) 161 | check.main(verbose=args.verbose) 162 | 163 | 164 | if __name__ == '__main__': # pragma: no cover 165 | main() 166 | 167 | # vim:set et sts=4 ts=4 tw=80: 168 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | ========================================================== 3 | Check FreeBSD pkg audit Nagios|Icinga|shinken|etc plugin. 4 | ========================================================== 5 | 6 | .. image:: https://img.shields.io/pypi/l/checkpkgaudit.svg 7 | :target: https://pypi.python.org/pypi/checkpkgaudit/ 8 | 9 | .. image:: https://img.shields.io/pypi/implementation/checkpkgaudit.svg 10 | :target: https://pypi.python.org/pypi/checkpkgaudit/ 11 | 12 | .. image:: https://img.shields.io/pypi/pyversions/checkpkgaudit.svg 13 | :target: https://pypi.python.org/pypi/checkpkgaudit/ 14 | 15 | .. image:: https://img.shields.io/pypi/v/checkpkgaudit.svg 16 | :target: https://pypi.python.org/pypi/checkpkgaudit/ 17 | 18 | .. image:: https://img.shields.io/pypi/status/checkpkgaudit.svg 19 | :target: https://pypi.python.org/pypi/checkpkgaudit/ 20 | 21 | .. image:: https://img.shields.io/coveralls/jpcw/checkpkgaudit.svg 22 | :target: https://coveralls.io/r/jpcw/checkpkgaudit 23 | 24 | .. image:: https://api.travis-ci.org/jpcw/checkpkgaudit.svg?branch=master 25 | :target: http://travis-ci.org/jpcw/checkpkgaudit 26 | 27 | + Source: https://github.com/jpcw/checkpkgaudit 28 | 29 | + Bugtracker: https://github.com/jpcw/checkpkgaudit/issues 30 | 31 | .. contents:: 32 | 33 | usage 34 | ------- 35 | 36 | This check runs pkg audit over your host and its running jails 37 | 38 | sample outputs : 39 | 40 | + Ok 41 | 42 | :: 43 | 44 | CHECKPKGAUDIT OK - 0 vulnerabilities found ! | 'host.domain.tld'=0;;@1:;0 http=0;;@1:;0 masterdns=0;;@1:;0 ns0=0;;@1:;0 ns1=0;;@1:;0 ns2=0;;@1:;0 smtp=0;;@1:;0 45 | 46 | 47 | + Critical 48 | 49 | Critical state is reached with first vulnerable pkg. No warning, no configurable threasold, why waiting 2 or more vulnerabilities ? 50 | 51 | We are talking about security vulnerabilities ! 52 | 53 | Of course, the plugin sum all the vulnerabilities and details each host|jail concerned 54 | 55 | 56 | :: 57 | 58 | CHECKPKGAUDIT CRITICAL - found 2 vulnerable(s) pkg(s) in : ns2, ns3 | 'host.domain.tld'=0;;@1:;0 http=0;;@1:;0 masterdns=0;;@1:;0 ns0=0;;@1:;0 ns1=0;;@1:;0 ns2=1;;@1:;0 ns3=1;;@1:;0 smtp=0;;@1:;0 59 | 60 | Notice that summary returns the total amount problems : 61 | 62 | found **2** vulnerable(s) pkg(s) in : **ns2, ns3** but performance data is detailled by host|jail 63 | 64 | + Unknown 65 | 66 | if an error occured during pkg audit, the plugin raises a check error, which returns an UNKNOWN state. 67 | 68 | typically UNKNOWN causes 69 | 70 | + *pkg audit -F* has not been runned on host or a jail 71 | 72 | :: 73 | 74 | CHECKPKGAUDIT UNKNOWN - jailname Try running 'pkg audit -F' first | 'host.domain.tld'=0;;@1:;0 http=0;;@1:;0 masterdns=0;;@1:;0 ns0=0;;@1:;0 ns1=0;;@1:;0 ns2=0;;@1:;0 smtp=0;;@1:;0 75 | 76 | + *pkg -j jailname audit* runned as a non sudoer user 77 | 78 | :: 79 | 80 | CHECKPKGAUDIT UNKNOWN - jailname pkg: jail_attach(jailname): Operation not permitted | 'host.domain.tld'=0;;@1:;0 81 | 82 | If you have running jails, sudo is your friend to run this plugin with an unprivileged user. A sample config here :: 83 | 84 | icinga ALL = NOPASSWD: /usr/local/bin/check_pkgaudit 85 | 86 | 87 | Install 88 | ------------ 89 | 90 | **checkpkgaudit** can be installed via 91 | either **easy_install** or **pip** . 92 | 93 | Within or not a virtualenv: 94 | 95 | .. code-block:: console 96 | 97 | easy_install checkpkgaudit 98 | # or 99 | pip install checkpkgaudit 100 | 101 | **check_pkgaudit** is located at /usr/local/bin/check_pkgaudit 102 | 103 | .. warning:: SSL certificate error 104 | 105 | If you encountered an ssl certificate error with easy_install, 106 | you probably need to install the Root certificate bundle 107 | from the Mozilla Project: 108 | 109 | .. code-block:: console 110 | 111 | pkg install -y ca_root_nss 112 | ln -s /usr/local/share/certs/ca-root-nss.crt /etc/ssl/cert.pem 113 | 114 | 115 | Nagios|icinga like configuration 116 | ----------------------------------- 117 | 118 | **check_pkgaudit** could be called localy or remotely 119 | via **check_by_ssh** or **NRPE**. 120 | 121 | **check_by_ssh** 122 | 123 | here a sample definition to check remotely by ssh 124 | 125 | Command definition :: 126 | 127 | define command{ 128 | command_name check_ssh_pkgaudit 129 | command_line $USER1$/check_by_ssh -H $HOSTADDRESS$ -i /var/spool/icinga/.ssh/id_rsa -C "sudo /usr/local/bin/check_pkgaudit" 130 | } 131 | 132 | the service itself :: 133 | 134 | define service{ 135 | use my-service 136 | host_name hostname 137 | service_description pkg audit 138 | check_command check_ssh_pkgaudit! 139 | } 140 | 141 | icinga2 command :: 142 | 143 | object CheckCommand "pkgaudit" { 144 | import "plugin-check-command" 145 | import "ipv4-or-ipv6" 146 | command = [ PluginDir + "/check_by_ssh" ] 147 | arguments = { 148 | "-H" = "$address$" 149 | "-i" = "$ssh_id$" 150 | "-p" = "$ssh_port$" 151 | "-C" = "$ssh_command$" 152 | } 153 | vars.address = "$check_address$" 154 | vars.ssh_id = "/var/spool/icinga/.ssh/id_rsa" 155 | vars.ssh_port = "$vars.ssh_port$" 156 | vars.ssh_command = "sudo /usr/local/bin/check_pkgaudit" 157 | } 158 | 159 | icinga2 service :: 160 | 161 | apply Service "pkgaudit" { 162 | check_command = "pkgaudit" 163 | assign where host.name == "hostname" 164 | } 165 | 166 | 167 | **NRPE** 168 | 169 | add this line to /usr/local/etc/nrpe.cfg :: 170 | 171 | ... 172 | command[check_pkgaudit]=/usr/local/bin/check_pkgaudit 173 | ... 174 | 175 | nagios command definition :: 176 | 177 | define command{ 178 | command_name check_nrpe_pkgaudit 179 | command_line $USER1$/check_nrpe -H $HOSTADDRESS$ -c check_pkgaudit 180 | } 181 | 182 | the service itself :: 183 | 184 | define service{ 185 | use my-service 186 | host_name hostname 187 | service_description pkg audit 188 | check_command check_nrpe_pkgaudit 189 | } 190 | 191 | testing 192 | --------- 193 | 194 | .. code-block:: shell 195 | 196 | python bootstrap-buildout.py --setuptools-version=33.1.1 --buildout-version=2.5.2 197 | bin/buildout -N 198 | bin/test 199 | 200 | -------------------------------------------------------------------------------- /bootstrap-buildout.py: -------------------------------------------------------------------------------- 1 | ############################################################################## 2 | # 3 | # Copyright (c) 2006 Zope Foundation and Contributors. 4 | # All Rights Reserved. 5 | # 6 | # This software is subject to the provisions of the Zope Public License, 7 | # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8 | # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9 | # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10 | # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11 | # FOR A PARTICULAR PURPOSE. 12 | # 13 | ############################################################################## 14 | """Bootstrap a buildout-based project 15 | 16 | Simply run this script in a directory containing a buildout.cfg. 17 | The script accepts buildout command-line options, so you can 18 | use the -c option to specify an alternate configuration file. 19 | """ 20 | 21 | import os 22 | import shutil 23 | import sys 24 | import tempfile 25 | 26 | from optparse import OptionParser 27 | 28 | __version__ = '2015-07-01' 29 | # See zc.buildout's changelog if this version is up to date. 30 | 31 | tmpeggs = tempfile.mkdtemp(prefix='bootstrap-') 32 | 33 | usage = '''\ 34 | [DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options] 35 | 36 | Bootstraps a buildout-based project. 37 | 38 | Simply run this script in a directory containing a buildout.cfg, using the 39 | Python that you want bin/buildout to use. 40 | 41 | Note that by using --find-links to point to local resources, you can keep 42 | this script from going over the network. 43 | ''' 44 | 45 | parser = OptionParser(usage=usage) 46 | parser.add_option("--version", 47 | action="store_true", default=False, 48 | help=("Return bootstrap.py version.")) 49 | parser.add_option("-t", "--accept-buildout-test-releases", 50 | dest='accept_buildout_test_releases', 51 | action="store_true", default=False, 52 | help=("Normally, if you do not specify a --version, the " 53 | "bootstrap script and buildout gets the newest " 54 | "*final* versions of zc.buildout and its recipes and " 55 | "extensions for you. If you use this flag, " 56 | "bootstrap and buildout will get the newest releases " 57 | "even if they are alphas or betas.")) 58 | parser.add_option("-c", "--config-file", 59 | help=("Specify the path to the buildout configuration " 60 | "file to be used.")) 61 | parser.add_option("-f", "--find-links", 62 | help=("Specify a URL to search for buildout releases")) 63 | parser.add_option("--allow-site-packages", 64 | action="store_true", default=False, 65 | help=("Let bootstrap.py use existing site packages")) 66 | parser.add_option("--buildout-version", 67 | help="Use a specific zc.buildout version") 68 | parser.add_option("--setuptools-version", 69 | help="Use a specific setuptools version") 70 | parser.add_option("--setuptools-to-dir", 71 | help=("Allow for re-use of existing directory of " 72 | "setuptools versions")) 73 | 74 | options, args = parser.parse_args() 75 | if options.version: 76 | print("bootstrap.py version %s" % __version__) 77 | sys.exit(0) 78 | 79 | 80 | ###################################################################### 81 | # load/install setuptools 82 | 83 | try: 84 | from urllib.request import urlopen 85 | except ImportError: 86 | from urllib2 import urlopen 87 | 88 | ez = {} 89 | if os.path.exists('ez_setup.py'): 90 | exec(open('ez_setup.py').read(), ez) 91 | else: 92 | exec(urlopen('https://bootstrap.pypa.io/ez_setup.py').read(), ez) 93 | 94 | if not options.allow_site_packages: 95 | # ez_setup imports site, which adds site packages 96 | # this will remove them from the path to ensure that incompatible versions 97 | # of setuptools are not in the path 98 | import site 99 | # inside a virtualenv, there is no 'getsitepackages'. 100 | # We can't remove these reliably 101 | if hasattr(site, 'getsitepackages'): 102 | for sitepackage_path in site.getsitepackages(): 103 | # Strip all site-packages directories from sys.path that 104 | # are not sys.prefix; this is because on Windows 105 | # sys.prefix is a site-package directory. 106 | if sitepackage_path != sys.prefix: 107 | sys.path[:] = [x for x in sys.path 108 | if sitepackage_path not in x] 109 | 110 | setup_args = dict(to_dir=tmpeggs, download_delay=0) 111 | 112 | if options.setuptools_version is not None: 113 | setup_args['version'] = options.setuptools_version 114 | if options.setuptools_to_dir is not None: 115 | setup_args['to_dir'] = options.setuptools_to_dir 116 | 117 | ez['use_setuptools'](**setup_args) 118 | import setuptools 119 | import pkg_resources 120 | 121 | # This does not (always?) update the default working set. We will 122 | # do it. 123 | for path in sys.path: 124 | if path not in pkg_resources.working_set.entries: 125 | pkg_resources.working_set.add_entry(path) 126 | 127 | ###################################################################### 128 | # Install buildout 129 | 130 | ws = pkg_resources.working_set 131 | 132 | setuptools_path = ws.find( 133 | pkg_resources.Requirement.parse('setuptools')).location 134 | 135 | # Fix sys.path here as easy_install.pth added before PYTHONPATH 136 | cmd = [sys.executable, '-c', 137 | 'import sys; sys.path[0:0] = [%r]; ' % setuptools_path + 138 | 'from setuptools.command.easy_install import main; main()', 139 | '-mZqNxd', tmpeggs] 140 | 141 | find_links = os.environ.get( 142 | 'bootstrap-testing-find-links', 143 | options.find_links or 144 | ('http://downloads.buildout.org/' 145 | if options.accept_buildout_test_releases else None) 146 | ) 147 | if find_links: 148 | cmd.extend(['-f', find_links]) 149 | 150 | requirement = 'zc.buildout' 151 | version = options.buildout_version 152 | if version is None and not options.accept_buildout_test_releases: 153 | # Figure out the most recent final version of zc.buildout. 154 | import setuptools.package_index 155 | _final_parts = '*final-', '*final' 156 | 157 | def _final_version(parsed_version): 158 | try: 159 | return not parsed_version.is_prerelease 160 | except AttributeError: 161 | # Older setuptools 162 | for part in parsed_version: 163 | if (part[:1] == '*') and (part not in _final_parts): 164 | return False 165 | return True 166 | 167 | index = setuptools.package_index.PackageIndex( 168 | search_path=[setuptools_path]) 169 | if find_links: 170 | index.add_find_links((find_links,)) 171 | req = pkg_resources.Requirement.parse(requirement) 172 | if index.obtain(req) is not None: 173 | best = [] 174 | bestv = None 175 | for dist in index[req.project_name]: 176 | distv = dist.parsed_version 177 | if _final_version(distv): 178 | if bestv is None or distv > bestv: 179 | best = [dist] 180 | bestv = distv 181 | elif distv == bestv: 182 | best.append(dist) 183 | if best: 184 | best.sort() 185 | version = best[-1].version 186 | if version: 187 | requirement = '=='.join((requirement, version)) 188 | cmd.append(requirement) 189 | 190 | import subprocess 191 | if subprocess.call(cmd) != 0: 192 | raise Exception( 193 | "Failed to execute command:\n%s" % repr(cmd)[1:-1]) 194 | 195 | ###################################################################### 196 | # Import and run buildout 197 | 198 | ws.add_entry(tmpeggs) 199 | ws.require(requirement) 200 | import zc.buildout.buildout 201 | 202 | if not [a for a in args if '=' not in a]: 203 | args.append('bootstrap') 204 | 205 | # if -c was provided, we push it back into args for buildout' main function 206 | if options.config_file is not None: 207 | args[0:0] = ['-c', options.config_file] 208 | 209 | zc.buildout.buildout.main(args) 210 | shutil.rmtree(tmpeggs) 211 | -------------------------------------------------------------------------------- /src/checkpkgaudit/tests/test_checkauditpkg.py: -------------------------------------------------------------------------------- 1 | 2 | import mock 3 | 4 | try: 5 | import unittest2 as unittest 6 | except ImportError: # pragma: no cover 7 | import unittest 8 | 9 | import nagiosplugin 10 | from nagiosplugin.metric import Metric 11 | from nagiosplugin import CheckError 12 | 13 | from checkpkgaudit import checkpkgaudit 14 | 15 | no_jails = " JID IP Address Hostname Path" 16 | 17 | jails = (" JID IP Address Hostname Path\n", 18 | " 50 10.0.0.93 masterdns /usr/jails/masterdns\n", 19 | " 51 - hastd: disk1 (primary) /var/empty\n", 20 | " 52 10.0.0.25 smtp /usr/jails/smtp\n", 21 | " 54 10.0.0.53 ns0 /usr/jails/ns0\n", 22 | " 55 10.0.0.153 ns1 /usr/jails/ns1\n", 23 | " 57 10.0.0.80 http /usr/jails/http\n", 24 | " 59 10.0.0.20 supervision /usr/jails/supervision\n", 25 | " 61 formationpy /usr/jails/formationpy\n") 26 | 27 | 28 | class Test__getjails(unittest.TestCase): 29 | 30 | def test__get_jls_no_running_jails(self): 31 | meth = checkpkgaudit._get_jails 32 | mocked = "checkpkgaudit.checkpkgaudit.subprocess" 33 | with mock.patch(mocked) as subprocess: 34 | subprocess.check_output.return_value = no_jails 35 | self.assertEqual(meth(), []) 36 | 37 | def test__get_jls_running_jails(self): 38 | meth = checkpkgaudit._get_jails 39 | mocked = "checkpkgaudit.checkpkgaudit.subprocess" 40 | jls = [{'hostname': 'masterdns', 'jid': '50'}, 41 | {'hostname': 'smtp', 'jid': '52'}, 42 | {'hostname': 'ns0', 'jid': '54'}, 43 | {'hostname': 'ns1', 'jid': '55'}, 44 | {'hostname': 'http', 'jid': '57'}, 45 | {'hostname': 'supervision', 'jid': '59'}, 46 | {'hostname': 'formationpy', 'jid': '61'}] 47 | with mock.patch(mocked) as subprocess: 48 | subprocess.check_output.return_value = ''.join(jails) 49 | self.assertEqual(meth(), jls) 50 | 51 | 52 | class Test_CheckPkgAudit(unittest.TestCase): 53 | 54 | def test_pkg_audit_not_installed(self): 55 | check = checkpkgaudit.CheckPkgAudit() 56 | err_message = ("pkg: vulnxml file /var/db/pkg/vuln.xml does not exist." 57 | " Try running 'pkg audit -F' first") 58 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 59 | _popen.return_value = '', err_message 60 | with self.assertRaises(CheckError): 61 | check.pkg_audit() # NOQA 62 | 63 | def test_pkg_audit_not_installed_in_jail(self): 64 | check = checkpkgaudit.CheckPkgAudit() 65 | err_message = ("pkg: vulnxml file /var/db/pkg/vuln.xml does not exist." 66 | " Try running 'pkg audit -F' first") 67 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 68 | _popen.return_value = '', err_message 69 | with self.assertRaises(CheckError): 70 | check.pkg_audit(jail='supervision') # NOQA 71 | 72 | def test_pkg_audit_not_authorised_inside_jail(self): 73 | check = checkpkgaudit.CheckPkgAudit() 74 | err_message = "pkg: jail_attach(masterdns): Operation not permitted" 75 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 76 | _popen.return_value = '', err_message 77 | with self.assertRaises(CheckError): 78 | check.pkg_audit(jail='masterdns') # NOQA 79 | 80 | def test_pkg_audit_no_problems(self): 81 | check = checkpkgaudit.CheckPkgAudit() 82 | ok_message = "0 problem(s) in the installed packages found." 83 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 84 | _popen.return_value = ok_message, '' 85 | self.assertEqual(check.pkg_audit(), 0) 86 | 87 | def test_pkg_audit_no_problems_inside_jail(self): 88 | check = checkpkgaudit.CheckPkgAudit() 89 | ok_message = "0 problem(s) in the installed packages found." 90 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 91 | _popen.return_value = ok_message, '' 92 | self.assertEqual(check.pkg_audit('masterdns'), 0) 93 | 94 | def test_pkg_audit_with_problems(self): 95 | check = checkpkgaudit.CheckPkgAudit() 96 | pb = ("bind910-9.10.1P1_1 is vulnerable:\n", 97 | "bind -- denial of service vulnerability\n", 98 | "CVE: CVE-2015-1349\n", 99 | "WWW: http://vuxml.FreeBSD.org/freebsd/58033a95", 100 | "-bba8-11e4-88ae-d050992ecde8.html\n", 101 | "\n", 102 | "1 problem(s) in the installed packages found.\n") 103 | 104 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 105 | _popen.return_value = ''.join(pb), '' 106 | self.assertEqual(check.pkg_audit(), 1) 107 | 108 | def test_pkg_audit_with_problems_inside_jail(self): 109 | check = checkpkgaudit.CheckPkgAudit() 110 | pb = ("bind910-9.10.1P1_1 is vulnerable:\n", 111 | "bind -- denial of service vulnerability\n", 112 | "CVE: CVE-2015-1349\n", 113 | "WWW: http://vuxml.FreeBSD.org/freebsd/58033a95", 114 | "-bba8-11e4-88ae-d050992ecde8.html\n", 115 | "\n", 116 | "1 problem(s) in the installed packages found.\n") 117 | 118 | with mock.patch("checkpkgaudit.checkpkgaudit._popen") as _popen: 119 | _popen.return_value = ''.join(pb), '' 120 | self.assertEqual(check.pkg_audit(), 1) 121 | 122 | def test_probe_host(self): 123 | check = checkpkgaudit.CheckPkgAudit() 124 | check.hostname = 'hostname.domain.tld' 125 | mocked = "checkpkgaudit.checkpkgaudit._get_jails" 126 | with mock.patch(mocked) as _get_jails: 127 | _get_jails.return_value = [] 128 | 129 | mocked = "checkpkgaudit.checkpkgaudit.CheckPkgAudit.pkg_audit" 130 | with mock.patch(mocked) as pkg_audit: 131 | pkg_audit.return_value = 0 132 | 133 | probe = check.probe() 134 | host = next(probe) 135 | self.assertEqual(type(host), Metric) 136 | self.assertEqual(host.name, 'hostname.domain.tld') 137 | self.assertEqual(host.value, 0) 138 | 139 | def test_probe_host_with_jails(self): 140 | check = checkpkgaudit.CheckPkgAudit() 141 | check.hostname = 'hostname.domain.tld' 142 | mocked = "checkpkgaudit.checkpkgaudit._get_jails" 143 | with mock.patch(mocked) as _get_jails: 144 | _get_jails.return_value = [{'hostname': 'masterdns', 'jid': '50'}] 145 | 146 | mocked = "checkpkgaudit.checkpkgaudit.CheckPkgAudit.pkg_audit" 147 | with mock.patch(mocked) as pkg_audit: 148 | pkg_audit.return_value = 0 149 | probe = check.probe() 150 | host = next(probe) 151 | self.assertIsNotNone(host) 152 | jail = next(probe) 153 | self.assertIsNotNone(jail) 154 | 155 | 156 | class Test_AuditSummary(unittest.TestCase): 157 | 158 | def test_ok(self): 159 | from nagiosplugin.result import Result, Results 160 | from nagiosplugin.state import Ok 161 | from checkpkgaudit.checkpkgaudit import AuditSummary 162 | results = Results() 163 | ok_r1 = Result(Ok, '', nagiosplugin.Metric('met1', 0)) 164 | ok_r2 = Result(Ok, '', nagiosplugin.Metric('met1', 0)) 165 | results.add(ok_r1) 166 | results.add(ok_r2) 167 | summary = AuditSummary() 168 | sum_ok = summary.ok(results) 169 | self.assertEqual(sum_ok, '0 vulnerabilities found !') 170 | 171 | def test_problem_unknown(self): 172 | from nagiosplugin.result import Result, Results 173 | from nagiosplugin.state import Critical, Unknown 174 | from checkpkgaudit.checkpkgaudit import AuditSummary 175 | hint = 'masterdns pkg: jail_attach(masterdns): Operation not permitted' 176 | results = Results() 177 | r1 = Result(Critical, '', nagiosplugin.Metric('met1', 1)) 178 | r2 = Result(Unknown, hint, nagiosplugin.Metric('met1', 0)) 179 | results.add(r1) 180 | results.add(r2) 181 | summary = AuditSummary() 182 | sum_unknown = summary.problem(results) 183 | self.assertEqual(sum_unknown, hint) 184 | 185 | def test_problem_crit(self): 186 | from nagiosplugin.result import Result, Results 187 | from nagiosplugin.state import Critical 188 | from checkpkgaudit.checkpkgaudit import AuditSummary 189 | message = "found 2 vulnerable(s) pkg(s) in : ns1, ns2" 190 | results = Results() 191 | r1 = Result(Critical, '', nagiosplugin.Metric('ns1', 1)) 192 | r2 = Result(Critical, '', nagiosplugin.Metric('ns2', 1)) 193 | results.add(r1) 194 | results.add(r2) 195 | summary = AuditSummary() 196 | sum_crit = summary.problem(results) 197 | self.assertEqual(sum_crit, message) 198 | --------------------------------------------------------------------------------