Headline
CVE-2021-33226: salt/status.py at master · saltstack/salt
Buffer Overflow vulnerability in Saltstack v.3003 and before allows attacker to execute arbitrary code via the func variable in salt/salt/modules/status.py file.
“"” Module for returning various status data about a minion. These data can be useful for compiling into stats later. “"” import collections import copy import datetime import fnmatch import itertools import logging import os import re import time import salt.channel import salt.config import salt.minion import salt.utils.event import salt.utils.files import salt.utils.network import salt.utils.path import salt.utils.platform import salt.utils.stringutils from salt.exceptions import CommandExecutionError log = logging.getLogger(__file__) __virtualname__ = “status” # Don’t shadow built-in’s. __func_alias__ = {"time_": “time"} log = logging.getLogger(__name__) def __virtual__(): “"” Not all functions supported by Windows “"” if salt.utils.platform.is_windows(): return False, “Windows platform is not supported by this module” return __virtualname__ def _number(text): “"” Convert a string to a number. Returns an integer if the string represents an integer, a floating point number if the string is a real number, or the string unchanged otherwise. “"” if text.isdigit(): return int(text) try: return float(text) except ValueError: return text def _get_boot_time_aix(): “"” Return the number of seconds since boot time on AIX t=$(LC_ALL=POSIX ps -o etime= -p 1) d=0 h=0 case $t in *-*) d=${t%%-*}; t=${t#*-};; esac case $t in *:*:*) h=${t%%:*}; t=${t#*:};; esac s=$((d*86400 + h*3600 + ${t%%:*}*60 + ${t#*:})) “"” res = __salt__[“cmd.run_all”](“ps -o etime= -p 1”) if res[“retcode”] > 0: raise CommandExecutionError(“Unable to find boot_time for pid 1.”) bt_time = res[“stdout”] match = re.match(r"\s*(?:(\d+)-)?(?:(\d\d):)?(\d\d):(\d\d)\s*", bt_time) if not match: raise CommandExecutionError(“Unexpected time format.”) groups = match.groups(default="00”) boot_secs = ( _number(groups[0]) * 86400 + _number(groups[1]) * 3600 + _number(groups[2]) * 60 + _number(groups[3]) ) return boot_secs def _aix_loadavg(): “"” Return the load average on AIX “"” # 03:42PM up 9 days, 20:41, 2 users, load average: 0.28, 0.47, 0.69 uptime = __salt__[“cmd.run”](“uptime”) ldavg = uptime.split(“load average”) load_avg = ldavg[1].split() return { "1-min": load_avg[1].strip(“,”), "5-min": load_avg[2].strip(“,”), "15-min": load_avg[3], } def _aix_nproc(): “"” Return the maximun number of PROCESSES allowed per user on AIX “"” nprocs = __salt__[“cmd.run”]( "lsattr -E -l sys0 | grep maxuproc", python_shell=True ).split() return _number(nprocs[1]) def procs(): “"” Return the process data … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.procs “"” # Get the user, pid and cmd ret = {} uind = 0 pind = 0 cind = 0 plines = __salt__[“cmd.run”](__grains__[“ps”], python_shell=True).splitlines() guide = plines.pop(0).split() if “USER” in guide: uind = guide.index(“USER”) elif “UID” in guide: uind = guide.index(“UID”) if “PID” in guide: pind = guide.index(“PID”) if “COMMAND” in guide: cind = guide.index(“COMMAND”) elif “CMD” in guide: cind = guide.index(“CMD”) for line in plines: if not line: continue comps = line.split() ret[comps[pind]] = {"user": comps[uind], "cmd": " ".join(comps[cind:])} return ret def custom(): “"” Return a custom composite of status data and info for this minion, based on the minion config file. An example config like might be:: status.cpustats.custom: [ 'cpu’, 'ctxt’, 'btime’, ‘processes’ ] Where status refers to status.py, cpustats is the function where we get our data, and custom is this function It is followed by a list of keys that we want returned. This function is meant to replace all_status(), which returns anything and everything, which we probably don’t want. By default, nothing is returned. Warning: Depending on what you include, there can be a LOT here! CLI Example: … code-block:: bash salt ‘*’ status.custom “"” ret = {} conf = __salt__[“config.dot_vals”](“status”) for key, val in conf.items(): func = "{}()".format(key.split(“.”)[1]) vals = eval(func) # pylint: disable=W0123 for item in val: ret[item] = vals[item] return ret def uptime(): “"” Return the uptime for this system. … versionchanged:: 2015.8.9 The uptime function was changed to return a dictionary of easy-to-read key/value pairs containing uptime information, instead of the output from a ``cmd.run`` call. … versionchanged:: 2016.11.0 Support for OpenBSD, FreeBSD, NetBSD, MacOS, and Solaris … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.uptime “"” curr_seconds = time.time() # Get uptime in seconds if salt.utils.platform.is_linux(): ut_path = “/proc/uptime” if not os.path.exists(ut_path): raise CommandExecutionError( "File {ut_path} was not found.".format(ut_path=ut_path) ) with salt.utils.files.fopen(ut_path) as rfh: seconds = int(float(rfh.read().split()[0])) elif salt.utils.platform.is_sunos(): # note: some flavors/versions report the host uptime inside a zone # https://support.oracle.com/epmos/faces/BugDisplay?id=15611584 res = __salt__[“cmd.run_all”](“kstat -p unix:0:system_misc:boot_time”) if res[“retcode”] > 0: raise CommandExecutionError(“The boot_time kstat was not found.”) seconds = int(curr_seconds - int(res[“stdout”].split()[-1])) elif salt.utils.platform.is_openbsd() or salt.utils.platform.is_netbsd(): bt_data = __salt__[“sysctl.get”](“kern.boottime”) if not bt_data: raise CommandExecutionError(“Cannot find kern.boottime system parameter”) seconds = int(curr_seconds - int(bt_data)) elif salt.utils.platform.is_freebsd() or salt.utils.platform.is_darwin(): # format: { sec = 1477761334, usec = 664698 } Sat Oct 29 17:15:34 2016 bt_data = __salt__[“sysctl.get”](“kern.boottime”) if not bt_data: raise CommandExecutionError(“Cannot find kern.boottime system parameter”) data = bt_data.split(“{”)[-1].split(“}”)[0].strip().replace(" ", “”) uptime = { k: int( v, ) for k, v in [p.strip().split(“=”) for p in data.split(“,”)] } seconds = int(curr_seconds - uptime[“sec”]) elif salt.utils.platform.is_aix(): seconds = _get_boot_time_aix() else: return __salt__[“cmd.run”](“uptime”) # Setup datetime and timedelta objects boot_time = datetime.datetime.utcfromtimestamp(curr_seconds - seconds) curr_time = datetime.datetime.utcfromtimestamp(curr_seconds) up_time = curr_time - boot_time # Construct return information ut_ret = { "seconds": seconds, "since_iso": boot_time.isoformat(), "since_t": int(curr_seconds - seconds), "days": up_time.days, "time": "{}:{}".format(up_time.seconds // 3600, up_time.seconds % 3600 // 60), } if salt.utils.path.which(“who”): who_cmd = ( “who” if salt.utils.platform.is_openbsd() else “who -s” ) # OpenBSD does not support -s ut_ret[“users”] = len(__salt__[“cmd.run”](who_cmd).split(os.linesep)) return ut_ret def loadavg(): “"” Return the load averages for this minion … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.loadavg :raises CommandExecutionError: If the system cannot report loadaverages to Python “"” if __grains__[“kernel”] == "AIX": return _aix_loadavg() try: load_avg = os.getloadavg() except AttributeError: # Some UNIX-based operating systems do not have os.getloadavg() raise salt.exceptions.CommandExecutionError( “status.loadavag is not available on your platform” ) return {"1-min": load_avg[0], "5-min": load_avg[1], "15-min": load_avg[2]} def cpustats(): “"” Return the CPU stats for this minion … versionchanged:: 2016.11.4 Added support for AIX … versionchanged:: 2018.3.0 Added support for OpenBSD CLI Example: … code-block:: bash salt ‘*’ status.cpustats “"” def linux_cpustats(): “"” linux specific implementation of cpustats “"” ret = {} try: with salt.utils.files.fopen("/proc/stat", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: for line in stats.splitlines(): if not line: continue comps = line.split() if comps[0] == "cpu": ret[comps[0]] = { "idle": _number(comps[4]), "iowait": _number(comps[5]), "irq": _number(comps[6]), "nice": _number(comps[2]), "softirq": _number(comps[7]), "steal": _number(comps[8]), "system": _number(comps[3]), "user": _number(comps[1]), } elif comps[0] == "intr": ret[comps[0]] = { "total": _number(comps[1]), "irqs": [_number(x) for x in comps[2:]], } elif comps[0] == "softirq": ret[comps[0]] = { "total": _number(comps[1]), "softirqs": [_number(x) for x in comps[2:]], } else: ret[comps[0]] = _number(comps[1]) return ret def freebsd_cpustats(): “"” freebsd specific implementation of cpustats “"” vmstat = __salt__[“cmd.run”](“vmstat -P”).splitlines() vm0 = vmstat[0].split() cpu0loc = vm0.index(“cpu0”) vm1 = vmstat[1].split() usloc = vm1.index(“us”) vm2 = vmstat[2].split() cpuctr = 0 ret = {} for cpu in vm0[cpu0loc:]: ret[cpu] = { "us": _number(vm2[usloc + 3 * cpuctr]), "sy": _number(vm2[usloc + 1 + 3 * cpuctr]), "id": _number(vm2[usloc + 2 + 3 * cpuctr]), } cpuctr += 1 return ret def sunos_cpustats(): “"” sunos specific implementation of cpustats “"” mpstat = __salt__[“cmd.run”](“mpstat 1 2”).splitlines() fields = mpstat[0].split() ret = {} for cpu in mpstat: if cpu.startswith(“CPU”): continue cpu = cpu.split() ret[_number(cpu[0])] = {} for i in range(1, len(fields) - 1): ret[_number(cpu[0])][fields[i]] = _number(cpu[i]) return ret def aix_cpustats(): “"” AIX specific implementation of cpustats “"” ret = {} ret[“mpstat”] = [] procn = None fields = [] for line in __salt__[“cmd.run”](“mpstat -a”).splitlines(): if not line: continue procn = len(ret[“mpstat”]) if line.startswith(“System”): comps = line.split(“:”) ret[“mpstat”].append({}) ret[“mpstat”][procn][“system”] = {} cpu_comps = comps[1].split() for comp in cpu_comps: cpu_vals = comp.split(“=”) ret[“mpstat”][procn][“system”][cpu_vals[0]] = cpu_vals[1] if line.startswith(“cpu”): fields = line.split() continue if fields: cpustat = line.split() ret[_number(cpustat[0])] = {} for i in range(1, len(fields) - 1): ret[_number(cpustat[0])][fields[i]] = _number(cpustat[i]) return ret def openbsd_cpustats(): “"” openbsd specific implementation of cpustats “"” systat = __salt__[“cmd.run”](“systat -s 2 -B cpu”).splitlines() fields = systat[3].split() ret = {} for cpu in systat[4:]: cpu_line = cpu.split() cpu_idx = cpu_line[0] ret[cpu_idx] = {} for idx, field in enumerate(fields[1:]): ret[cpu_idx][field] = cpu_line[idx + 1] return ret # dict that return a function that does the right thing per platform get_version = { "Linux": linux_cpustats, "FreeBSD": freebsd_cpustats, "Junos": freebsd_cpustats, "OpenBSD": openbsd_cpustats, "SunOS": sunos_cpustats, "AIX": aix_cpustats, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def meminfo(): “"” Return the memory info for this minion … versionchanged:: 2016.11.4 Added support for AIX … versionchanged:: 2018.3.0 Added support for OpenBSD CLI Example: … code-block:: bash salt ‘*’ status.meminfo “"” def linux_meminfo(): “"” linux specific implementation of meminfo “"” ret = {} try: with salt.utils.files.fopen("/proc/meminfo", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: for line in stats.splitlines(): if not line: continue comps = line.split() comps[0] = comps[0].replace(":", “”) ret[comps[0]] = { "value": comps[1], } if len(comps) > 2: ret[comps[0]][“unit”] = comps[2] return ret def freebsd_meminfo(): “"” freebsd specific implementation of meminfo “"” sysctlvm = __salt__[“cmd.run”](“sysctl vm”).splitlines() sysctlvm = [x for x in sysctlvm if x.startswith(“vm”)] sysctlvm = [x.split(“:”) for x in sysctlvm] sysctlvm = [[y.strip() for y in x] for x in sysctlvm] sysctlvm = [x for x in sysctlvm if x[1]] # If x[1] not empty ret = {} for line in sysctlvm: ret[line[0]] = line[1] # Special handling for vm.total as it’s especially important sysctlvmtot = __salt__[“cmd.run”](“sysctl -n vm.vmtotal”).splitlines() sysctlvmtot = [x for x in sysctlvmtot if x] ret[“vm.vmtotal”] = sysctlvmtot return ret def aix_meminfo(): “"” AIX specific implementation of meminfo “"” ret = {} ret[“svmon”] = [] ret[“vmstat”] = [] procn = None fields = [] pagesize_flag = False for line in __salt__[“cmd.run”](“svmon -G”).splitlines(): # Note: svmon is per-system # size inuse free pin virtual mmode # memory 1048576 1039740 8836 285078 474993 Ded # pg space 917504 2574 # # work pers clnt other # pin 248379 0 2107 34592 # in use 474993 0 564747 # # PageSize PoolSize inuse pgsp pin virtual # s 4 KB - 666956 2574 60726 102209 # m 64 KB - 23299 0 14022 23299 if not line: continue if re.match(r"\s", line): # assume fields line fields = line.split() continue if line.startswith(“memory”) or line.startswith(“pin”): procn = len(ret[“svmon”]) ret[“svmon”].append({}) comps = line.split() ret[“svmon”][procn][comps[0]] = {} for idx, field in enumerate(fields): if len(comps) > idx + 1: ret[“svmon”][procn][comps[0]][field] = comps[idx + 1] continue if line.startswith(“pg space”) or line.startswith(“in use”): procn = len(ret[“svmon”]) ret[“svmon”].append({}) comps = line.split() pg_space = "{} {}".format(comps[0], comps[1]) ret[“svmon”][procn][pg_space] = {} for idx, field in enumerate(fields): if len(comps) > idx + 2: ret[“svmon”][procn][pg_space][field] = comps[idx + 2] continue if line.startswith(“PageSize”): fields = line.split() pagesize_flag = False continue if pagesize_flag: procn = len(ret[“svmon”]) ret[“svmon”].append({}) comps = line.split() ret[“svmon”][procn][comps[0]] = {} for idx, field in enumerate(fields): if len(comps) > idx: ret[“svmon”][procn][comps[0]][field] = comps[idx] continue for line in __salt__[“cmd.run”](“vmstat -v”).splitlines(): # Note: vmstat is per-system if not line: continue procn = len(ret[“vmstat”]) ret[“vmstat”].append({}) comps = line.lstrip().split(" ", 1) ret[“vmstat”][procn][comps[1]] = comps[0] return ret def openbsd_meminfo(): “"” openbsd specific implementation of meminfo “"” vmstat = __salt__[“cmd.run”](“vmstat”).splitlines() # We’re only interested in memory and page values which are printed # as subsequent fields. fields = [ "active virtual pages", "free list size", "page faults", "pages reclaimed", "pages paged in", "pages paged out", "pages freed", "pages scanned", ] data = vmstat[2].split()[2:10] ret = dict(zip(fields, data)) return ret # dict that return a function that does the right thing per platform get_version = { "Linux": linux_meminfo, "FreeBSD": freebsd_meminfo, "Junos": freebsd_meminfo, "OpenBSD": openbsd_meminfo, "AIX": aix_meminfo, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def cpuinfo(): “"” … versionchanged:: 2016.3.2 Return the CPU info for this minion … versionchanged:: 2016.11.4 Added support for AIX … versionchanged:: 2018.3.0 Added support for NetBSD and OpenBSD CLI Example: … code-block:: bash salt ‘*’ status.cpuinfo “"” def linux_cpuinfo(): “"” linux specific cpuinfo implementation “"” ret = {} try: with salt.utils.files.fopen("/proc/cpuinfo", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: for line in stats.splitlines(): if not line: continue comps = line.split(“:”) comps[0] = comps[0].strip() if comps[0] == "flags": ret[comps[0]] = comps[1].split() else: ret[comps[0]] = comps[1].strip() return ret def bsd_cpuinfo(): “"” bsd specific cpuinfo implementation “"” bsd_cmd = “sysctl hw.model hw.ncpu” ret = {} if __grains__[“kernel”].lower() in ["netbsd", “openbsd”]: sep = “=” else: sep = “:” for line in __salt__[“cmd.run”](bsd_cmd).splitlines(): if not line: continue comps = line.split(sep) comps[0] = comps[0].strip() ret[comps[0]] = comps[1].strip() return ret def sunos_cpuinfo(): “"” sunos specific cpuinfo implementation “"” ret = {} ret[“isainfo”] = {} for line in __salt__[“cmd.run”](“isainfo -x”).splitlines(): # Note: isainfo is per-system and not per-cpu # Output Example: # amd64: rdrand f16c vmx avx xsave pclmulqdq aes sse4.2 sse4.1 ssse3 popcnt tscp cx16 sse3 sse2 sse fxsr mmx cmov amd_sysc cx8 tsc fpu # i386: rdrand f16c vmx avx xsave pclmulqdq aes sse4.2 sse4.1 ssse3 popcnt tscp ahf cx16 sse3 sse2 sse fxsr mmx cmov sep cx8 tsc fpu if not line: continue comps = line.split(“:”) comps[0] = comps[0].strip() ret[“isainfo”][comps[0]] = sorted(comps[1].strip().split()) ret[“psrinfo”] = [] procn = None for line in __salt__[“cmd.run”](“psrinfo -v -p”).splitlines(): # Output Example: # The physical processor has 6 cores and 12 virtual processors (0-5 12-17) # The core has 2 virtual processors (0 12) # The core has 2 virtual processors (1 13) # The core has 2 virtual processors (2 14) # The core has 2 virtual processors (3 15) # The core has 2 virtual processors (4 16) # The core has 2 virtual processors (5 17) # x86 (GenuineIntel 306E4 family 6 model 62 step 4 clock 2100 MHz) # Intel® Xeon® CPU E5-2620 v2 @ 2.10GHz # The physical processor has 6 cores and 12 virtual processors (6-11 18-23) # The core has 2 virtual processors (6 18) # The core has 2 virtual processors (7 19) # The core has 2 virtual processors (8 20) # The core has 2 virtual processors (9 21) # The core has 2 virtual processors (10 22) # The core has 2 virtual processors (11 23) # x86 (GenuineIntel 306E4 family 6 model 62 step 4 clock 2100 MHz) # Intel® Xeon® CPU E5-2620 v2 @ 2.10GHz # # Output Example 2: # The physical processor has 4 virtual processors (0-3) # x86 (GenuineIntel 406D8 family 6 model 77 step 8 clock 2400 MHz) # Intel® Atom™ CPU C2558 @ 2.40GHz if not line: continue if line.startswith(“The physical processor”): procn = len(ret[“psrinfo”]) line = line.split() ret[“psrinfo”].append({}) if “cores” in line: ret[“psrinfo”][procn][“topology”] = {} ret[“psrinfo”][procn][“topology”][“cores”] = _number(line[4]) ret[“psrinfo”][procn][“topology”][“threads”] = _number(line[7]) elif “virtual” in line: ret[“psrinfo”][procn][“topology”] = {} ret[“psrinfo”][procn][“topology”][“threads”] = _number(line[4]) elif line.startswith(" " * 6): # 3x2 space indent ret[“psrinfo”][procn][“name”] = line.strip() elif line.startswith(" " * 4): # 2x2 space indent line = line.strip().split() ret[“psrinfo”][procn][“vendor”] = line[1][1:] ret[“psrinfo”][procn][“family”] = _number(line[4]) ret[“psrinfo”][procn][“model”] = _number(line[6]) ret[“psrinfo”][procn][“step”] = _number(line[8]) ret[“psrinfo”][procn][“clock”] = "{} {}".format(line[10], line[11][:-1]) return ret def aix_cpuinfo(): “"” AIX specific cpuinfo implementation “"” ret = {} ret[“prtconf”] = [] ret[“lparstat”] = [] procn = None for line in __salt__[“cmd.run”]( 'prtconf | grep -i "Processor"’, python_shell=True ).splitlines(): # Note: prtconf is per-system and not per-cpu # Output Example: # prtconf | grep -i “Processor” # Processor Type: PowerPC_POWER7 # Processor Implementation Mode: POWER 7 # Processor Version: PV_7_Compat # Number Of Processors: 2 # Processor Clock Speed: 3000 MHz # Model Implementation: Multiple Processor, PCI bus # + proc0 Processor # + proc4 Processor if not line: continue procn = len(ret[“prtconf”]) if line.startswith(“Processor”) or line.startswith(“Number”): ret[“prtconf”].append({}) comps = line.split(“:”) comps[0] = comps[0].rstrip() ret[“prtconf”][procn][comps[0]] = comps[1] else: continue for line in __salt__[“cmd.run”]( ‘prtconf | grep "CPU"’, python_shell=True ).splitlines(): # Note: prtconf is per-system and not per-cpu # Output Example: # CPU Type: 64-bit if not line: continue procn = len(ret[“prtconf”]) if line.startswith(“CPU”): ret[“prtconf”].append({}) comps = line.split(“:”) comps[0] = comps[0].rstrip() ret[“prtconf”][procn][comps[0]] = comps[1] else: continue for line in __salt__[“cmd.run”]( "lparstat -i | grep CPU", python_shell=True ).splitlines(): # Note: lparstat is per-system and not per-cpu # Output Example: # Online Virtual CPUs : 2 # Maximum Virtual CPUs : 2 # Minimum Virtual CPUs : 1 # Maximum Physical CPUs in system : 32 # Active Physical CPUs in system : 32 # Active CPUs in Pool : 32 # Shared Physical CPUs in system : 32 # Physical CPU Percentage : 25.00% # Desired Virtual CPUs : 2 if not line: continue procn = len(ret[“lparstat”]) ret[“lparstat”].append({}) comps = line.split(“:”) comps[0] = comps[0].rstrip() ret[“lparstat”][procn][comps[0]] = comps[1] return ret # dict that returns a function that does the right thing per platform get_version = { "Linux": linux_cpuinfo, "FreeBSD": bsd_cpuinfo, "Junos": bsd_cpuinfo, "NetBSD": bsd_cpuinfo, "OpenBSD": bsd_cpuinfo, "SunOS": sunos_cpuinfo, "AIX": aix_cpuinfo, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def diskstats(): “"” … versionchanged:: 2016.3.2 Return the disk stats for this minion … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.diskstats “"” def linux_diskstats(): “"” linux specific implementation of diskstats “"” ret = {} try: with salt.utils.files.fopen("/proc/diskstats", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: for line in stats.splitlines(): if not line: continue comps = line.split() ret[comps[2]] = { "major": _number(comps[0]), "minor": _number(comps[1]), "device": _number(comps[2]), "reads_issued": _number(comps[3]), "reads_merged": _number(comps[4]), "sectors_read": _number(comps[5]), "ms_spent_reading": _number(comps[6]), "writes_completed": _number(comps[7]), "writes_merged": _number(comps[8]), "sectors_written": _number(comps[9]), "ms_spent_writing": _number(comps[10]), "io_in_progress": _number(comps[11]), "ms_spent_in_io": _number(comps[12]), "weighted_ms_spent_in_io": _number(comps[13]), } return ret def generic_diskstats(): “"” generic implementation of diskstats note: freebsd and sunos “"” ret = {} iostat = __salt__[“cmd.run”](“iostat -xzd”).splitlines() header = iostat[1] for line in iostat[2:]: comps = line.split() ret[comps[0]] = {} for metric, value in zip(header.split()[1:], comps[1:]): ret[comps[0]][metric] = _number(value) return ret def aix_diskstats(): “"” AIX specific implementation of diskstats “"” ret = {} procn = None fields = [] disk_name = “” disk_mode = “” for line in __salt__[“cmd.run”](“iostat -dDV”).splitlines(): # Note: iostat -dDV is per-system # # System configuration: lcpu=8 drives=1 paths=2 vdisks=2 # # hdisk0 xfer: %tm_act bps tps bread bwrtn # 0.0 0.8 0.0 0.0 0.8 # read: rps avgserv minserv maxserv timeouts fails # 0.0 2.5 0.3 12.4 0 0 # write: wps avgserv minserv maxserv timeouts fails # 0.0 0.3 0.2 0.7 0 0 # queue: avgtime mintime maxtime avgwqsz avgsqsz sqfull # 0.3 0.0 5.3 0.0 0.0 0.0 # -------------------------------------------------------------------------------- if not line or line.startswith(“System”) or line.startswith("-----------"): continue if not re.match(r"\s", line): # have new disk dsk_comps = line.split(“:”) dsk_firsts = dsk_comps[0].split() disk_name = dsk_firsts[0] disk_mode = dsk_firsts[1] fields = dsk_comps[1].split() ret[disk_name] = [] procn = len(ret[disk_name]) ret[disk_name].append({}) ret[disk_name][procn][disk_mode] = {} continue if “:” in line: comps = line.split(“:”) fields = comps[1].split() disk_mode = comps[0].lstrip() procn = len(ret[disk_name]) ret[disk_name].append({}) ret[disk_name][procn][disk_mode] = {} else: comps = line.split() for idx, field in enumerate(fields): if len(comps) > idx: ret[disk_name][procn][disk_mode][field] = comps[idx] return ret # dict that return a function that does the right thing per platform get_version = { "Linux": linux_diskstats, "FreeBSD": generic_diskstats, "Junos": generic_diskstats, "SunOS": generic_diskstats, "AIX": aix_diskstats, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def diskusage(*args): “"” Return the disk usage for this minion Usage:: salt ‘*’ status.diskusage [paths and/or filesystem types] CLI Example: … code-block:: bash salt ‘*’ status.diskusage # usage for all filesystems salt ‘*’ status.diskusage / /tmp # usage for / and /tmp salt ‘*’ status.diskusage ext? # usage for ext[234] filesystems salt ‘*’ status.diskusage / ext? # usage for / and all ext filesystems “"” selected = set() fstypes = set() if not args: # select all filesystems fstypes.add(“*”) else: for arg in args: if arg.startswith(“/”): # select path selected.add(arg) else: # select fstype fstypes.add(arg) if fstypes: # determine which mount points host the specified fstypes regex = re.compile( "|".join(fnmatch.translate(fstype).format("(%s)") for fstype in fstypes) ) # ifile source of data varies with OS, otherwise all the same if __grains__[“kernel”] == "Linux": try: with salt.utils.files.fopen("/proc/mounts", “r”) as fp_: ifile = salt.utils.stringutils.to_unicode(fp_.read()).splitlines() except OSError: return {} elif __grains__[“kernel”] in ("FreeBSD", “SunOS”): ifile = __salt__[“cmd.run”](“mount -p”).splitlines() else: raise CommandExecutionError( “status.diskusage not yet supported on this platform” ) for line in ifile: comps = line.split() if __grains__[“kernel”] == "SunOS": if len(comps) >= 4: mntpt = comps[2] fstype = comps[3] if regex.match(fstype): selected.add(mntpt) else: if len(comps) >= 3: mntpt = comps[1] fstype = comps[2] if regex.match(fstype): selected.add(mntpt) # query the filesystems disk usage ret = {} for path in selected: fsstats = os.statvfs(path) blksz = fsstats.f_bsize available = fsstats.f_bavail * blksz total = fsstats.f_blocks * blksz ret[path] = {"available": available, "total": total} return ret def vmstats(): “"” … versionchanged:: 2016.3.2 Return the virtual memory stats for this minion … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.vmstats “"” def linux_vmstats(): “"” linux specific implementation of vmstats “"” ret = {} try: with salt.utils.files.fopen("/proc/vmstat", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: for line in stats.splitlines(): if not line: continue comps = line.split() ret[comps[0]] = _number(comps[1]) return ret def generic_vmstats(): “"” generic implementation of vmstats note: works on FreeBSD, SunOS and OpenBSD (possibly others) “"” ret = {} for line in __salt__[“cmd.run”](“vmstat -s”).splitlines(): comps = line.split() if comps[0].isdigit(): ret[" ".join(comps[1:])] = _number(comps[0].strip()) return ret # dict that returns a function that does the right thing per platform get_version = { "Linux": linux_vmstats, "FreeBSD": generic_vmstats, "Junos": generic_vmstats, "OpenBSD": generic_vmstats, "SunOS": generic_vmstats, "AIX": generic_vmstats, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def nproc(): “"” Return the number of processing units available on this system … versionchanged:: 2016.11.4 Added support for AIX … versionchanged:: 2018.3.0 Added support for Darwin, FreeBSD and OpenBSD CLI Example: … code-block:: bash salt ‘*’ status.nproc “"” def linux_nproc(): “"” linux specific implementation of nproc “"” try: return _number(__salt__[“cmd.run”](“nproc”).strip()) except ValueError: return 0 def generic_nproc(): “"” generic implementation of nproc “"” ncpu_data = __salt__[“sysctl.get”](“hw.ncpu”) if not ncpu_data: # We need at least one CPU to run return 1 else: return _number(ncpu_data) # dict that returns a function that does the right thing per platform get_version = { "Linux": linux_nproc, "Darwin": generic_nproc, "FreeBSD": generic_nproc, "Junos": generic_nproc, "OpenBSD": generic_nproc, "AIX": _aix_nproc, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def netstats(): “"” Return the network stats for this minion … versionchanged:: 2016.11.4 Added support for AIX … versionchanged:: 2018.3.0 Added support for OpenBSD CLI Example: … code-block:: bash salt ‘*’ status.netstats “"” def linux_netstats(): “"” linux specific netstats implementation “"” ret = {} try: with salt.utils.files.fopen("/proc/net/netstat", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: headers = [“”] for line in stats.splitlines(): if not line: continue comps = line.split() if comps[0] == headers[0]: index = len(headers) - 1 row = {} for field in range(index): if field < 1: continue else: row[headers[field]] = _number(comps[field]) rowname = headers[0].replace(":", “”) ret[rowname] = row else: headers = comps return ret def freebsd_netstats(): return bsd_netstats() def bsd_netstats(): “"” bsd specific netstats implementation “"” ret = {} for line in __salt__[“cmd.run”](“netstat -s”).splitlines(): if line.startswith(“\t\t”): continue # Skip, too detailed if not line.startswith(“\t”): key = line.split()[0].replace(":", “”) ret[key] = {} else: comps = line.split() if comps[0].isdigit(): ret[key][" ".join(comps[1:])] = comps[0] return ret def sunos_netstats(): “"” sunos specific netstats implementation “"” ret = {} for line in __salt__[“cmd.run”](“netstat -s”).splitlines(): line = line.replace("=", " = ").split() if len(line) > 6: line.pop(0) if “=” in line: if len(line) >= 3: if line[2].isdigit() or line[2][0] == "-": line[2] = _number(line[2]) ret[line[0]] = line[2] if len(line) >= 6: if line[5].isdigit() or line[5][0] == "-": line[5] = _number(line[5]) ret[line[3]] = line[5] return ret def aix_netstats(): “"” AIX specific netstats implementation “"” ret = {} fields = [] procn = None proto_name = None for line in __salt__[“cmd.run”](“netstat -s”).splitlines(): if not line: continue if not re.match(r"\s", line) and “:” in line: comps = line.split(“:”) proto_name = comps[0] ret[proto_name] = [] procn = len(ret[proto_name]) ret[proto_name].append({}) continue else: comps = line.split() comps[0] = comps[0].strip() if comps[0].isdigit(): ret[proto_name][procn][" ".join(comps[1:])] = _number(comps[0]) else: continue return ret # dict that returns a function that does the right thing per platform get_version = { "Linux": linux_netstats, "FreeBSD": bsd_netstats, "Junos": bsd_netstats, "OpenBSD": bsd_netstats, "SunOS": sunos_netstats, "AIX": aix_netstats, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def netdev(): “"” … versionchanged:: 2016.3.2 Return the network device stats for this minion … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.netdev “"” def linux_netdev(): “"” linux specific implementation of netdev “"” ret = {} try: with salt.utils.files.fopen("/proc/net/dev", “r”) as fp_: stats = salt.utils.stringutils.to_unicode(fp_.read()) except OSError: pass else: for line in stats.splitlines(): if not line: continue if line.find(“:”) < 0: continue comps = line.split() # Fix lines like eth0:9999…’ comps[0] = line.split(“:”)[0].strip() # Support lines both like eth0:999 and eth0: 9999 comps.insert(1, line.split(“:”)[1].strip().split()[0]) ret[comps[0]] = { "iface": comps[0], "rx_bytes": _number(comps[2]), "rx_compressed": _number(comps[8]), "rx_drop": _number(comps[5]), "rx_errs": _number(comps[4]), "rx_fifo": _number(comps[6]), "rx_frame": _number(comps[7]), "rx_multicast": _number(comps[9]), "rx_packets": _number(comps[3]), "tx_bytes": _number(comps[10]), "tx_carrier": _number(comps[16]), "tx_colls": _number(comps[15]), "tx_compressed": _number(comps[17]), "tx_drop": _number(comps[13]), "tx_errs": _number(comps[12]), "tx_fifo": _number(comps[14]), "tx_packets": _number(comps[11]), } return ret def freebsd_netdev(): “"” freebsd specific implementation of netdev “"” _dict_tree = lambda: collections.defaultdict(_dict_tree) ret = _dict_tree() netstat = __salt__[“cmd.run”](“netstat -i -n -4 -b -d”).splitlines() netstat += __salt__[“cmd.run”](“netstat -i -n -6 -b -d”).splitlines()[1:] header = netstat[0].split() for line in netstat[1:]: comps = line.split() for i in range(4, 13): # The columns we want ret[comps[0]][comps[2]][comps[3]][header[i]] = _number(comps[i]) return ret def sunos_netdev(): “"” sunos specific implementation of netdev “"” ret = {} ##NOTE: we cannot use hwaddr_interfaces here, so we grab both ip4 and ip6 for dev in itertools.chain( __grains__[“ip4_interfaces”].keys(), __grains__[“ip6_interfaces”].keys() ): # fetch device info netstat_ipv4 = __salt__[“cmd.run”]( "netstat -i -I {dev} -n -f inet".format(dev=dev) ).splitlines() netstat_ipv6 = __salt__[“cmd.run”]( "netstat -i -I {dev} -n -f inet6".format(dev=dev) ).splitlines() # prepare data netstat_ipv4[0] = netstat_ipv4[0].split() netstat_ipv4[1] = netstat_ipv4[1].split() netstat_ipv6[0] = netstat_ipv6[0].split() netstat_ipv6[1] = netstat_ipv6[1].split() # add data ret[dev] = {} for val in netstat_ipv4[0][:-1]: if val == "Name": continue if val in ["Address", “Net/Dest”]: ret[dev]["IPv4 {field}".format(field=val)] = val else: ret[dev][val] = _number(val) for val in netstat_ipv6[0][:-1]: if val == "Name": continue if val in ["Address", “Net/Dest”]: ret[dev]["IPv6 {field}".format(field=val)] = val else: ret[dev][val] = _number(val) return ret def aix_netdev(): “"” AIX specific implementation of netdev “"” ret = {} fields = [] procn = None for dev in itertools.chain( __grains__[“ip4_interfaces”].keys(), __grains__[“ip6_interfaces”].keys() ): # fetch device info # root@la68pp002_pub:# netstat -i -n -I en0 -f inet # Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll # en0 1500 link#3 e2.eb.32.42.84.c 10029668 0 446490 0 0 # en0 1500 172.29.128 172.29.149.95 10029668 0 446490 0 0 # root@la68pp002_pub:# netstat -i -n -I en0 -f inet6 # Name Mtu Network Address Ipkts Ierrs Opkts Oerrs Coll # en0 1500 link#3 e2.eb.32.42.84.c 10029731 0 446499 0 0 netstat_ipv4 = __salt__[“cmd.run”]( "netstat -i -n -I {dev} -f inet".format(dev=dev) ).splitlines() netstat_ipv6 = __salt__[“cmd.run”]( "netstat -i -n -I {dev} -f inet6".format(dev=dev) ).splitlines() # add data ret[dev] = [] for line in netstat_ipv4: if line.startswith(“Name”): fields = line.split() continue comps = line.split() if len(comps) < 3: raise CommandExecutionError( "Insufficent data returned by command to process '{}’".format( line ) ) if comps[2].startswith(“link”): continue procn = len(ret[dev]) ret[dev].append({}) ret[dev][procn][“ipv4”] = {} for i in range(1, len(fields)): if len(comps) > i: ret[dev][procn][“ipv4”][fields[i]] = comps[i] for line in netstat_ipv6: if line.startswith(“Name”): fields = line.split() continue comps = line.split() if len(comps) < 3: raise CommandExecutionError( "Insufficent data returned by command to process '{}’".format( line ) ) if comps[2].startswith(“link”): continue procn = len(ret[dev]) ret[dev].append({}) ret[dev][procn][“ipv6”] = {} for i in range(1, len(fields)): if len(comps) > i: ret[dev][procn][“ipv6”][fields[i]] = comps[i] return ret # dict that returns a function that does the right thing per platform get_version = { "Linux": linux_netdev, "FreeBSD": freebsd_netdev, "Junos": freebsd_netdev, "SunOS": sunos_netdev, "AIX": aix_netdev, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def w(): # pylint: disable=C0103 “"” Return a list of logged in users for this minion, using the w command CLI Example: … code-block:: bash salt ‘*’ status.w “"” def linux_w(): “"” Linux specific implementation for w “"” user_list = [] users = __salt__[“cmd.run”](“w -fh”).splitlines() for row in users: if not row: continue comps = row.split() rec = { "idle": comps[3], "jcpu": comps[4], "login": comps[2], "pcpu": comps[5], "tty": comps[1], "user": comps[0], "what": " ".join(comps[6:]), } user_list.append(rec) return user_list def bsd_w(): “"” Generic BSD implementation for w “"” user_list = [] users = __salt__[“cmd.run”](“w -h”).splitlines() for row in users: if not row: continue comps = row.split() rec = { "from": comps[2], "idle": comps[4], "login": comps[3], "tty": comps[1], "user": comps[0], "what": " ".join(comps[5:]), } user_list.append(rec) return user_list # dict that returns a function that does the right thing per platform get_version = { "Darwin": bsd_w, "FreeBSD": bsd_w, "Junos": bsd_w, "Linux": linux_w, "OpenBSD": bsd_w, } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def all_status(): “"” Return a composite of all status data and info for this minion. Warning: There is a LOT here! CLI Example: … code-block:: bash salt ‘*’ status.all_status “"” return { "cpuinfo": cpuinfo(), "cpustats": cpustats(), "diskstats": diskstats(), "diskusage": diskusage(), "loadavg": loadavg(), "meminfo": meminfo(), "netdev": netdev(), "netstats": netstats(), "uptime": uptime(), "vmstats": vmstats(), "w": w(), } def pid(sig): “"” Return the PID or an empty string if the process is running or not. Pass a signature to use to find the process via ps. Note you can pass a Python-compatible regular expression to return all pids of processes matching the regexp. … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.pid <sig> “"” my_pid = str(os.getpid()) cmd = __grains__[“ps”] output = __salt__[“cmd.run_stdout”](cmd, python_shell=True) pids = “” for line in output.splitlines(): if my_pid in line: continue if re.search(sig, line): if pids: pids += “\n” pids += line.split()[1] return pids def version(): “"” Return the system version for this minion … versionchanged:: 2016.11.4 Added support for AIX … versionchanged:: 2018.3.0 Added support for OpenBSD CLI Example: … code-block:: bash salt ‘*’ status.version “"” def linux_version(): “"” linux specific implementation of version “"” try: with salt.utils.files.fopen("/proc/version", “r”) as fp_: return salt.utils.stringutils.to_unicode(fp_.read()).strip() except OSError: return {} def bsd_version(): “"” bsd specific implementation of version “"” return __salt__[“cmd.run”](“sysctl -n kern.version”) # dict that returns a function that does the right thing per platform get_version = { "Linux": linux_version, "FreeBSD": bsd_version, "Junos": bsd_version, "OpenBSD": bsd_version, "AIX": lambda: __salt__[“cmd.run”](“oslevel -s”), } errmsg = “This method is unsupported on the current operating system!” return get_version.get(__grains__[“kernel”], lambda: errmsg)() def master(master=None, connected=True): “"” … versionadded:: 2014.7.0 Return the connection status with master. Fire an event if the connection to master is not as expected. This function is meant to be run via a scheduled job from the minion. If master_ip is an FQDN/Hostname, it must be resolvable to a valid IPv4 address. … versionchanged:: 2016.11.4 Added support for AIX CLI Example: … code-block:: bash salt ‘*’ status.master “"” master_ips = None if master: master_ips = salt.utils.network.host_to_ips(master) if not master_ips: return master_connection_status = False port = __salt__[“config.get”](“publish_port", default=4505) connected_ips = salt.utils.network.remote_port_tcp(port) # Get connection status for master for master_ip in master_ips: if master_ip in connected_ips: master_connection_status = True break # Connection to master is not as expected if master_connection_status is not connected: with salt.utils.event.get_event(“minion", opts=__opts__, listen=False) as event: if master_connection_status: event.fire_event( {"master": master}, salt.minion.master_event(type="connected”) ) else: event.fire_event( {"master": master}, salt.minion.master_event(type="disconnected”) ) return master_connection_status def ping_master(master): “"” … versionadded:: 2016.3.0 Sends ping request to the given master. Fires ‘__master_failback’ event on success. Returns bool result. CLI Example: … code-block:: bash salt ‘*’ status.ping_master localhost “"” if master is None or master == "": return False opts = copy.deepcopy(__opts__) opts[“master”] = master if “master_ip” in opts: # avoid ‘master ip changed’ warning del opts[“master_ip”] opts.update(salt.minion.prep_ip_port(opts)) try: opts.update(salt.minion.resolve_dns(opts, fallback=False)) except Exception: # pylint: disable=broad-except return False timeout = opts.get("auth_timeout", 60) load = {"cmd": “ping"} result = False with salt.channel.client.ReqChannel.factory(opts, crypt="clear”) as channel: try: payload = channel.send(load, tries=0, timeout=timeout) result = True except Exception as e: # pylint: disable=broad-except pass if result: with salt.utils.event.get_event( “minion", opts=__opts__, listen=False ) as event: event.fire_event( {"master": master}, salt.minion.master_event(type="failback”) ) return result def proxy_reconnect(proxy_name, opts=None): “"” Forces proxy minion reconnection when not alive. proxy_name The virtual name of the proxy module. opts: None Opts dictionary. Not intended for CLI usage. CLI Example: … code-block:: bash salt ‘*’ status.proxy_reconnect rest_sample “"” if not opts: opts = __opts__ if “proxy” not in opts: return False # fail proxy_keepalive_fn = proxy_name + “.alive” if proxy_keepalive_fn not in __proxy__: return False # fail chk_reboot_active_key = proxy_name + “.get_reboot_active” if chk_reboot_active_key in __proxy__ and __proxy__[chk_reboot_active_key](): # if rebooting or shutting down, don’t run proxy_reconnect # it interferes with the connection and disrupts the shutdown/reboot # especially minion_id = opts.get("proxyid", “”) or opts.get("id", “”) log.info( "%s (%s proxy) is rebooting or shutting down. Don’t probe connection.", minion_id, proxy_name, ) return True is_alive = __proxy__[proxy_keepalive_fn](opts) if not is_alive: minion_id = opts.get("proxyid", “”) or opts.get("id", “”) log.info("%s (%s proxy) is down. Restarting.", minion_id, proxy_name) __proxy__[proxy_name + “.shutdown”](opts) # safely close connection __proxy__[proxy_name + “.init”](opts) # reopen connection log.debug(“Restarted %s (%s proxy)!", minion_id, proxy_name) return True # success def time_(format="%A, %d. %B %Y %I:%M%p”): “"” … versionadded:: 2016.3.0 Return the current time on the minion, formatted based on the format parameter. Default date format: Monday, 27. July 2015 07:55AM CLI Example: … code-block:: bash salt ‘*’ status.time salt ‘*’ status.time ‘%s’ “"” dt = datetime.datetime.today() return dt.strftime(format)