Update to 0.79

This commit is contained in:
Thorsten Spille 2022-02-20 13:25:19 +01:00 committed by GitHub
parent a3a61e68ed
commit 78d92e8c7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -22,11 +22,12 @@
## copy to /usr/local/etc/rc.syshook.d/start/99-checkmk_agent and chmod +x ## copy to /usr/local/etc/rc.syshook.d/start/99-checkmk_agent and chmod +x
## ##
__VERSION__ = "0.73" __VERSION__ = "0.79"
import sys import sys
import os import os
import shlex import shlex
import glob
import re import re
import time import time
import json import json
@ -41,10 +42,15 @@ import base64
import traceback import traceback
from cryptography import x509 from cryptography import x509
from cryptography.hazmat.backends import default_backend as crypto_default_backend from cryptography.hazmat.backends import default_backend as crypto_default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from xml.etree import cElementTree as ELementTree from xml.etree import cElementTree as ELementTree
from collections import Counter,defaultdict from collections import Counter,defaultdict
from pprint import pprint from pprint import pprint
from socketserver import TCPServer,StreamRequestHandler from socketserver import TCPServer,StreamRequestHandler
BASEDIR = os.path.dirname(os.path.abspath(os.path.basename(__file__)))
LOCALDIR = os.path.join(BASEDIR,"local")
class object_dict(defaultdict): class object_dict(defaultdict):
def __getattr__(self,name): def __getattr__(self,name):
@ -70,45 +76,119 @@ def etree_to_dict(t):
d[t.tag] = text d[t.tag] = text
return d return d
def pad_pkcs7(message,size=16):
_pad = size - (len(message) % size)
if type(message) == str:
return message + chr(_pad) * _pad
else:
return message + bytes([_pad]) * _pad
class checkmk_handler(StreamRequestHandler): class checkmk_handler(StreamRequestHandler):
def handle(self): def handle(self):
with self.server._mutex: with self.server._mutex:
try: try:
_strmsg = self.server.do_checks() _strmsg = self.server.do_checks(remote_ip=self.client_address[0])
except Exception as e: except Exception as e:
_strmsg = str(e) raise
with self.wfile as _f: _strmsg = str(e).encode("utf-8")
_f.write(_strmsg.encode("utf-8")) try:
self.wfile.write(_strmsg)
except:
raise
pass
class checkmk_checker(object): class checkmk_checker(object):
_certificate_timestamp = 0 _certificate_timestamp = 0
_check_cache = {}
_datastore_mutex = threading.RLock() _datastore_mutex = threading.RLock()
_datastore = object_dict() _datastore = object_dict()
def do_checks(self,debug=False):
def encrypt(self,message,password='secretpassword'):
SALT_LENGTH = 8
KEY_LENGTH = 32
IV_LENGTH = 16
PBKDF2_CYCLES = 10_000
SALT = b"Salted__"
_backend = crypto_default_backend()
_kdf_key = PBKDF2HMAC(
algorithm = hashes.SHA256,
length = KEY_LENGTH + IV_LENGTH,
salt = SALT,
iterations = PBKDF2_CYCLES,
backend = _backend
).derive(password.encode("utf-8"))
_key, _iv = _kdf_key[:KEY_LENGTH],_kdf_key[KEY_LENGTH:]
_encryptor = Cipher(
algorithms.AES(_key),
modes.CBC(_iv),
backend = _backend
).encryptor()
message = pad_pkcs7(message)
message = message.encode("utf-8")
_encrypted_message = _encryptor.update(message) + _encryptor.finalize()
return pad_pkcs7(b"03",10) + SALT + _encrypted_message
def _encrypt(self,message):
_cmd = shlex.split('openssl enc -aes-256-cbc -md sha256 -iter 10000 -k "secretpassword"',posix=True)
_proc = subprocess.Popen(_cmd,stderr=subprocess.DEVNULL,stdout=subprocess.PIPE,stdin=subprocess.PIPE)
_out,_err = _proc.communicate(input=message.encode("utf-8"))
return b"03" + _out
def do_checks(self,debug=False,remote_ip=None,**kwargs):
self._getosinfo() self._getosinfo()
_errors = [] _errors = []
_failed_sections = []
_lines = ["<<<check_mk>>>"] _lines = ["<<<check_mk>>>"]
_lines.append("AgentOS: {os}".format(**self._info)) _lines.append("AgentOS: {os}".format(**self._info))
_lines.append(f"Version: {__VERSION__}") _lines.append(f"Version: {__VERSION__}")
_lines.append("Hostname: {hostname}".format(**self._info)) _lines.append("Hostname: {hostname}".format(**self._info))
if self.onlyfrom:
_lines.append("OnlyFrom: {0}".format(",".join(self.onlyfrom)))
_lines.append(f"LocalDirectory: {LOCALDIR}")
for _check in dir(self): for _check in dir(self):
if _check.startswith("check_"): if _check.startswith("check_"):
_name = _check.split("_",1)[1]
try: try:
_lines += getattr(self,_check)() _lines += getattr(self,_check)()
except: except:
_failed_sections.append(_name)
_errors.append(traceback.format_exc()) _errors.append(traceback.format_exc())
_lines.append("<<<local:sep(0)>>>") _lines.append("<<<local:sep(0)>>>")
for _check in dir(self): for _check in dir(self):
if _check.startswith("checklocal_"): if _check.startswith("checklocal_"):
_name = _check.split("_",1)[1]
try: try:
_lines += getattr(self,_check)() _lines += getattr(self,_check)()
except: except:
_failed_sections.append(_name)
_errors.append(traceback.format_exc()) _errors.append(traceback.format_exc())
if os.path.isdir(LOCALDIR):
for _local_file in glob.glob(f"{LOCALDIR}/**",recursive=True):
if os.path.isfile(_local_file) and os.access(_local_file,os.X_OK):
try:
_cachetime = int(_local_file.split(os.path.sep)[-2])
except:
_cachetime = 0
try:
_lines.append(self._run_cache_prog(_local_file,_cachetime))
except:
_errors.append(traceback.format_exc())
_lines.append("") _lines.append("")
if debug: if debug:
sys.stderr.write("\n".join(_errors)) sys.stderr.write("\n".join(_errors))
sys.stderr.flush() sys.stderr.flush()
return "\n".join(_lines) if _failed_sections:
_lines.append("<<<check_mk>>>")
_lines.append("FailedPythonPlugins: {0}".format(",".join(_failed_sections)))
if self.encryptionkey:
return self.encrypt("\n".join(_lines),password=self.encryptionkey)
return "\n".join(_lines).encode("utf-8")
def _get_storedata(self,section,key): def _get_storedata(self,section,key):
with self._datastore_mutex: with self._datastore_mutex:
@ -203,7 +283,7 @@ class checkmk_checker(object):
def get_opnsense_ipaddr(self): def get_opnsense_ipaddr(self):
try: try:
_ret = {} _ret = {}
for _if,_ip,_mask in re.findall("^([\w_]+):\sflags=(?:8943|8051|8043).*?inet\s([\d.]+)\snetmask\s0x([a-f0-9]+)",subprocess.check_output("ifconfig",encoding="utf-8"),re.DOTALL | re.M): for _if,_ip,_mask in re.findall("^([\w_]+):\sflags=(?:8943|8051|8043).*?inet\s([\d.]+)\snetmask\s0x([a-f0-9]+)",self._run_prog("ifconfig"),re.DOTALL | re.M):
_ret[_if] = "{0}/{1}".format(_ip,str(bin(int(_mask,16))).count("1")) _ret[_if] = "{0}/{1}".format(_ip,str(bin(int(_mask,16))).count("1"))
return _ret return _ret
except: except:
@ -248,7 +328,7 @@ class checkmk_checker(object):
_interface_status = dict( _interface_status = dict(
map(lambda x: (x[0],(x[1:])), map(lambda x: (x[0],(x[1:])),
re.findall("^(?P<iface>[\w.]+):.*?(?P<adminstate>UP|DOWN),.*?\n(?:\s+(?:media:.*?(?P<speed>\d+G?).*?\<(?P<duplex>.*?)\>|(?:status:\s(?P<operstate>[ \w]+))|).*?\n)*", re.findall("^(?P<iface>[\w.]+):.*?(?P<adminstate>UP|DOWN),.*?\n(?:\s+(?:media:.*?(?P<speed>\d+G?).*?\<(?P<duplex>.*?)\>|(?:status:\s(?P<operstate>[ \w]+))|).*?\n)*",
subprocess.check_output("ifconfig",encoding="utf-8"),re.M) self._run_prog("ifconfig"),re.M)
) )
) )
_interface_data = self._run_prog("/usr/bin/netstat -i -b -d -n -W -f link").split("\n") _interface_data = self._run_prog("/usr/bin/netstat -i -b -d -n -W -f link").split("\n")
@ -290,7 +370,7 @@ class checkmk_checker(object):
def checklocal_services(self): def checklocal_services(self):
_phpcode = '<?php require_once("config.inc");require_once("system.inc"); require_once("plugins.inc"); require_once("util.inc"); foreach(plugins_services() as $_service) { printf("%s;%s;%s\n",$_service["name"],$_service["description"],service_status($_service));} ?>' _phpcode = '<?php require_once("config.inc");require_once("system.inc"); require_once("plugins.inc"); require_once("util.inc"); foreach(plugins_services() as $_service) { printf("%s;%s;%s\n",$_service["name"],$_service["description"],service_status($_service));} ?>'
_proc = subprocess.Popen(["php"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,encoding="utf-8") _proc = subprocess.Popen(["php"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,encoding="utf-8")
_data,_ = _proc.communicate(input=_phpcode) _data,_ = _proc.communicate(input=_phpcode,timeout=15)
_services = [] _services = []
for _service in _data.strip().split("\n"): for _service in _data.strip().split("\n"):
_services.append(_service.split(";")) _services.append(_service.split(";"))
@ -561,7 +641,7 @@ class checkmk_checker(object):
def checklocal_ipsec(self): def checklocal_ipsec(self):
_ret = [] _ret = []
_json_data = subprocess.check_output("/usr/local/opnsense/scripts/ipsec/list_status.py",encoding="utf-8") _json_data = self._run_prog("/usr/local/opnsense/scripts/ipsec/list_status.py")
if len(_json_data.strip()) < 20: if len(_json_data.strip()) < 20:
return [] return []
for _con in json.loads(_json_data).values(): for _con in json.loads(_json_data).values():
@ -605,7 +685,7 @@ class checkmk_checker(object):
_client["bytes_sent"] = 0 _client["bytes_sent"] = 0
_client["status"] = 2 _client["status"] = 2
_dump = subprocess.check_output(["wg","show","all","dump"],encoding="utf-8").strip() _dump = self._run_prog(["wg","show","all","dump"]).strip()
for _line in _dump.split("\n"): for _line in _dump.split("\n"):
_values = _line.split("\t") _values = _line.split("\t")
if len(_values) != 9: if len(_values) != 9:
@ -629,7 +709,7 @@ class checkmk_checker(object):
def checklocal_unbound(self): def checklocal_unbound(self):
_ret = [] _ret = []
try: try:
_output = subprocess.check_output(["/usr/local/sbin/unbound-control", "-c", "/var/unbound/unbound.conf", "stats_noreset"],encoding="utf-8",stderr=subprocess.DEVNULL) _output = self._run_prog(["/usr/local/sbin/unbound-control", "-c", "/var/unbound/unbound.conf", "stats_noreset"])
_unbound_stat = dict( _unbound_stat = dict(
map( map(
lambda x: (x[0].replace(".","_"),float(x[1])), lambda x: (x[0].replace(".","_"),float(x[1])),
@ -732,7 +812,7 @@ class checkmk_checker(object):
def check_kernel(self): def check_kernel(self):
_ret = ["<<<kernel>>>"] _ret = ["<<<kernel>>>"]
_out = self._run_prog("sysctl -a") _out = self._run_prog("sysctl -a",timeout=10)
_kernel = dict([_v.split(": ") for _v in _out.split("\n") if len(_v.split(": ")) == 2]) _kernel = dict([_v.split(": ") for _v in _out.split("\n") if len(_v.split(": ")) == 2])
_ret.append("{0:.0f}".format(time.time())) _ret.append("{0:.0f}".format(time.time()))
_ret.append("cpu {0} {1} {2} {4} {3}".format(*(_kernel.get("kern.cp_time","").split(" ")))) _ret.append("cpu {0} {1} {2} {4} {3}".format(*(_kernel.get("kern.cp_time","").split(" "))))
@ -784,7 +864,7 @@ class checkmk_checker(object):
def check_ntp(self): def check_ntp(self):
_ret = ["<<<ntp>>>"] _ret = ["<<<ntp>>>"]
for _line in self._run_prog("ntpq -np").split("\n")[2:]: for _line in self._run_prog("ntpq -np",timeout=30).split("\n")[2:]:
if _line.strip(): if _line.strip():
_ret.append("{0} {1}".format(_line[0],_line[1:])) _ret.append("{0} {1}".format(_line[0],_line[1:]))
return _ret return _ret
@ -813,18 +893,82 @@ class checkmk_checker(object):
_ret.append(f"{_uptime_sec} {_idle_sec}") _ret.append(f"{_uptime_sec} {_idle_sec}")
return _ret return _ret
def _run_prog(self,cmdline="",*args,shell=False): def _run_prog(self,cmdline="",*args,shell=False,timeout=60):
if cmdline: if type(cmdline) == str:
args = list(args) + shlex.split(cmdline,posix=True) _process = shlex.split(cmdline,posix=True)
else:
_process = cmdline
try: try:
return subprocess.check_output(args,encoding="utf-8",shell=shell,stderr=subprocess.DEVNULL) return subprocess.check_output(_process,encoding="utf-8",shell=shell,stderr=subprocess.DEVNULL,timeout=timeout)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
return "" return ""
except subprocess.TimeoutExpired:
return ""
def _run_cache_prog(self,cmdline="",cachetime=10,*args,shell=False):
if type(cmdline) == str:
_process = shlex.split(cmdline,posix=True)
else:
_process = cmdline
_process_id = "".join(_process)
_runner = self._check_cache.get(_process_id)
if _runner == None:
_runner = checkmk_cached_process(_process,shell=shell)
self._check_cache[_process_id] = _runner
return _runner.get(cachetime)
class checkmk_cached_process(object):
def __init__(self,process,shell=False):
self._processs = process
self._islocal = os.path.dirname(process[0]).startswith(LOCALDIR)
self._shell = shell
self._mutex = threading.Lock()
with self._mutex:
self._data = (0,"")
self._thread = None
def _runner(self,timeout):
try:
_data = subprocess.check_output(self._processs,shell=self._shell,encoding="utf-8",stderr=subprocess.DEVNULL,timeout=timeout)
except subprocess.CalledProcessError as e:
_data = ""
except subprocess.TimeoutExpired:
_data = ""
with self._mutex:
self._data = (int(time.time()),_data)
self._thread = None
def get(self,cachetime):
with self._mutex:
_now = time.time()
_mtime = self._data[0]
if _now - _mtime > cachetime or cachetime == 0:
if not self._thread:
if cachetime > 0:
_timeout = cachetime*2-1
else:
_timeout = None
with self._mutex:
self._thread = threading.Thread(target=self._runner,args=[_timeout])
self._thread.start()
self._thread.join(30) ## waitmax
with self._mutex:
_mtime, _data = self._data
if not _data.strip():
return ""
if self._islocal:
_data = "".join([f"cached({_mtime},{cachetime}) {_line}" for _line in _data.splitlines(True) if len(_line.strip()) > 0])
else:
_data = re.sub("\B[<]{3}(.*?)[>]{3}\B",f"<<<\\1:cached({_mtime},{cachetime})>>>",_data)
return _data
class checkmk_server(TCPServer,checkmk_checker): class checkmk_server(TCPServer,checkmk_checker):
def __init__(self,port,pidfile,user,**kwargs): def __init__(self,port,pidfile,user,onlyfrom=None,encryptionkey=None,**kwargs):
self.pidfile = pidfile self.pidfile = pidfile
self.onlyfrom=onlyfrom.split(",") if onlyfrom else None
self.encryptionkey = encryptionkey
self._mutex = threading.Lock() self._mutex = threading.Lock()
self.user = pwd.getpwnam(user) self.user = pwd.getpwnam(user)
self.allow_reuse_address = True self.allow_reuse_address = True
@ -836,6 +980,11 @@ class checkmk_server(TCPServer,checkmk_checker):
os.setgid(_gid) os.setgid(_gid)
os.setuid(_uid) os.setuid(_uid)
def verify_request(self, request, client_address):
if self.onlyfrom and client_address[0] not in self.onlyfrom:
return False
return True
def server_start(self): def server_start(self):
sys.stderr.write("starting checkmk_agent\n") sys.stderr.write("starting checkmk_agent\n")
sys.stderr.flush() sys.stderr.flush()
@ -979,7 +1128,7 @@ class smart_disc(object):
def _get_data(self): def _get_data(self):
try: try:
self._smartctl_output = subprocess.check_output(["smartctl","-a","-n","standby", f"/dev/{self.device}"],encoding=sys.stdout.encoding) self._smartctl_output = subprocess.check_output(["smartctl","-a","-n","standby", f"/dev/{self.device}"],encoding=sys.stdout.encoding,timeout=10)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if e.returncode & 0x1: if e.returncode & 0x1:
raise raise
@ -993,6 +1142,9 @@ class smart_disc(object):
_status = "SMART Health Status: DISK FAILING" _status = "SMART Health Status: DISK FAILING"
self._smartctl_output += f"\n{_status}\n" self._smartctl_output += f"\n{_status}\n"
except subprocess.TimeoutExpired:
self._smartctl_output += "\nSMART smartctl Timeout\n"
def __str__(self): def __str__(self):
_ret = [] _ret = []
@ -1011,17 +1163,21 @@ if __name__ == "__main__":
_parser.add_argument("--port",type=int,default=6556, _parser.add_argument("--port",type=int,default=6556,
help=_("Port checkmk_agent listen")) help=_("Port checkmk_agent listen"))
_parser.add_argument("--start",action="store_true", _parser.add_argument("--start",action="store_true",
help=_("")) help=_("start the daemon"))
_parser.add_argument("--stop",action="store_true", _parser.add_argument("--stop",action="store_true",
help=_("")) help=_("stop the daemon"))
_parser.add_argument("--nodaemon",action="store_true", _parser.add_argument("--nodaemon",action="store_true",
help=_("")) help=_("run in foreground"))
_parser.add_argument("--status",action="store_true", _parser.add_argument("--status",action="store_true",
help=_("")) help=_("show status if running"))
_parser.add_argument("--user",type=str,default="root", _parser.add_argument("--user",type=str,default="root",
help=_("")) help=_(""))
_parser.add_argument("--encrypt",type=str,dest="encryptionkey",
help=_("Encryption password (do not use from cmdline)"))
_parser.add_argument("--pidfile",type=str,default="/var/run/checkmk_agent.pid", _parser.add_argument("--pidfile",type=str,default="/var/run/checkmk_agent.pid",
help=_("")) help=_(""))
_parser.add_argument("--onlyfrom",type=str,
help=_("comma seperated ip addresses to allow"))
_parser.add_argument("--debug",action="store_true", _parser.add_argument("--debug",action="store_true",
help=_("debug Ausgabe")) help=_("debug Ausgabe"))
args = _parser.parse_args() args = _parser.parse_args()