diff --git a/pandora_agents/unix/Linux/pandora_agent.conf b/pandora_agents/unix/Linux/pandora_agent.conf index 34f9d8e02b..52ca10f17b 100644 --- a/pandora_agents/unix/Linux/pandora_agent.conf +++ b/pandora_agents/unix/Linux/pandora_agent.conf @@ -254,6 +254,9 @@ module_plugin pandora_mem_used module_plugin pandora_netusage +# Service autodiscovery plugin +module_plugin autodiscover --default + # Plugin for inventory on the agent (Only Enterprise) #module_plugin inventory 1 cpu ram video nic hd cdrom software init_services filesystem users route diff --git a/pandora_agents/unix/plugins/autodiscover b/pandora_agents/unix/plugins/autodiscover new file mode 100644 index 0000000000..cbe4de677e Binary files /dev/null and b/pandora_agents/unix/plugins/autodiscover differ diff --git a/pandora_agents/win32/bin/pandora_agent.conf b/pandora_agents/win32/bin/pandora_agent.conf index ba1e0b6444..b0f3c97c61 100644 --- a/pandora_agents/win32/bin/pandora_agent.conf +++ b/pandora_agents/win32/bin/pandora_agent.conf @@ -1,6 +1,6 @@ # Base config file for Pandora FMS Windows Agent # (c) 2006-2017 Artica Soluciones Tecnologicas -# Version 7.0NG.744 +# Version 7.0NG.744 # This program is Free Software, you can redistribute it and/or modify it # under the terms of the GNU General Public Licence as published by the Free Software @@ -245,6 +245,10 @@ module_plugin cscript.exe //B "%ProgramFiles%\Pandora_Agent\util\network.vbs" #module_crontab * 12-15 * * 1 #module_end +# Service autodiscovery plugin +module_plugin "%PROGRAMFILES%\Pandora_Agent\util\autodiscover.exe" --default + + ######################################### # EXAMPLES # ######################################### diff --git a/pandora_agents/win32/bin/util/autodiscover.exe b/pandora_agents/win32/bin/util/autodiscover.exe new file mode 100644 index 0000000000..41b7bf3629 Binary files /dev/null and b/pandora_agents/win32/bin/util/autodiscover.exe differ diff --git a/pandora_plugins/Autodiscover services/autodiscover b/pandora_plugins/Autodiscover services/autodiscover new file mode 100644 index 0000000000..cbe4de677e Binary files /dev/null and b/pandora_plugins/Autodiscover services/autodiscover differ diff --git a/pandora_plugins/Autodiscover services/autodiscover.exe b/pandora_plugins/Autodiscover services/autodiscover.exe new file mode 100644 index 0000000000..41b7bf3629 Binary files /dev/null and b/pandora_plugins/Autodiscover services/autodiscover.exe differ diff --git a/pandora_plugins/Autodiscover services/autodiscover.py b/pandora_plugins/Autodiscover services/autodiscover.py new file mode 100644 index 0000000000..78a70cddba --- /dev/null +++ b/pandora_plugins/Autodiscover services/autodiscover.py @@ -0,0 +1,398 @@ +#!/usr/bin/env python3 +################################################### +# +# Pandora FMS Autodiscovery plugin. +# Checks the status of the services in list and monitors CPU and Memory for each of them. +# +# (c) A. Kevin Rojas +# +# TO DO LIST: +# - Enable child services detection (Windows) +# - Make CPU/Memory usage available for child services (Windows) +# +################################################### + +from sys import argv, path, stderr, exit +import psutil +from subprocess import * + +global module_list +module_list = [] + + +######################################################################################### +# Powershell class +######################################################################################### +class PSCheck: + @staticmethod + def check_service(servicename, option=False, memcpu=False): + """Check services with powershell by parsing their DisplayName. Returns a dict\ + list with the name of the service and a boolean with its status.\n + Requires service name (case insensitive).""" + pscall = Popen(["powershell", "Get-Service", "-Name", "'*"+ str(servicename) + "*'", + "|", "Select-Object", "-ExpandProperty", "Name"], + stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True) + result = pscall.communicate() + result = str(result[0]).strip().split("\n") + procname = '' + if result != '': + output = [] + for element in result: + if element != '': + # Get process name + procname = PSCheck.get_serviceprocess(element) + # Get process status + parstatus = PSCheck.getstatus(element) + if memcpu == True and parstatus == 1: + usage = get_memcpu(str(procname), str(element)) + output += usage + # Generate module with name and status + parent = service_module(str(element), parstatus) + output += parent + if option == True: + children = PSCheck.getchildren(element, memcpu) + if type(children) == list and len(children) > 1: + for child in children: + output += child + else: + output += children + else: + next + + #if output != '': + if output and element and procname: + return ({"name" : element, "process" : procname, "modules": output}) + else: + return (None) + + @staticmethod + def getchildren(servicename, memcpu=False): + """Gets Dependent services of a given Windows service""" + pschild = Popen(["powershell", "Get-Service", "-Name '" + str(servicename) + + "' -DS", "|", "Select-Object", "-ExpandProperty", "Name"], + stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True) + children = pschild.communicate()[0].strip() + kids = [] + for child in (children.split("\n") if children != "" else []): + status = PSCheck.getstatus(child) + kids += service_module(str(child), status, "Service " + str(servicename) + " - Status") + if status: + if memcpu == True: + kidsusage = get_memcpu(str(child)) + for usage in kidsusage: + kids += usage + else: + next + return (kids) + + @staticmethod + def getstatus(servicename): + """Gets the status of a given Windows service""" + running = Popen(["powershell", "Get-Service", "-Name '" + str(servicename) + + "' |", "Select-Object", "-ExpandProperty", "Status"], + stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True) + status = running.communicate()[0].strip() + return (int(status == "Running")) + + @staticmethod + def get_serviceprocess(servicename): + """Gets name of the process of the service""" + service = psutil.win_service_get(servicename) + srv_pid = service.pid() + process = psutil.Process(srv_pid) + proc_name = process.name() + return (proc_name) + + +######################################################################################### +# Services creation +######################################################################################### + +def service_module(name, value, parent=None): + #print ("service_module BEGIN "+str(now(0,1))) + module = [{ + "name" : "Service "+ name + " - Status", + "type" : "generic_proc", + "value" : value, + "module_parent" : parent, + }] + #print ("service_module END "+str(now(0,1))) + return (module) + +def get_memcpu (process, servicename): + """Creates a module for Memory and CPU for a given process. Returns a list of dictionaries.""" + modules = [] + if process: + if servicename != None: + parentname = servicename + else: + parentname = process + modules += [{ + "name" : "Service "+ process + " - Memory usage", + "type" : "generic_data", + "value" : proc_percentbyname(process)[0], + "unit" : "%", + "module_parent" : "Service "+ parentname + " - Status", + }, + {"name" : "Service "+ process + " - CPU usage", + "type" : "generic_data", + "value" : proc_percentbyname(process)[1], + "unit" : "%", + "module_parent" : "Service "+ parentname + " - Status", + }] + return (modules) + +def proc_percentbyname(procname): ############# 03/03/2020 + """Gets Memory and CPU usage for a given process. Returns a list.""" + #print ("proc_percentbyname BEGIN "+str(now(0,1))) + procs = [p for p in psutil.process_iter() if procname in p.name().lower()] + memory = [] + cpu = [] + try: + for proc in procs: + if proc.name() == procname: + cpu.append(proc.cpu_percent(interval=0.5)) + memory.append(proc.memory_percent()) + else: + next + except psutil.NoSuchProcess: + next + #print ("proc_percentbyname END "+str(now(0,1))) + return ([sum(memory),sum(cpu)]) + +def win_service(servicelist, option=False, memcpu=False): + """Creates modules for Windows servers.""" + modules = [] + for srvc in servicelist: + if srvc and len(srvc) > 2: + output = PSCheck.check_service(srvc, option, memcpu) + if output != None and output["modules"]: + modules += PSCheck.check_service(srvc.strip(), option, memcpu)["modules"] + module_list.append(srvc) + winprocess = output["name"] + #if memcpu == True: + # modules += get_memcpu(winprocess) ## Only available for parent service ATM. + else: + next + else: + next + for module in modules: + print_module(module, 1) + + +def lnx_service(services_list, memcpu=False): + """Creates modules for Linux servers""" + modules = [] + sysctl = getstatusoutput("command -v systemctl")[0] + servic = getstatusoutput("command -v service")[0] + for srvc in services_list: + status = None + if sysctl == 0: + ### Systemd available + syscall = Popen(["systemctl", "is-active", srvc], stdout=PIPE, + stdin=DEVNULL, universal_newlines=True) + result = syscall.communicate() + result = result[0].strip().lower() + if result == "active": + modules += service_module(srvc, 1) + status = 1 + elif result == "inactive": + modules += service_module(srvc, 0) + status = 0 + elif result == "unknown": + next + elif sysctl != 0 and servic == 0: + ### Systemd not available, switch to service command + syscall = Popen(["service", srvc, "status"], stdout=PIPE, + stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True) + result = syscall.communicate()[0].lower() + if "is running" in result: + modules += service_module(srvc, 1) + status = 1 + elif "is stopped" in result: + modules += service_module(srvc, 0) + status = 0 + else: + next + else: + print ("No systemd or service commands available. Exiting...", file=stderr) + exit() + if status: + module_list.append(srvc) + if memcpu == True: + modules += get_memcpu(srvc, None) + + for m in modules: + print_module (m, 1) + + +######################################################################################### +# print_module function +######################################################################################### +def print_module(module, str_flag=False): + """Returns module in XML format. Accepts only {dict}.\n + + Only works with one module at a time: otherwise iteration is needed. + + Module "value" field accepts str type or [list] for datalists. + + Use not_print_flag to avoid printing the XML (only populates variables). + """ + data = dict(module) + module_xml = ("\n" + "\t\n" + "\t" + str(data["type"]) + "\n" + ) + #### Strip spaces if module not generic_data_string + if type(data["type"]) is not str and "string" not in data["type"]: + data["value"] = data["value"].strip() + if isinstance(data["value"], list): # Checks if value is a list + module_xml += "\t\n" + for value in data["value"]: + if type(value) is dict and "value" in value: + module_xml += "\t\n" + module_xml += "\t\t\n" + if "timestamp" in value: + module_xml += "\t\t\n" + module_xml += "\t\n" + else: + module_xml += "\t\n" + if "desc" in data: + module_xml += "\t\n" + if "unit" in data: + module_xml += "\t\n" + if "interval" in data: + module_xml += "\t\n" + if "tags" in data: + module_xml += "\t" + str(data["tags"]) + "\n" + if "module_group" in data: + module_xml += "\t" + str(data["module_group"]) + "\n" + if "module_parent" in data and data["module_parent"] != None: + module_xml += "\t" + str(data["module_parent"]) + "\n" + if "min_warning" in data: + module_xml += "\t\n" + if "max_warning" in data: + module_xml += "\t\n" + if "min_critical" in data: + module_xml += "\t\n" + if "max_critical" in data: + module_xml += "\t\n" + if "str_warning" in data: + module_xml += "\t\n" + if "str_critical" in data: + module_xml += "\t\n" + if "critical_inverse" in data: + module_xml += "\t\n" + if "warning_inverse" in data: + module_xml += "\t\n" + if "max" in data: + module_xml += "\t\n" + if "min" in data: + module_xml += "\t\n" + if "post_process" in data: + module_xml += "\t\n" + if "disabled" in data: + module_xml += "\t\n" + if "min_ff_event" in data: + module_xml += "\t\n" + if "status" in data: + module_xml += "\t\n" + if "timestamp" in data: + module_xml += "\t\n" + if "custom_id" in data: + module_xml += "\t\n" + if "critical_instructions" in data: + module_xml += "\t\n" + if "warning_instructions" in data: + module_xml += "\t\n" + if "unknown_instructions" in data: + module_xml += "\t\n" + if "quiet" in data: + module_xml += "\t\n" + if "module_ff_interval" in data: + module_xml += "\t\n" + if "crontab" in data: + module_xml += "\t\n" + if "min_ff_event_normal" in data: + module_xml += "\t\n" + if "min_ff_event_warning" in data: + module_xml += "\t\n" + if "min_ff_event_critical" in data: + module_xml += "\t\n" + if "ff_type" in data: + module_xml += "\t\n" + if "ff_timeout" in data: + module_xml += "\t\n" + if "each_ff" in data: + module_xml += "\t\n" + if "module_parent_unlink" in data: + module_xml += "\t\n" + if "global_alerts" in data: + for alert in data["alert"]: + module_xml += "\t\n" + module_xml += "\n" + + #### Print flag + if str_flag is not False: + print (module_xml) + + return (module_xml) + + +######################################################################################### +# MAIN +######################################################################################### + +def main(): + """Checks OS and calls the discover function.""" + if psutil.WINDOWS: + OS = "Windows" + service_list = ["MySQL", "postgresql", "pgsql", "oracle", "MSSQL", "IISADMIN", + "apache", "nginx", "W3svc", "NTDS", "Netlogon", "DNS", "MSExchangeADTopology", + "MSExchangeServiceHost", "MSExchangeSA", "MSExchangeTransport"] + discover(OS, service_list) + elif psutil.LINUX: + OS = "Linux" + service_list = ["httpd", "apache2", "nginx", "ldap", "docker", + "postfix", "mysqld", "postgres", "oracle", "mongod"] + discover(OS, service_list) + else: + print ("OS not recognized. Exiting...", file=stderr) + exit() + +def discover(osyst, servicelist): + """Shows help and triggers the creation of service modules""" + if "--usage" in argv: + memcpu = True + else: + memcpu = False + if len(argv) > 2 and argv[1] == "--list": + servicelist = argv[2].split(",") + if osyst == "Windows": + win_service(servicelist, False, memcpu) ## False won't get children + elif osyst == "Linux": + lnx_service(servicelist, memcpu) + elif len(argv) > 1 and argv[1] == "--default": + if osyst == "Windows": + win_service(servicelist, False, memcpu) ## False won't get children + elif osyst == "Linux": + lnx_service(servicelist, memcpu) + else: + print ("\nPandora FMS Autodiscovery plugin.") + print ("Checks the status of the services in list and monitors CPU and Memory for each of them.\n") + print ("Usage:") + print ("{} [options] [--usage]".format(argv[0])) + print ("--help") + print ("\tPrints this help screen") + print ("--default") + print ("\tRuns this tool with default monitoring.".format(argv[0])) + print ("\tServices monitored by default for {}:".format(osyst)) + print ("\t",", ".join(servicelist)) + print ("--list \"\"") + print ("\tReplaces default services for a given list (comma-separated)") + if osyst == "Windows": + print ("\tEach element of the list will be treated as a regexp, but they must be over 2 characters.") + print ("\tElements under 2 characters will be discarded.") + print ("--usage") + print ("\tAdds modules for CPU and Memory usage per service/process (optional, can take some time).\n") + + +##### RUN #### +main() \ No newline at end of file