| Current Path : /opt/microsoft/omsconfig/Scripts/3.x/Scripts/ |
| Current File : //opt/microsoft/omsconfig/Scripts/3.x/Scripts/nxOMSAuditdPlugin.py |
#!/usr/bin/env python
#============================================================================
# Copyright (C) Microsoft Corporation, All rights reserved.
#============================================================================
import os
import sys
import imp
import codecs
import subprocess
import platform
import re
import shutil
import traceback
protocol = imp.load_source('protocol', '../protocol.py')
nxDSCLog = imp.load_source('nxDSCLog', '../nxDSCLog.py')
LG = nxDSCLog.DSCLog
LOG_FATAL = 'FATAL'
LOG_ERROR = 'ERROR'
LOG_INFO = 'INFO'
LOG_DEBUG = 'DEBUG'
WORKSPACE_ID_TMPL_STR = "%WORKSPACE_ID%"
AGENT_ETC_ROOT = '/etc/opt/microsoft/omsagent'
AGENT_VAR_ROOT = '/var/opt/microsoft/omsagent'
AUOMS_CONF_FILE = '/etc/opt/microsoft/auoms/auoms.conf'
AUOMSCOLLECT_CONF_FILE = '/etc/opt/microsoft/auoms/auomscollect.conf'
AUOMS_OUTCONF_DIR = '/etc/opt/microsoft/auoms/outconf.d'
AUOMS_RULES_DIR = '/etc/opt/microsoft/auoms/rules.d'
AUOMS_PLUGIN_CONF = '/etc/audisp/plugins.d/auoms.conf'
SUDO_SCRIPT = '/opt/microsoft/omsconfig/Scripts/OMSAuditdPlugin.sh'
MODULE_RESOURCE_DIR = '/opt/microsoft/omsconfig/modules/nxOMSAuditdPlugin/DSCResources/MSFT_nxOMSAuditdPluginResource'
VERSION_FILE = '/opt/microsoft/omsconfig/modules/nxOMSAuditdPlugin/VERSION'
PLUGIN_LIB_RB = '/opt/microsoft/omsagent/plugin/auditd_plugin_lib.rb'
PLUGIN_FILTER_RB = '/opt/microsoft/omsagent/plugin/filter_auditd_plugin.rb'
RESOURCE_OMSAGENT_CONF = MODULE_RESOURCE_DIR + '/conf/auditd_plugin.conf'
RESOURCE_AUOMS_V1_CONF = MODULE_RESOURCE_DIR + '/conf/auoms_v1.conf'
RESOURCE_AUOMS_V2_CONF = MODULE_RESOURCE_DIR + '/conf/auoms_v2.conf'
RESOURCE_AUOMS_V3_CONF = MODULE_RESOURCE_DIR + '/conf/auoms_v3.conf'
RESOURCE_AUOMS_V4_CONF = MODULE_RESOURCE_DIR + '/conf/auoms_v4.conf'
RESOURCE_AUOMSCOLLECT_V3_CONF = MODULE_RESOURCE_DIR + '/conf/auomscollect_v3.conf'
RESOURCE_OUTPUT_V1_CONF = MODULE_RESOURCE_DIR + '/conf/output_v1.conf'
RESOURCE_OUTPUT_V2_CONF = MODULE_RESOURCE_DIR + '/conf/output_v2.conf'
RESOURCE_OUTPUT_V3_CONF = MODULE_RESOURCE_DIR + '/conf/output_v3.conf'
RESOURCE_OUTPUT_V4_CONF = MODULE_RESOURCE_DIR + '/conf/output_v4.conf'
RESOURCE_PLUGIN_LIB_RB = MODULE_RESOURCE_DIR + '/plugin/auditd_plugin_lib.rb'
RESOURCE_PLUGIN_FILTER_RB = MODULE_RESOURCE_DIR + '/plugin/filter_auditd_plugin.rb'
RESOURCE_AUDIT_RULES_V1 = MODULE_RESOURCE_DIR + '/rules/oms-security-audit-v1.rules'
RESOURCE_AUDIT_RULES_V2 = MODULE_RESOURCE_DIR + '/rules/oms-security-audit-v2.rules'
RESOURCE_SUDO_SCRIPT = MODULE_RESOURCE_DIR + '/Scripts/OMSAuditdPlugin.sh'
MIN_AUOMS_VER_FOR_V2_CONFIG = '1.2'
MIN_AUOMS_VER_FOR_V3_CONFIG = '2.0'
MIN_AUOMS_VER_FOR_V2_RULES = '2.0'
MIN_AUOMS_VER_FOR_V3_COLLECT_CONFIG = '2.0'
MIN_AUOMS_VER_FOR_V4_CONFIG = '2.1'
AUOMS_BIN = '/opt/microsoft/auoms/bin/auoms'
AUOMSCTL_BIN = '/opt/microsoft/auoms/bin/auomsctl'
AUDITD_BIN = '/sbin/auditd'
TMP_AUOMS_STATE_FILE = 'auoms.state'
TMP_RULES_FILE = 'auditd_plugin.rules'
TMP_LOADED_RULES_FILE = 'auditd_loaded.rules'
TMP_AUDIT_STATE_FILE = 'audit.state'
def Set_Marshall(WorkspaceId, Ensure):
try:
WorkspaceId = WorkspaceId
Ensure = Ensure
return Set(WorkspaceId, Ensure)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, 10)).replace("\n", "#NL#")
LogStatus("Set_End", error_msg, False)
def Test_Marshall(WorkspaceId, Ensure):
try:
WorkspaceId = WorkspaceId
Ensure = Ensure
return Test(WorkspaceId, Ensure)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
error_msg = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, 10)).replace("\n", "#NL#")
LogStatus("Set_End", error_msg, False)
def Get_Marshall(WorkspaceId, Ensure):
WorkspaceId = WorkspaceId
Ensure = Ensure
arg_names = list(locals().keys())
retval = 0
(retval, Ensure) = Get(WorkspaceId)
WorkspaceId = protocol.MI_String(WorkspaceId)
Ensure = protocol.MI_String(Ensure)
retd = {}
ld = locals()
for k in arg_names:
retd[k] = ld[k]
return retval, retd
def IsUUID(uuidStr):
try:
uuidOut = re.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', uuidStr, re.I)
except:
return False
return uuidOut is not None
# Compare versions
# Only the numeric parts are compared. For example this version "1.2.3.stuff" and "1.2.3-stuff" will be normalized to "1.2.3"
def CompareVersion(version1, version2):
def normalize(v):
parts = [re.sub(r'^\s*0*([0-9]+).*$', r'\1', x) for x in re.sub(r'^\s*([0-9]+(?:\.[0-9]+)*).*$', r'\1', v).split(".")]
return [int(x) for x in parts]
parts1 = normalize(version1)
parts2 = normalize(version2)
lendiff = len(parts1) - len(parts2)
if lendiff > 0:
parts2.extend([0] * lendiff)
elif lendiff < 0:
parts1.extend([0] * (-lendiff))
for i, p in enumerate(parts1):
if p < parts2[i]:
return -1
elif p > parts2[i]:
return 1
return 0
def IsValidWorkspaceId(WorkspaceId):
if not IsUUID(WorkspaceId):
return False
return os.path.isdir(os.path.join(AGENT_VAR_ROOT, WorkspaceId))
def GetTmpDir(WorkspaceId):
return os.path.join(AGENT_VAR_ROOT, WorkspaceId, "tmp")
def GetAgentConfDir(WorkspaceId):
return os.path.join(AGENT_ETC_ROOT, WorkspaceId, "conf/omsagent.d")
def GetAgentRunDir(WorkspaceId):
return os.path.join(AGENT_VAR_ROOT, WorkspaceId, "run")
def GetOMSAgentConfPath(WorkspaceId):
return os.path.join(GetAgentConfDir(WorkspaceId), "auditd_plugin.conf")
def GetOMSAgentPidFilePath(WorkspaceId):
return os.path.join(GetAgentRunDir(WorkspaceId), "omsagent.pid")
def GetTmpAuomsConfPath(WorkspaceId):
return os.path.join(GetTmpDir(WorkspaceId), "auoms.conf")
def GetTmpAuomscollectConfPath(WorkspaceId):
return os.path.join(GetTmpDir(WorkspaceId), "auomscollect.conf")
def GetTmpAuomsOutputConfPath(WorkspaceId):
return os.path.join(GetTmpDir(WorkspaceId), "output.conf")
def GetAuomOutputConfPath(WorkspaceId):
return os.path.join(AUOMS_OUTCONF_DIR, WorkspaceId + ".conf")
def GetTmpAuditRulesPath(WorkspaceId):
return os.path.join(GetTmpDir(WorkspaceId), TMP_RULES_FILE)
def GetTmpAuditLoadedRulesPath(WorkspaceId):
return os.path.join(GetTmpDir(WorkspaceId), TMP_LOADED_RULES_FILE)
def RunCommand(args):
proc = subprocess.Popen(args, -1, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out_data, err_data) = proc.communicate()
exit_code = proc.returncode
return (out_data.decode('utf8', 'repalce'), out_data.decode('utf8', 'repalce'), exit_code)
def GetRootProcessPid(comm):
args = []
args.append("/usr/bin/pgrep")
args.append("-x")
args.append("-U")
args.append("0")
args.append(comm)
(out_txt, err_txt, exit_code) = RunCommand(args)
if exit_code == 0:
pid = int(out_txt)
if pid <= 0:
pid = -1
return pid
return 0
def GetAuditdPid():
return GetRootProcessPid("auditd")
def GetAuomsPid():
return GetRootProcessPid("auoms")
def GetAuomsCollectPid():
return GetRootProcessPid("auomscollect")
def LogStatus(name, message, is_fatal):
has_auditd = "false"
auditd_pid = 0
auomscollect_pid = 0
auoms_pid = 0
if os.path.isfile(AUDITD_BIN):
has_auditd = "true"
auditd_pid = GetAuditdPid()
auomscollect_pid = GetAuomsCollectPid()
auoms_pid = GetAuomsPid()
dsc_version = ReadFile(VERSION_FILE)
if dsc_version is None:
dsc_version = "Unknown"
LG().Log(LOG_INFO, "STATUS:"+name+":<"+message+">:"+dsc_version+":"+has_auditd+":"+str(auditd_pid)+":"+str(auomscollect_pid)+":"+str(auoms_pid))
if is_fatal:
LG().Log(LOG_FATAL, message)
def Set(WorkspaceId, Ensure):
LogStatus("Set_Start", "Start", False)
if not IsValidWorkspaceId(WorkspaceId):
LogStatus("Set_End", "Invalid workspace id", False)
return [1]
if IsSudoScriptOutOfDate():
LG().Log(LOG_INFO, "Updating sudo script")
try:
WriteFile(SUDO_SCRIPT, ReadFile(RESOURCE_SUDO_SCRIPT))
except:
LogStatus("Set_End", "Failed to update " + SUDO_SCRIPT, True)
return [1]
auoms_version = GetPackageVersion("auoms")
if auoms_version is None:
LogStatus("Set_End", "Failed to determine auoms version", True)
# Failed to get auoms version, return fake success
return [1]
(error_msg, audit_version, auoms_state, actual_audit_rules, actual_conf, actual_collect_conf,
actual_outconf, actual_plugin_conf, loaded_audit_rules, audit_state) = GetState(WorkspaceId)
if error_msg is not None:
LogStatus("Set_End", error_msg, True)
return [1]
(error_msg, desired_auoms_state, desired_rules, desired_conf, desired_collect_conf, desired_outconf,
desired_plugin_conf) = GetDesiredState(WorkspaceId, Ensure, audit_version, auoms_state, auoms_version)
if error_msg is not None:
LogStatus("Set_End", error_msg, True)
return [1]
run_script = False
if auoms_state != desired_auoms_state:
run_script = True
LG().Log(LOG_INFO, "Actual auoms state differs from desired auoms state")
audit_enabled = GetEnabledFromAuditState(audit_state)
arg1 = ""
agent_restart_ignore_error = False
if IsTextDifferent(actual_plugin_conf, desired_plugin_conf):
run_script = True
LG().Log(LOG_INFO, "Actual Plugin conf differs from Desired plugin conf, so omsagent will be restarted")
arg1 = WorkspaceId
if desired_plugin_conf is None:
RemoveFile(GetOMSAgentConfPath(WorkspaceId))
else:
agent_restart_ignore_error = True
WriteFile(GetOMSAgentConfPath(WorkspaceId), desired_plugin_conf)
if AreFilesDifferent(PLUGIN_LIB_RB, RESOURCE_PLUGIN_LIB_RB) or AreFilesDifferent(PLUGIN_FILTER_RB, RESOURCE_PLUGIN_FILTER_RB):
run_script = True
LG().Log(LOG_INFO, "Actual Plugin RB files differ from Desired plugin RB files, so omsagent will be restarted")
arg1 = WorkspaceId
try:
shutil.copyfile(RESOURCE_PLUGIN_LIB_RB, PLUGIN_LIB_RB)
except:
LogStatus("Set_End", "Failed to copy " + src + " to " + dest + ": " + str(sys.exc_info()[1]), True)
return [1]
try:
shutil.copyfile(RESOURCE_PLUGIN_FILTER_RB, PLUGIN_FILTER_RB)
except:
LogStatus("Set_End", "Failed to copy " + src + " to " + dest + ": " + str(sys.exc_info()[1]), True)
return [1]
if arg1 == "" and OmsAgentNeedsRestart(WorkspaceId):
run_script = True
LG().Log(LOG_INFO, "Plugin conf or RB files have changed since omsagent start, omsagent will be restarted")
arg1 = WorkspaceId
rules_file_path = ""
if IsTextDifferent(actual_audit_rules, desired_rules):
run_script = True
if desired_rules is None:
LG().Log(LOG_INFO, "Auditd rules will be removed")
rules_file_path = "remove"
else:
LG().Log(LOG_INFO, "Actual auditd rules differ from desired auditd rules")
rules_file_path = GetTmpAuditRulesPath(WorkspaceId)
WriteFile(rules_file_path, desired_rules)
conf_path = ""
if IsTextDifferent(actual_conf, desired_conf):
if desired_conf is not None:
run_script = True
LG().Log(LOG_INFO, "Actual auoms conf differs from desired conf")
conf_path = GetTmpAuomsConfPath(WorkspaceId)
WriteFile(conf_path, desired_conf)
collect_conf_path = ""
if IsTextDifferent(actual_collect_conf, desired_collect_conf):
if desired_collect_conf is not None:
run_script = True
LG().Log(LOG_INFO, "Actual auomscollect conf differs from desired conf")
collect_conf_path = GetTmpAuomscollectConfPath(WorkspaceId)
WriteFile(collect_conf_path, desired_collect_conf)
outconf_name = ""
outconf_path = ""
if IsTextDifferent(actual_outconf, desired_outconf):
run_script = True
outconf_name = WorkspaceId + ".conf"
if desired_outconf is None:
LG().Log(LOG_INFO, "auoms output conf will be removed")
outconf_path = "remove"
else:
LG().Log(LOG_INFO, "Actual auoms output conf differs from desired output conf")
outconf_path = GetTmpAuomsOutputConfPath(WorkspaceId)
WriteFile(outconf_path, desired_outconf)
rules_diff = ""
rules_diff_path = ""
# Only do loaded rules check if auoms version < 2.0
if CompareVersion(auoms_version, '2.0') < 0:
# On some systems the auditd reload will cause rule load as well
# So, to prevent trying to load rules twice (and getting an error as a result)
# Only check loaded rules if rules file already contains the rules
# This may cause a 15 minute delay between when plugin is enabled
# and when rules are actually loaded
if actual_audit_rules is not None:
rules_diff = DiffAuditRules(desired_rules, loaded_audit_rules)
if rules_diff is not None and audit_enabled != "2":
run_script = True
LG().Log(LOG_INFO, "Not all desired auditd rules are loaded")
LG().Log(LOG_INFO, "Missing rules are: \n" + rules_diff)
rules_diff_path = GetTmpAuditLoadedRulesPath(WorkspaceId)
WriteFile(rules_diff_path, rules_diff)
script_failed = False
is_fatal = False
status_message = "Success"
exit_code = 0
if run_script:
# Run script that will restart omsagent and enable+start/disable+stop auoms
args = []
args.append("/usr/bin/sudo")
args.append(SUDO_SCRIPT)
args.append("set")
args.append(arg1)
if auoms_state != desired_auoms_state:
args.append(desired_auoms_state)
else:
args.append("")
args.append(rules_file_path)
args.append(outconf_name)
args.append(outconf_path)
args.append(rules_diff_path)
args.append(conf_path)
args.append(collect_conf_path)
(out_txt, err_txt, exit_code) = RunCommand(args)
if exit_code != 0:
script_failed = True
is_fatal = True
LG().Log(LOG_ERROR, "Command failed: '" + "' '".join(args) + "'")
LG().Log(LOG_INFO, "stdout: " + out_txt)
LG().Log(LOG_INFO, "stderr: " + err_txt)
if exit_code == 2:
status_message = "Failed to restart omsagent"
if agent_restart_ignore_error:
is_fatal = False
elif exit_code == 3:
if desired_auoms_state == "running":
status_message = "Failed to enable OMS Auditd Plugin"
else:
status_message = "Failed to disable OMS Auditd Plugin"
elif exit_code == 4:
status_message = "Failed to change OMS audit rules"
elif exit_code == 5:
status_message = "Failed to change auoms output config"
elif exit_code == 6:
if desired_auoms_state == "running":
status_message = "OMS Auditd Plugin failed to start"
else:
status_message = "OMS Auditd Plugin failed to stop"
elif exit_code == 7:
status_message = "Failed to change loaded OMS audit rules"
elif exit_code == 8:
status_message = "Failed to trigger auditd service reload"
elif exit_code == 9:
status_message = "Failed to change auoms config"
elif exit_code == 10:
status_message = "Failed to change auomscollect config"
elif exit_code == 11:
status_message = "Failed to trigger auoms restart"
elif exit_code == 12:
status_message = "Audit rules immutable (-e 2): reboot required so that desired rules can be loaded"
elif exit_code == 13:
status_message = "Failed to enable auoms service"
else:
status_message = "Unknown error while trying to apply desired OMS Auditd Plugin changes: " + err_txt
RemoveFile(GetTmpAuomsConfPath(WorkspaceId))
RemoveFile(GetTmpAuomsOutputConfPath(WorkspaceId))
RemoveFile(GetTmpAuditRulesPath(WorkspaceId))
RemoveFile(GetTmpAuditLoadedRulesPath(WorkspaceId))
if not script_failed and Ensure == "Present":
# Only report running state errors if script did not fail
# If script failed, we don't want to last LOG_FATAL message to get overridden by running state error
if os.path.isfile(AUOMSCTL_BIN):
if os.path.isfile(AUDITD_BIN):
if GetAuditdPid() <= 0:
LogStatus("Set_End", "Auditd service needs to be started", True)
return [0]
if GetAuomsCollectPid() <= 0:
LogStatus("Set_End", "Auditd service is running, but auomscollect is not running", True)
return [0]
else:
if GetAuomsCollectPid() <= 0:
LogStatus("Set_End", "Auoms service is running, but auomscollect is not running", True)
return [0]
else:
if not GetAuditdPid() <= 0:
LogStatus("Set_End", "Auditd service needs to be started", True)
return [0]
if not GetAuomsPid() <= 0:
LogStatus("Set_End", "Auditd service is running, but auoms is not running", True)
return [0]
if rules_diff is not None and audit_enabled == "2":
LogStatus("Set_End", "Audit rules immutable (-e 2): reboot required so that desired rules can be loaded", True)
return [0]
LogStatus("Set_End", status_message, is_fatal)
return [exit_code]
def Test(WorkspaceId, DesiredEnsure):
LogStatus("Test_Start", "Start", False)
if not IsValidWorkspaceId(WorkspaceId):
LogStatus("Test_End", "Invalid workspace id", True)
# Failed to determine state, return fake success
return [0]
if not os.path.isfile(AUOMS_BIN):
if DesiredEnsure == "Present":
LogStatus("Test_End", "OMS Auditd Plugin needs to be installed", True)
return [0]
# Only auoms 2.0 has auomsctl. Only check for auditd if auoms version < 2.0.
if not os.path.isfile(AUOMSCTL_BIN):
if not os.path.isfile(AUDITD_BIN):
if DesiredEnsure == "Present":
LogStatus("Test_End", "Auditd needs to be installed", True)
return [0]
if IsSudoScriptOutOfDate():
LogStatus("Test_End", "sudo script needs to be updated", False)
return [-1]
if OmsAgentNeedsRestart(WorkspaceId):
LogStatus("Test_End", "omsagent needs restart", False)
return [-1]
auoms_version = GetPackageVersion("auoms")
if auoms_version is None:
LogStatus("Test_End", "Failed to determine auoms version", True)
# Failed to get auoms version, return fake success
return [0]
(error_msg, audit_version, auoms_state, actual_audit_rules, actual_conf, actual_collect_conf,
actual_outconf, actual_plugin_conf, loaded_audit_rules, audit_state) = GetState(WorkspaceId)
if error_msg is not None:
LogStatus("Set_End", error_msg, True)
return [0]
(error_msg, desired_auoms_state, desired_rules, desired_conf, desired_collect_conf, desired_outconf,
desired_plugin_conf) = GetDesiredState(WorkspaceId, DesiredEnsure, audit_version, auoms_state, auoms_version)
if error_msg is not None:
LogStatus("Set_End", error_msg, True)
return [0]
if desired_auoms_state != auoms_state:
LogStatus("Test_End", "Auoms State does not match desired state", False)
LG().Log(LOG_INFO, "Auoms State does not match desired state")
return [-1]
if IsTextDifferent(desired_rules, actual_audit_rules):
LogStatus("Test_End", "Audit Rules do not match desired rules", False)
return [-1]
if desired_conf is not None and IsTextDifferent(desired_conf, actual_conf):
LogStatus("Test_End", "auoms conf does not match desired conf", False)
return [-1]
if desired_collect_conf is not None and IsTextDifferent(desired_collect_conf, actual_collect_conf):
LogStatus("Test_End", "auomscollect conf does not match desired conf", False)
return [-1]
if desired_outconf is not None and IsTextDifferent(desired_outconf, actual_outconf):
LogStatus("Test_End", "auoms outconf does not match desired conf", False)
return [-1]
if IsTextDifferent(desired_plugin_conf, actual_plugin_conf):
LogStatus("Test_End", "Plugin Conf does not match desired conf", False)
return [-1]
if AreFilesDifferent(PLUGIN_LIB_RB, RESOURCE_PLUGIN_LIB_RB) or AreFilesDifferent(PLUGIN_FILTER_RB, RESOURCE_PLUGIN_FILTER_RB):
LogStatus("Test_End", "Plugin RB files do not match desired RB files", False)
return [-1]
rules_diff = DiffAuditRules(desired_rules, loaded_audit_rules)
if rules_diff is not None:
LogStatus("Test_End", "One or more desired audit rules are not loaded", False)
return [-1]
# If something isn't running that should be running, trigger a set so that it can report the actual issue
if DesiredEnsure == "Present":
if os.path.isfile(AUOMSCTL_BIN):
if os.path.isfile(AUDITD_BIN):
if GetAuditdPid() <= 0:
LogStatus("Test_End", "Auditd not running", False)
return [-1]
if GetAuomsPid() <= 0 or GetAuomsCollectPid() <= 0:
LogStatus("Test_End", "Auoms (or auomscollect) not running", False)
return [-1]
else:
if GetAuditdPid() <= 0 or GetAuomsPid() <= 0:
LogStatus("Test_End", "Auditd or auoms not running", False)
return [-1]
if GetEnabledFromAuditState(audit_state) == "2":
LogStatus("Test_End", "Audit rules immutable (-e 2): reboot required so that desired rules can be loaded", True)
return [0]
LogStatus("Test_End", "Set Operation Not Needed", False)
return [0]
def Get(WorkspaceId):
# Get not supported
return (-1, "Absent")
def IsSudoScriptOutOfDate():
actual = ReadFile(SUDO_SCRIPT)
desired = ReadFile(RESOURCE_SUDO_SCRIPT)
return IsTextDifferent(actual, desired)
# Is TargetPath newer than ReferencePath
def IsNewer(ReferencePath, TargetPath):
if os.path.exists(ReferencePath) and os.path.exists(TargetPath):
ref_stat = os.stat(ReferencePath)
target_stat = os.stat(TargetPath)
if target_stat.st_mtime > ref_stat.st_mtime:
return True
return False
def OmsAgentNeedsRestart(WorkspaceId):
IsConfNewer = IsNewer(GetOMSAgentPidFilePath(WorkspaceId), GetOMSAgentConfPath(WorkspaceId))
IsLibNewer = IsNewer(GetOMSAgentPidFilePath(WorkspaceId), PLUGIN_LIB_RB)
IsFilterNewer = IsNewer(GetOMSAgentPidFilePath(WorkspaceId), PLUGIN_FILTER_RB)
if IsConfNewer or IsLibNewer or IsFilterNewer:
return True
return False
def GetOtherOutconfCounts(WorkspaceId):
tcount = 0
wcount = 0
for name in os.listdir(AUOMS_OUTCONF_DIR):
if name.endswith(".conf"):
if name != WorkspaceId + ".conf":
tcount += 1
if IsValidWorkspaceId(name[:-5]):
wcount += 1
return (tcount, wcount)
def HasExecveat():
args = []
args.append("/usr/bin/ausyscall")
args.append("execveat")
(out_txt, err_txt, exit_code) = RunCommand(args)
if exit_code == 0:
return True
else:
return False
def GetDesiredAuditRules(AuditVersion, AuomsVersion):
if CompareVersion(AuomsVersion, MIN_AUOMS_VER_FOR_V2_RULES) >= 0:
desired_rules = ReadFile(RESOURCE_AUDIT_RULES_V2)
return desired_rules
if AuditVersion == 1:
desired_rules = ReadFile(RESOURCE_AUDIT_RULES_V1)
else:
desired_rules = ReadFile(RESOURCE_AUDIT_RULES_V2)
if not HasExecveat():
# This assumes that the rule is "-S execve,execveat"
desired_rules = re.sub(r",execveat", r"", desired_rules)
if platform.machine() != "x86_64" and desired_rules != None:
lines = desired_rules.splitlines()
new_lines = []
for line in lines:
if not 'b64' in line:
new_lines.append(line)
desired_rules = "\n".join(lines) + "\n"
return desired_rules
def GetPackageVersion(PackageName):
deb_ver = None
rpm_ver = None
if os.path.isfile("/usr/bin/dpkg-query"):
args = []
args.append("/usr/bin/dpkg-query")
args.append("-f")
args.append("${Version}")
args.append("-W")
args.append(PackageName)
(out_txt, err_txt, exit_code) = RunCommand(args)
if exit_code == 0:
# Some packages have "1:" at rhe beginning of the version
# So remove it.
deb_ver = re.sub(r'^[0-9]+:', r'', out_txt)
else:
LG().Log(LOG_ERROR, "Command failed: " + " ".join(args))
LG().Log(LOG_INFO, "stdout: " + out_txt)
LG().Log(LOG_INFO, "stderr: " + err_txt)
if os.path.isfile("/bin/rpm"):
args = []
args.append("/bin/rpm")
args.append("-q")
args.append("-a")
args.append("--qf")
args.append("%{VERSION}.%{RELEASE}")
args.append(PackageName)
(out_txt, err_txt, exit_code) = RunCommand(args)
if exit_code == 0:
rpm_ver = out_txt
else:
LG().Log(LOG_ERROR, "Command failed: " + " ".join(args))
LG().Log(LOG_INFO, "stdout: " + out_txt)
LG().Log(LOG_INFO, "stderr: " + err_txt)
if deb_ver is None:
return rpm_ver
return deb_ver
def GetDesiredAuomsConf(AuomsVersion):
conf_path = RESOURCE_AUOMS_V1_CONF
outconf_path = RESOURCE_OUTPUT_V1_CONF
collectconf_path = None
if CompareVersion(AuomsVersion, MIN_AUOMS_VER_FOR_V4_CONFIG) >= 0:
conf_path = RESOURCE_AUOMS_V4_CONF
outconf_path = RESOURCE_OUTPUT_V4_CONF
collectconf_path = RESOURCE_AUOMSCOLLECT_V3_CONF
elif CompareVersion(AuomsVersion, MIN_AUOMS_VER_FOR_V3_CONFIG) >= 0:
conf_path = RESOURCE_AUOMS_V3_CONF
outconf_path = RESOURCE_OUTPUT_V3_CONF
collectconf_path = RESOURCE_AUOMSCOLLECT_V3_CONF
elif CompareVersion(AuomsVersion, MIN_AUOMS_VER_FOR_V2_CONFIG) >= 0:
conf_path = RESOURCE_AUOMS_V2_CONF
outconf_path = RESOURCE_OUTPUT_V2_CONF
conf_txt = ReadFile(conf_path)
outconf_txt = ReadFile(outconf_path)
collectconf_txt = None
if collectconf_path is not None:
collectconf_txt = ReadFile(collectconf_path)
return (conf_txt, outconf_txt, collectconf_txt)
def GetDesiredState(WorkspaceId, Ensure, AuditVersion, CurrentAuomsState, AuomsVersion):
desired_auoms_state = "disabled"
desired_rules = None
desired_conf = None
desired_outconf = None
desired_plugin_conf = None
desired_collect_conf = None
(other_conf_count, other_ws_conf_count) = GetOtherOutconfCounts(WorkspaceId)
if Ensure == "Present":
if os.path.isfile(AUOMSCTL_BIN):
desired_auoms_state = "running"
else:
desired_auoms_state = "enabled"
desired_rules = GetDesiredAuditRules(AuditVersion, AuomsVersion)
(desired_conf, desired_outconf, desired_collect_conf) = GetDesiredAuomsConf(AuomsVersion)
desired_plugin_conf = ReadFile(RESOURCE_OMSAGENT_CONF)
if desired_rules is None:
return ("Failed to determine desired audit rules", None, None, None, None, None, None)
if desired_conf is None:
return ("Failed to determine desired auoms conf", None, None, None, None, None, None)
if desired_outconf is None:
return ("Failed to determine desired output conf", None, None, None, None, None, None)
if desired_plugin_conf is None:
return ("Failed to determine desired plugin conf", None, None, None, None, None, None)
if CompareVersion(AuomsVersion, MIN_AUOMS_VER_FOR_V3_COLLECT_CONFIG) >= 0:
if desired_collect_conf is None:
return ("Failed to determine desired auomscollect conf", None, None, None, None, None, None)
desired_outconf = desired_outconf.replace(WORKSPACE_ID_TMPL_STR, WorkspaceId)
desired_plugin_conf = desired_plugin_conf.replace(WORKSPACE_ID_TMPL_STR, WorkspaceId)
if other_conf_count > 0 and other_conf_count > other_ws_conf_count:
# There are non-OMS outconf present. This likely means AzSecPack is present
# So, don't touch the auoms conf file.
desired_conf = ReadFile(AUOMS_CONF_FILE)
else:
if other_conf_count > 0:
if other_ws_conf_count > 0:
# There are other workspace outconfs present, so the desired state is "running"
if os.path.isfile(AUOMSCTL_BIN):
desired_auoms_state = "running"
else:
desired_auoms_state = "enabled"
else:
# If the only other outconfs are non-OMS, then we don't
# want to change the auoms plugin state
desired_auoms_state = CurrentAuomsState
# If there are any other workspaces with auoms outconf files, then
# we don't want to remove the audit rules
if other_ws_conf_count > 0:
desired_rules = GetDesiredAuditRules(AuditVersion, AuomsVersion)
if desired_rules is None:
return ("Failed to determine desired audit rules", None, None, None, None, None, None)
return (None, desired_auoms_state, desired_rules, desired_conf, desired_collect_conf, desired_outconf, desired_plugin_conf)
def GetState(WorkspaceId):
args = []
args.append("/usr/bin/sudo")
args.append(SUDO_SCRIPT)
args.append("get")
args.append(GetTmpDir(WorkspaceId))
(out_txt, err_txt, exit_code) = RunCommand(args)
if exit_code != 0:
LG().Log(LOG_ERROR, "Command failed: " + " ".join(args))
LG().Log(LOG_INFO, "stdout: " + out_txt)
LG().Log(LOG_INFO, "stderr: " + err_txt)
error_msg = ""
if exit_code == 2:
error_msg = "Could not determine auditd version: Perhaps it isn't installed"
elif exit_code == 3:
error_msg = "Failed to obtain auoms plugin state"
elif exit_code == 4:
error_msg = "Failed to obtain current state of oms audit rules"
elif exit_code == 5:
error_msg = "Failed to obtain current state of loaded audit rules"
else:
error_msg = "Unknown error while trying to determine OMSAuditdPlugin state: " + err_txt
return (error_msg, None, None, None, None, None, None, None, None, None)
audit_version = 2
parts = out_txt.strip().split(".")
if len(parts) >= 2:
if parts[0] == "1" and int(parts[1]) < 8:
audit_version = 1
elif len(parts) == 1:
audit_version = int(parts[0])
else:
return ("Could not determine auditd version: Invalid version string returned from 'OMSAuditdPlugin.sh get'", None, None, None, None, None, None, None, None, None)
auoms_state_file = os.path.join(GetTmpDir(WorkspaceId), TMP_AUOMS_STATE_FILE)
rules_file = os.path.join(GetTmpDir(WorkspaceId), TMP_RULES_FILE)
loaded_rules_file = os.path.join(GetTmpDir(WorkspaceId), TMP_LOADED_RULES_FILE)
audit_state_file = os.path.join(GetTmpDir(WorkspaceId), TMP_AUDIT_STATE_FILE)
auoms_state = ReadFile(auoms_state_file)
if auoms_state is not None:
auoms_state = auoms_state.strip()
audit_state = ReadFile(audit_state_file)
audit_rules = ReadFile(rules_file)
loaded_audit_rules = ReadFile(loaded_rules_file)
RemoveFile(auoms_state_file)
RemoveFile(rules_file)
RemoveFile(loaded_rules_file)
RemoveFile(audit_state_file)
if audit_rules is not None and audit_rules.strip() == "":
audit_rules = None
actual_conf = ReadFile(AUOMS_CONF_FILE)
actual_collect_conf = ReadFile(AUOMSCOLLECT_CONF_FILE)
actual_outconf = ReadFile(GetAuomOutputConfPath(WorkspaceId))
actual_plugin_conf = ReadFile(GetOMSAgentConfPath(WorkspaceId))
return (None, audit_version, auoms_state, audit_rules, actual_conf, actual_collect_conf, actual_outconf, actual_plugin_conf, loaded_audit_rules, audit_state)
def CanonicalizeAuditRule(txt):
rule = txt.strip()
if re.match("^\s*#", rule):
return None
if rule.strip() == "":
return None
if rule.find("LIST_RULES") == 0:
rule = re.sub(r"\s+key=\S+", "", rule)
if rule.find("perm=") > -1:
rule = re.sub(r"LIST_RULES:\s+exit,always (?:watch|dir)=(\S+)(?: \(0x.\))? perm=(\S+)", r"-w \1 -p \2", rule)
else:
rule = re.sub(r"LIST_RULES:\s+exit,always", r"-a always,exit", rule)
rule = re.sub(r"arch=1073741827 \(0x40000003\)", r"-F arch=b32", rule)
rule = re.sub(r"arch=3221225534 \(0xc000003e\)", r"-F arch=b64", rule)
rule = re.sub(r"syscall=(\S+)", r"-S \1", rule)
else:
rule = re.sub(r"\s+-k\s+\S+", "", rule)
rule = re.sub(r"\s+key=\S+", "", rule)
rule = re.sub(r"(-w\s+(?:/[^/ ]+)+)/?", r"\1", rule)
rule = re.sub("arch=i386", "arch=b32", rule)
rule = re.sub("arch=x86_64", "arch=b64", rule)
rule = re.sub("exit,always", "always,exit", rule)
rule = " ".join(rule.split())
return rule
def DiffAuditRules(DesiredRules, LoadedRules):
if LoadedRules is None or DesiredRules is None:
return None
lr = LoadedRules.split("\n")
lrm = {}
rules = []
for r in lr:
cr = CanonicalizeAuditRule(r)
if cr is not None:
lrm[cr] = 1
dr = DesiredRules.split("\n")
drm = {}
for r in dr:
cr = CanonicalizeAuditRule(r)
if cr is not None and cr not in lrm:
rules.append(r)
if len(rules) == 0:
return None
return "\n".join(rules)
def GetEnabledFromAuditState(AuditState):
for line in AuditState.split("\n"):
m = re.match(r"^.*enabled[ =]([012])", line)
if m:
return m.group(1)
return "-1"
def ReadFile(Path):
if not os.path.isfile(Path):
return None
with codecs.open(Path, 'r', 'utf8') as f:
return f.read()
def WriteFile(Path, Text):
with codecs.open(Path, 'w', 'utf8') as f:
return f.write(Text)
def RemoveFile(Path):
if os.path.isfile(Path):
os.remove(Path)
def IsTextDifferent(Text1, Text2):
if Text1 is None and not Text2 is None:
return True
if not Text1 is None and Text2 is None:
return True
if Text1 is None and Text2 is None:
return False
txt1 = "\n".join(Text1.rstrip().splitlines())
txt2 = "\n".join(Text2.rstrip().splitlines())
return txt1 != txt2
def AreFilesDifferent(Path1, Path2):
Text1 = ReadFile(Path1)
Text2 = ReadFile(Path2)
return IsTextDifferent(Text1, Text2)