ok
Direktori : /lib/fm-agent/library/ |
Current File : //lib/fm-agent/library/inspector.py |
from datetime import datetime import time import agent_util import platform import os import sys import logging import socket try: # Python 2.x import httplib except: import http.client as httplib try: import psutil except: psutil = None try: import distro except: distro = None try: import json except ImportError: try: import simplejson as json # it's possible that we may not need json for the action that we're taking. # for example, for the rpm post install script, on a python version that # doesn't have json, we'll get this far in the code. but the post # install doesn't use json, so we're fine except ImportError: json = None def mac_address_octets(n): if sys.version_info[0] == 2: rng = xrange else: # python 3x uses range rng = range for i in rng(6): yield n & 0xFF n >>= 8 def int_to_mac_address(n): """ Expresses a decimal integer in standard MAC address format ex: 7267271067680 -> '06:9c:0b:1c:48:20' """ values = ["%02x" % a for a in mac_address_octets(n)] return ":".join(reversed(values)) def get_fqdn(): hostname = socket.getfqdn() if "darwin" == sys.platform: """ Mac is odd around ipv4/6 addresses - see: https://stackoverflow.com/questions/8625029/why-does-pythons-socket-getfqdn-return-a-long-string-that-looks-like-an-ipv6 """ if hostname.lower().endswith(".arpa"): hostname = socket.gethostname() return hostname def get_server_name(): if "darwin" != sys.platform: return None try: """ For Fortisase integration, get the value we want to specify for server_name, which is sent up in handshake and also for config changes. This allows us to match the name provided by Forticlient. """ sc_util = agent_util.which("scutil", exc=True) _, output = agent_util.execute_command("{} --get ComputerName".format(sc_util)) return output.strip() except: return None def get_platform_uuids(): log = logging.getLogger("Inspector") my_platform = sys.platform.lower() # # The keys in the rv dictionary should match the textkeys # in the table ServerIdentifierType rv = {"platform": sys.platform.lower()} if "darwin" == my_platform: try: ioreg_bin = agent_util.which("ioreg") rc, output = agent_util.execute_command( "{} -d2 -c IOPlatformExpertDevice | grep IOPlatformUUID".format( ioreg_bin ) ) if 0 == rc: rv["uuid"] = output.strip().split("=")[1].strip().strip('"') else: log.error("ioreg error: {}".format(rc)) except Exception as e: log.info("Gather UUID failure: {}".format(str(e))) pass return rv class Inspector(object): SOURCE_LIST_PATHS = ["/etc/apt/sources.list", "/etc/yum.repos.d/panopta.repo"] PANOPTA_REPO = "http://packages.panopta.com/" def __init__(self, agent): self.agent = agent def get_all_facts(self, wifi_info=None): facts = {} log = logging.getLogger("Inspector") try: facts.update(self.get_agent_facts()) facts.update(self.get_python_facts()) facts.update(self.get_process_facts()) facts.update(self.get_mac_addresses()) facts.update(self.get_hardware_facts()) facts.update(self.get_time_facts()) facts.update(self.get_hostname()) facts.update(self.get_platform_facts()) facts.update(self.get_machine_facts()) facts.update(self.get_wifi_facts(wifi_info)) facts.update(self.get_dem_network_facts()) if sys.version_info >= (2, 6, 0): # Cloud detection, but only if we're on a new enough Python to have timeout facts.update(self.get_cloud_facts()) except Exception as e: log.info("get_all facts: {}".format(e)) return facts def get_platform_facts(self): facts = {} if "darwin" == sys.platform: try: _, output = agent_util.execute_command("sw_vers | grep ProductVersion") pv = output.split(":")[1].strip() facts["MacOSVersion"] = pv except: pass return facts def get_hostname(self): facts = {} facts["hostname"] = get_fqdn() return facts def get_time_facts(self): facts = {} try: retcode, output = agent_util.execute_command("ls -l /etc/localtime") timezone = "/".join(output.strip().split("/")[-2:]) facts["timezone"] = timezone except: log = logging.getLogger(self.__class__.__name__) log.exception("Unable to get Time Zone") return facts def get_hardware_facts(self): """ Gather CPU and memory specs for the machine """ facts = {"hardware": {}} if "darwin" == sys.platform.lower(): try: retcode, output = agent_util.execute_command( "sysctl -a | grep machdep.cpu" ) for line in output.strip().split("\n"): try: key, value = line.strip().split(":", 1) if key in ( "machdep.cpu.brand_string", "machdep.cpu.core_count", ): facts["hardware"][key] = value.strip() except: continue except: log = logging.getLogger(self.__class__.__name__) log.exception("Unable to get CPU hardware facts") try: total_mem = os.sysconf("SC_PHYS_PAGES") * os.sysconf("SC_PAGE_SIZE") facts["hardware"]["mem_total"] = total_mem except: log = logging.getLogger(self.__class__.__name__) log.exception("Unable to get memory hardware facts") else: try: retcode, output = agent_util.execute_command("lscpu") for line in output.strip().split("\n"): try: key, value = line.strip().split(":", 1) except: continue key = key.strip().lower().replace("(s)", "").replace(" ", "_") value = value.strip() facts["hardware"][key] = value except: log = logging.getLogger(self.__class__.__name__) log.exception("Unable to get CPU hardware facts") try: retcode, output = agent_util.execute_command("free -m") for line in output.strip().split("\n"): fields = line.strip().split() if fields[0].lower().startswith("mem"): facts["hardware"]["mem_total"] = int(fields[1]) if fields[0].lower().startswith("swap"): facts["hardware"]["swap_total"] = int(fields[1]) except: log = logging.getLogger(self.__class__.__name__) log.exception("Unable to get memory hardware facts") return facts def get_agent_facts(self): facts = {} # This is a little hokey, but the last time this file's metadata changed # *should* be when this file whas created, ie. when the Agent was # installed. I thought this was better than storing the install time in # the local database, since there is a possibility that it could get # corrupted. facts["installed_time"] = os.path.getctime(os.path.abspath(__file__)) facts["used_manifest"] = os.path.exists(self.agent.manifest_file) facts["installed_from_repo"] = False for source_list_path in self.SOURCE_LIST_PATHS: if os.path.exists(source_list_path): try: source_list = open(source_list_path) facts["installed_from_repo"] = ( self.PANOPTA_REPO in source_list.read() ) source_list.close() if facts["installed_from_repo"]: break except: pass brand = "panopta" if "darwin" == sys.platform or os.path.exists("/etc/fm-agent"): brand = "fortimonitor" # Set the agent brand, default to Panopta unless we have an FM-Agent config file facts["agent_brand"] = brand return facts def get_python_facts(self): facts = {"python": {}} facts["python"]["platform"] = platform.platform() facts["python"]["processor"] = platform.processor() facts["python"]["version"] = platform.python_version() facts["python"]["uname"] = platform.uname() try: facts["python"]["dist"] = platform.dist() except AttributeError: # Removed in Python 3.8. # https://docs.python.org/2.7/library/platform.html#platform.linux_distribution if distro: facts["python"]["dist"] = " ".join(distro.linux_distribution()) facts["python"]["sys_platform"] = sys.platform return facts def get_cloud_facts(self): facts = {} # Try Amazon try: h = httplib.HTTPConnection("169.254.169.254", timeout=5) h.request("GET", "/latest/dynamic/instance-identity/document") r = h.getresponse() if r.status == 200: data = json.loads(r.read()) facts["cloud_provider"] = "aws" facts["cloud_instance_id"] = data["instanceId"] facts["cloud_metadata"] = data return facts except: pass # Try Google try: headers = {"Metadata-Flavor": "Google"} h = httplib.HTTPConnection("metadata.google.internal", timeout=5) h.request( "GET", "/computeMetadata/v1/instance/?recursive=true", headers=headers ) r = h.getresponse() if r.status == 200: data = json.loads(r.read()) facts["cloud_provider"] = "gcp" facts["cloud_instance_id"] = data["id"] # Strip out sensitive keys if "serviceAccounts" in data: del data["serviceAccounts"] for key, value in data.get("attributes", {}).items(): if key in [ "/attribute", "/sshkeys", "/vmdnssetting", "/enable-oslogin", ]: data["attributes"].pop(key) facts["cloud_metadata"] = data try: manifest = self.agent.get_manifest() enabled_gcp_attributes = ( manifest.get("agent", "enable_gcp_attributes") == "true" ) facts["cloud_metadata"]["enable_gcp_attributes"] = ( enabled_gcp_attributes ) except Exception: log = logging.getLogger(self.__class__.__name__) log.exception( "Unable to parse manifest file to determine gcp attributes actions." ) return facts except: pass # Try Azure try: headers = {"Metadata": "true"} h = httplib.HTTPConnection("169.254.169.254", timeout=5) h.request( "GET", "/metadata/instance?api-version=2017-04-02", headers=headers ) r = h.getresponse() if r.status == 200: data = json.loads(r.read()) facts["cloud_provider"] = "azure" facts["cloud_instance_id"] = data["compute"]["vmId"] facts["cloud_metadata"] = data return facts except: pass # No cloud detected return {} def get_process_facts(self): facts = {} if psutil is None: return facts processes = set() for proc in psutil.process_iter(): processes.add(proc.name()) facts["processes"] = list(processes) return facts def mac_address_iter(self): import psutil for iface, addrs in psutil.net_if_addrs().items(): for addr in addrs: if addr.family != psutil.AF_LINK: continue if addr.address == "00:00:00:00:00:00": continue yield addr.address def get_mac_addresses(self): facts = {} try: import uuid facts["uuid_getnode"] = int_to_mac_address(uuid.getnode()) except: log = logging.getLogger(self.__class__.__name__) log.info( "Unable to import uuid module. Skipping MAC address fact collection." ) try: addrs = [] for i, mac_addr in enumerate(self.mac_address_iter()): addrs.append(mac_addr) facts["macaddress"] = addrs except ImportError: log = logging.getLogger(self.__class__.__name__) log.info("Unable to import psutil, skipping MAC address fact collection") except: log = logging.getLogger(self.__class__.__name__) log.info("Unknown error during MAC address collection") return facts def get_machine_facts(self): md = {} if "darwin" != sys.platform: return md facts = {"machine_description": md} try: tk = "SPHardwareDataType" data = self.query_system_profiler(tk) if data: info = data[tk][0] md["serial"] = info.get("serial_number", "") md["model"] = info.get("machine_model", "") except: facts.clear() return facts def get_wifi_facts(self, wifi_info=None): md = {} facts = {"DEMWifiInfo": md} try: md["ssid"] = wifi_info.get("SSID", "") md["bssid"] = wifi_info.get("BSSID", "") md["channel"] = wifi_info.get("channel", "") md["security_algorithm"] = wifi_info.get("link auth", "") md["nss"] = wifi_info.get("NSS", "") except: md.clear() md["timestamp"] = datetime.utcnow().timestamp() return facts def get_dem_network_facts(self): if "darwin" != sys.platform: return {} md = [] facts = {"DEMNetworkInterfaceConfigurations": md} try: tk = "SPNetworkDataType" info = self.query_system_profiler(tk) if info: network_infos = info.get(tk, []) for ni in network_infos: router = ni.get("IPv4", {}).get("Router", None) if not router: continue item = { "name": ni.get("_name"), "description": ni.get("hardware"), "dns_servers": [], "gateways": [router], } md.append(item) dns_info = ni.get("DNS", None) if dns_info: for sa in dns_info.get("ServerAddresses", []): item["dns_servers"].append(sa) except: logging.exception("get_dem_network_facts") facts.clear() return facts def query_system_profiler(self, info_type): try: sp = agent_util.which("system_profiler") from subprocess import Popen, PIPE d = Popen([sp, "-json", info_type], stdout=PIPE) data = json.loads(d.stdout.read()) return data except: logging.exception("query_system_profiler") return None