class ConfigureMACPolicy(Rule): """The ConfigureMACPolicy class configures either selinux or apparmor depending on the os platform. @change: Derek Walker - created two config items, one for enable/disable, and another for whether the user wants to use permissive or enforcing """ def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 107 self.rulename = 'ConfigureMACPolicy' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.guidance = ['NSA(2.1.1.6)(2.4.2)', 'CCE-3977-6', 'CCE-3999-0', 'CCE-3624-4', 'CIS 1.7'] self.applicable = {'type': 'white', 'family': ['linux']} datatype = "bool" key = "CONFIGUREMAC" instructions = "To prevent the configuration of a mandatory " + \ "access control policy, set the value of CONFIGUREMAC to " + \ "False. Note: The 'mandatory access control' is either SELinux " + \ "or AppArmor, depending on what is available to your current system." default = True self.ConfigureMAC = self.initCi(datatype, key, instructions, default) datatype2 = "string" key2 = "MODE" default2 = "permissive" instructions2 = "Valid modes for SELinux are: permissive or " + \ "enforcing\nValid modes for AppArmor are: complain or enforce" self.modeci = self.initCi(datatype2, key2, instructions2, default2) def report(self): """ :return: """ self.selinux = False self.apparmor = False self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.sh = ServiceHelper(self.environ, self.logger) selinux_packages = ["selinux", "libselinux", "selinux-basics"] apparmor_packages = ["apparmor"] self.mac_package = "" # discover whether this system can use - or is using - selinux # or if it should use apparmor (selinux takes precedence) for p in selinux_packages: if self.ph.check(p): self.selinux = True self.mac_package = p break if not self.selinux: for p in apparmor_packages: if self.ph.check(p): self.apparmor = True self.mac_package = p break if not bool(self.selinux or self.apparmor): for p in selinux_packages: if self.ph.checkAvailable(p): self.selinux = True self.mac_package = p break if not self.selinux: for p in apparmor_packages: if self.ph.checkAvailable(p): self.apparmor = True self.mac_package = p break self.compliant = True self.detailedresults = "" self.mode = str(self.modeci.getcurrvalue()) try: if self.selinux: if not self.reportSelinux(): self.compliant = False elif self.apparmor: if not self.reportApparmor(): self.compliant = False else: self.detailedresults += "\nCould not find either selinux or apparmor installed and nother appears to be available to this system!" self.compliant = False except (KeyboardInterrupt, SystemExit): raise except: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def set_selinux_conf_path(self): """ :return: """ # discover correct location of selinux config file self.selinux_config_file = "/etc/sysconfig/selinux" selinux_config_files = ["/etc/selinux/config", "/etc/sysconfig/selinux"] for p in selinux_config_files: if os.path.exists(p): self.selinux_config_file = p break def reportSelinux(self): """ :return: """ compliant = True conf_option_dict = {"selinux\s+status:\s+enabled": "status", "current\s+mode:\s+" + self.mode: "mode"} # check if selinux is installed if not self.ph.check(self.mac_package): compliant = False self.detailedresults += "\nSELinux is not installed" # check sestatus for current configuration of selinux self.ch.executeCommand("/usr/sbin/sestatus") output = self.ch.getOutput() for co in conf_option_dict: rco = re.compile(co, re.I) if not list(filter(rco.match, output)): compliant = False self.detailedresults += "\nSELinux " + conf_option_dict[co] + " is not configured properly" self.set_selinux_conf_path() # check selinux config file for correct configuration so setting is # persistent after reboot selinux_tmp_file = self.selinux_config_file + ".stonixtmp" selinux_config_dict = {"SELINUX": self.mode} self.selinux_config_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.selinux_config_file, selinux_tmp_file, selinux_config_dict, "present", "closedeq") self.selinux_config_editor.report() if self.selinux_config_editor.fixables: compliant = False self.detailedresults += "\nFollowing option(s) not configured correctly in " + self.selinux_config_file + " :\n" + "\n".join(self.selinux_config_editor.fixables) return compliant def reportApparmor(self): """ :return: """ compliant = True aa_enabled = "Yes" # check if apparmor is installed if not self.ph.check(self.mac_package): compliant = False self.detailedresults += "\nApparmor is not installed" # check if apparmor is enabled self.ch.executeCommand("/usr/bin/aa-enabled") output = self.ch.getOutputString() if not re.search(aa_enabled, output): compliant = False self.detailedresults += "\nApparmor is not enabled" # check if boot configuration for apparmor is correct f = open("/etc/default/grub", "r") contents = f.readlines() f.close() for line in contents: if re.search("GRUB_CMDLINE_LINUX_DEFAULT=", line): if not re.search("apparmor=1", line): compliant = False self.detailedresults += "\nApparmor not enabled in boot config" elif not re.search("security=apparmor", line): compliant = False self.detailedresults += "\nApparmor not enabled in boot config" break return compliant def fix(self): """ :return: """ self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 try: if self.selinux: if not self.fixSelinux(): self.rulesuccess = False elif self.apparmor: if not self.fixApparmor(): self.rulesuccess = False else: self.rulesuccess = False self.detailedresults += "\nNeither SELinux nor Apparmor appears to be available to this system!" except (KeyboardInterrupt, SystemExit): raise except: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixSelinux(self): """ :return: """ success = True self.iditerator = 0 activate_utility = "/usr/sbin/selinux-activate" # install selinux package if it is not installed if not self.ph.check(self.mac_package): if not self.ph.install(self.mac_package): success = False self.detailedresults += "\nFailed to install selinux package" else: # if we just installed selinux, then we need to discover the correct conf path self.set_selinux_conf_path() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype":"pkghelper", "pkgname":self.mac_package} self.statechglogger.recordchgevent(myid, event) if os.path.exists(activate_utility): self.ch.executeCommand(activate_utility) output = self.ch.getOutputString() if re.search("need to reboot", output, re.I): self.detailedresults += "\nSElinux has been configured, but you will need to reboot before selinux can become active. This rule will not report compliant until this is done." # set enforcement mode for selinux self.ch.executeCommand("/usr/sbin/setenforce " + self.mode) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to set selinux mode to: " + self.mode self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.selinux_config_editor.setEventID(myid) # configure the selinux config file for persistence through reboot if not self.selinux_config_editor.fix(): success = False self.detailedresults += "\nFailed to fix " + self.selinux_config_file elif not self.selinux_config_editor.commit(): success = False self.detailedresults += "\nFailed to fix " + self.selinux_config_file return success def fixApparmor(self): """ :return: """ success = True profiles_dir = "/etc/apparmor.d/" valid_modes = ["complain", "enforce"] grub_file = "/etc/default/grub" tmp_grub_file = grub_file + ".stonixtmp" # install apparmor package if not installed if not self.ph.check(self.mac_package): if not self.ph.install(self.mac_package): success = False self.detailedresults += "\nFailed to install apparmor package" else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype":"pkghelper", "pkgname":self.mac_package} self.statechglogger.recordchgevent(myid, event) if not self.ph.check("apparmor-profiles"): self.ph.install("apparmor-profiles") if not self.ph.check("apparmor-utils"): self.ph.install("apparmor-utils") # set apparmor enforcement mode if self.mode.lower() in ["complain", "permissive"]: self.ch.executeCommand("/usr/sbin/aa-complain " + profiles_dir + "*") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to set apparmor profiles to complain mode" elif self.mode.lower() in ["enforce", "enforcing"]: if os.path.exists(profiles_dir): self.ch.executeCommand("/usr/sbin/aa-enforce " + profiles_dir + "*") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to set apparmor profiles to enforce mode" else: self.logger.log(LogPriority.DEBUG, "apparmor profiles directory does not exist") success = False self.detailedresults += "\nFailed to set apparmor mode to: " + self.mode else: success = False self.detailedresults += "\nPlease specify one of the following options in the MODE field:\n" + "\n".join(valid_modes) # correct apparmor boot config # (can't use kveditor because it can't handle appending to a config line) if os.path.exists(grub_file): f = open(grub_file, "r") contents = f.readlines() f.close() for n, i in enumerate(contents): if re.search("GRUB_CMDLINE_LINUX_DEFAULT=", i): contents[n] = i.strip()[:-1]+' apparmor=1 security=apparmor"\n' tf = open(tmp_grub_file, "w") tf.writelines(contents) tf.close() self.iditerator += 1 event = {"eventtype":"conf", "filepath":grub_file} myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(grub_file, tmp_grub_file, myid) os.rename(tmp_grub_file, grub_file) # run update-grub to apply the new grub config self.ch.executeCommand("/usr/sbin/update-grub") return success
class ConfigureSystemAuthentication(Rule): """ Configure system authentication and password settings in accordance with rhel 7 stig requirements """ def __init__(self, config, environ, logger, statechglogger): """ :param config: :param environ: :param logger: :param statechglogger: """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 57 self.rulename = "ConfigureSystemAuthentication" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.applicable = {'type': 'white', 'family': 'linux'} datatype = "bool" key = "CONFIGSYSAUTH" instructions = "To disable this rule, set the value of " + \ "CONFIGSYSAUTH to False." default = True self.ci1 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "PASSWORDREQ" instructions = "To not configure password requirements, set " + \ "PASSWORDREQ to False. This configuration item will configure " + \ "PAM's password requirements when changing to a new password." default = True self.ci2 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "PASSWORDFAIL" instructions = "To not configure password fail locking, set " + \ "PASSWORDFAIL to False. This configuration item will " + \ "configure PAM's failed login attempts mechanism using either " + \ "faillock or tally2." default = True self.ci3 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "PWHASHING" instructions = "To not set the hashing algorithm, set " + \ "PWHASHING to False. This configuration item will configure " + \ "libuser and/or login.defs, which specifies the hashing " + \ "algorithm to use." default = True self.ci4 = self.initCi(datatype, key, instructions, default) self.guidance = ["NSA 2.3.3.1,", "NSA 2.3.3.2"] self.created = False self.localize() def localize(self): """ set up session variables based on system platform and version """ myos = self.environ.getostype().lower() if re.search("suse", myos): self.password = PASSWORD_ZYPPER self.auth = AUTH_ZYPPER self.acct = ACCOUNT_ZYPPER elif re.search("debian|ubuntu", myos): self.password = PASSWORD_APT self.auth = AUTH_APT self.acct = ACCOUNT_APT else: self.password = PASSWORD_YUM self.auth = AUTH_YUM self.acct = ACCOUNT_YUM self.session = SESSION_YUM def report(self): """ ConfigureSystemAuthentication() report method to report if system is compliant with authentication and password settings @author: Derek Walker :return: self.compliant :rtype: bool """ self.compliant = True self.detailedresults = "" self.ci2comp, self.ci3comp, self.ci4comp = True, True, True try: if not self.reportLinux(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): """ ConfigureSystemAuthentication.fix() method to fix the system to be compliant with authentication and password settings @author: Derek Walker :return: self.rulesuccess :rtype: bool """ self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 try: if not self.ci1.getcurrvalue(): return self.rulesuccess # delete past state change records from previous fix eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not self.fixLinux(): self.rulesuccess = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def reportLinux(self): """ Linux-specific submethod for config reporting @author: Derek Walker :return: compliant :rtype: bool """ self.logindefs = "/etc/login.defs" debug = "" compliant = True self.editor1, self.editor2 = "", "" self.pwqeditor = "" self.usingpwquality, self.usingcracklib = False, False self.usingpamtally2, self.usingpamfail = False, False self.created1, self.created2 = False, False self.libuserfile = "/etc/libuser.conf" self.ph = Pkghelper(self.logger, self.environ) self.determine_pwreqs_mechanism() # set pam password and authentication file paths pampassfiles = ["/etc/pam.d/common-password", "/etc/pam.d/common-password-pc", "/etc/pam.d/password-auth", "/etc/pam.d/password-auth-ac"] pamauthfiles = ["/etc/pam.d/common-auth", "/etc/pam.d/common-auth-pc", "/etc/pam.d/system-auth", "/etc/pam.d/system-auth-ac"] for f in pampassfiles: if os.path.isfile(f): self.pampassfile = f for f in pamauthfiles: if os.path.isfile(f): self.pamauthfile = f if not bool(self.pampassfile and self.pamauthfile): if self.ph.manager == "apt-get": self.pampassfile = "/etc/pam.d/common-password" self.pamauthfile = "/etc/pam.d/common-auth" elif self.ph.manager == "zypper": self.pampassfile = "/etc/pam.d/common-password-pc" self.pamauthfile = "/etc/pam.d/common-auth-pc" else: self.pampassfile = "/etc/pam.d/password-auth" self.pamauthfile = "/etc/pam.d/system-auth" if not self.check_pwreqs_configured(): self.ci2comp = False debug += "checkpasswordreqs method is False compliancy\n" compliant = False if not self.checkaccountlockout(): self.ci3comp = False debug += "checkaccountlockout method is False compliancy\n" compliant = False if not self.checklogindefs(): self.ci4comp = False debug += "checklogindefs method is False compliancy\n" compliant = False if not self.checklibuser(): self.ci4comp = False debug += "checklibuser method is False compliancy\n" compliant = False if debug: self.logger.log(LogPriority.DEBUG, debug) if not self.check_showfailed_logins(): compliant = False return compliant def fixLinux(self): """ Linux specific submethod to correct linux distributions. If your system is portage based, i.e. gentoo, you will need to do a manual fix for everything except the login.defs file @author: Derek Walker :return: success :rtype: bool """ success = True try: if self.ph.manager == "dnf": if not self.ph.check("authconfig"): self.ph.install("authconfig") # this is needed by fedora to continue except: pass # """create backups of pamfiles""" if os.path.exists(self.pampassfile): createFile(self.pampassfile + ".backup", self.logger) if os.path.exists(self.pamauthfile): createFile(self.pamauthfile + ".backup", self.logger) if self.ci2.getcurrvalue(): if not self.ci2comp: # configure regex for pwquality if self.usingpwquality: self.password = re.sub("pam_cracklib\.so", "pam_pwquality.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex = PWQUALITY_HIGH_REGEX else: regex = PWQUALITY_REGEX if self.pwqinstalled: if not self.setpasswordsetup(regex): success = False else: if not self.setpasswordsetup(regex, self.pwqualitypkgs): success = False # configure regex for cracklib elif self.usingcracklib: self.password = re.sub("pam_pwquality\.so", "pam_cracklib.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex = CRACKLIB_HIGH_REGEX else: regex = CRACKLIB_REGEX if self.clinstalled: if not self.setpasswordsetup(regex): success = False else: if not self.setpasswordsetup(regex, self.cracklibpkgs): success = False else: error = "Could not find pwquality/cracklib pam module. Fix failed." self.logger.log(LogPriority.ERROR, error) self.detailedresults += error + "\n" return False if self.ci3.getcurrvalue(): if not self.ci3comp: if self.usingpamfail: regex = PAMFAIL_REGEX if not self.setaccountlockout(regex): success = False self.detailedresults += "Unable to configure pam for faillock\n" elif self.usingpamtally2: regex = PAMTALLY_REGEX if not self.setaccountlockout(regex): success = False self.detailedresults += "Unable to configure pam for pam_tally2\n" else: self.detailedresults += "There is no account lockout program available for this system\n" success = False if self.ci4.getcurrvalue(): if not self.ci4comp: if not self.checklibuser(): if not self.setlibuser(): debug = "setlibuser() failed\n" self.detailedresults += "Unable to configure /etc/libuser.conf\n" self.logger.log(LogPriority.DEBUG, debug) success = False if not self.checklogindefs(): if not self.setlogindefs(): debug = "setdefpasshash() failed\n" self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += "Unable to configure /etc/login.defs file\n" success = False if not self.set_showfailed_logins(): success = False return success def check_showfailed_logins(self): """ config file: /etc/pam.d/postlogin-ac config option: session required pam_lastlog.so showfailed :return: """ configured = True search_line = {"session": "required pam_lastlog.so showfailed"} config_file = "/etc/pam.d/postlogin-ac" tmpfile = config_file + ".stonixtmp" self.lastlog_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", config_file, tmpfile, search_line, "present", "space") if not self.lastlog_editor.report(): configured = False self.detailedresults += "\nShow failed logins option is not configured correctly in PAM" return configured def set_showfailed_logins(self): """ config file: /etc/pam.d/postlogin-ac config option: session required pam_lastlog.so showfailed :return: success :rtype: bool """ success = True if not self.lastlog_editor.fix(): success = False self.detailedresults += "\nFailed to enable show failed logins option in PAM" elif not self.lastlog_editor.commit(): success = False self.detailedresults += "\nFailed to enable show failed logins option in PAM" return success def determine_pwreqs_mechanism(self): """ determine whether this system is using cracklib or pwquality as a password requirements control mechanism use pwquality by default since cracklib is legacy """ self.usingcracklib = False self.usingpwquality = False self.pwqualitypkg = "" self.cracklibpkg = "" # potential password requirements package names self.cracklibpkgs = ["libpam-cracklib", "cracklib"] self.pwqualitypkgs = ["libpam-pwquality", "pam_pwquality", "libpwquality", "libpwquality1"] # pwquality check for pkg in self.pwqualitypkgs: if self.ph.check(pkg): self.usingpwquality = True self.pwqualitypkg = pkg break if not self.usingpwquality: for pkg in self.pwqualitypkgs: if self.ph.checkAvailable(pkg): self.usingpwquality = True self.pwqualitypkg = pkg break # cracklib check (only runs if pwquality check turns up nothing) if not self.usingpwquality: for pkg in self.cracklibpkgs: if self.ph.check(pkg): self.usingcracklib = True self.cracklibpkg = pkg break if not self.usingcracklib: for pkg in self.cracklibpkgs: if self.ph.checkAvailable(pkg): self.usingcracklib = True self.cracklibpkg = pkg break def check_pwreqs_installed(self): """ determine if either a cracklib package or pwquality package is installed :return: installed :rtype: bool """ installed = False self.pwqinstalled = False self.clinstalled = False for pkg in self.pwqualitypkgs: if self.ph.check(pkg): self.pwqinstalled = True for pkg in self.cracklibpkgs: if self.ph.check(pkg): self.clinstalled = True if bool(self.clinstalled or self.pwqinstalled): installed = True return installed def check_pwreqs_configured(self): """ check whether the password requirements have been properly configured in PAM, using either cracklib or pwquality :return: passwords_configured :rtype: bool """ passwords_configured = True if not self.check_pwreqs_installed(): passwords_configured = False return passwords_configured if self.usingpwquality: if not self.pwqinstalled: for pkg in self.pwqualitypkgs: if self.ph.install(pkg): self.pwqinstalled = True break if not self.checkpasswordsetup("pwquality"): self.detailedresults += "System is using pwquality but it's not configured properly in PAM\n" passwords_configured = False elif self.usingcracklib: if not self.clinstalled: for pkg in self.cracklibpkgs: if self.ph.install(pkg): self.clinstalled = True break if not self.checkpasswordsetup("cracklib"): self.detailedresults += "System is using cracklib but it's not configured properly in PAM\n" passwords_configured = False return passwords_configured def checkpasswordsetup(self, package): """ Method called from within checkpasswordreqs method @author: Derek Walker :param package: string; name of package to check for :return: compliant :rtype: bool """ compliant = True regex1 = "" if package == "pwquality": self.password = re.sub("pam_cracklib\.so", "pam_pwquality.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex1 = PWQUALITY_HIGH_REGEX else: regex1 = PWQUALITY_REGEX if not self.chkpwquality(): compliant = False elif package == "cracklib": self.password = re.sub("pam_pwquality\.so", "pam_cracklib.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex1 = CRACKLIB_HIGH_REGEX else: regex1 = CRACKLIB_REGEX regex2 = "^password[ \t]+sufficient[ \t]+pam_unix.so\s+sha512\s+shadow\s+try_first_pass\s+use_authtok\s+remember=10" pamfiles = [] if self.ph.manager in ("yum", "dnf"): pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) else: pamfiles.append(self.pampassfile) for pamfile in pamfiles: found1, found2 = False, False if not os.path.exists(pamfile): self.detailedresults += pamfile + " doesn't exist\n" compliant = False else: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.detailedresults += "Incorrect permissions or ownership exist for file " + pamfile compliant = False contents = readFile(pamfile, self.logger) if not contents: self.detailedresults += pamfile + " is blank\n" compliant = False else: for line in contents: if re.search(regex1, line.strip()): found1 = True if re.search(regex2, line.strip()): found2 = True if not found1: self.detailedresults += "\n'password requisite ...' line not correct in " + pamfile if not found2: self.detailedresults += "\n'password sufficient ...' line not correct in " + pamfile compliant = False return compliant def setpasswordsetup(self, regex1, pkglist = None): """ configure password requirements in pam, install necessary packages :param regex1: string; regular expression :param pkglist: list; string names of packages to install :return: success :rtype: bool """ regex2 = "^password[ \t]+sufficient[ \t]+pam_unix.so sha512 shadow " + \ "try_first_pass use_authtok remember=10" success = True pamfiles = [] installed = False if pkglist: for pkg in pkglist: if self.ph.check(pkg): installed = True break else: installed = True if not installed: for pkg in pkglist: if self.ph.checkAvailable(pkg): if not self.ph.install(pkg): self.detailedresults += "Unable to install pkg " + pkg + "\n" return False else: installed = True if self.usingpwquality: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() + pkg event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) pwqfile = "/etc/security/pwquality.conf" tmpfile = pwqfile + ".stonixtmp" if self.environ.getsystemfismacat() == "high": data = {"difok": "7", "minlen": "14", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "4"} else: data = {"difok": "7", "minlen": "8", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "3"} self.pwqeditor = KVEditorStonix(self.statechglogger, self.logger, "conf", pwqfile, tmpfile, data, "present", "openeq") self.pwqeditor.report() break if not installed: self.detailedresults += "No password checking program available\n" return False if self.usingpwquality: if not self.setpwquality(): success = False if self.ph.manager in ("yum", "dnf"): writecontents = self.auth + "\n" + self.acct + "\n" + \ self.password + "\n" + self.session pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) else: writecontents = self.password pamfiles.append(self.pampassfile) for pamfile in pamfiles: if not os.path.exists(pamfile): self.detailedresults += pamfile + " doesn't exist.\n" + \ "Stonix will not attempt to create this file " + \ "and the fix for the this rule will not continue\n" return False # """Check permissions on pam file(s)""" for pamfile in pamfiles: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(pamfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set correct permissions on " + pamfile + "\n" contents = readFile(pamfile, self.logger) found1, found2 = False, False for line in contents: if re.search(regex1, line.strip()): found1 = True if re.search(regex2, line.strip()): found2 = True if not found1 or not found2: tmpfile = pamfile + ".stonixtmp" if writeFile(tmpfile, writecontents, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': pamfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(pamfile, tmpfile, myid) os.rename(tmpfile, pamfile) os.chown(pamfile, 0, 0) os.chmod(pamfile, 0o644) resetsecon(pamfile) else: self.detailedresults += "Unable to write to " + pamfile + "\n" success = False return success def checkaccountlockout(self): """ Method to determine which account locking program to use if any @author: Derek Walker :return: compliant :rtype: bool """ which = "/usr/bin/which " cmd1 = which + "faillock" cmd2 = which + "pam_tally2" ch = CommandHelper(self.logger) pamfiles = [] compliant = True regex = "" if ch.executeCommand(cmd1): debug = "ran " + cmd1 + " successfully\n" self.logger.log(LogPriority.DEBUG, debug) if ch.getReturnCode() == 0: debug = "return code of 0 and using faillock\n" self.logger.log(LogPriority.DEBUG, debug) self.usingpamfail = True elif ch.executeCommand(cmd2): debug = "ran " + cmd2 + " successfully\n" self.logger.log(LogPriority.DEBUG, debug) if ch.getReturnCode() == 0: debug = "return code of 0 and using pam_tally2\n" self.logger.log(LogPriority.DEBUG, debug) self.usingpamtally2 = True else: self.detailedresults += "There is no account " + \ "locking program available for this " + \ "distribution\n" return False elif ch.executeCommand(cmd2): debug = "ran " + cmd2 + " successfully\n" self.logger.log(LogPriority.DEBUG, debug) if ch.getReturnCode() == 0: debug = "return code of 0 and using pam_tally2\n" self.logger.log(LogPriority.DEBUG, debug) self.usingpamtally2 = True else: self.detailedresults += "There is no account " + \ "locking program available for this " + \ "distribution\n" return False else: self.detailedresults += "There is no account " + \ "locking program available for this " + \ "distribution\n" return False if self.usingpamfail: regex = "^auth[ \t]+required[ \t]+pam_faillock.so preauth silent audit " + \ "deny=5 unlock_time=900 fail_interval=900" elif self.usingpamtally2: regex = "^auth[ \t]+required[ \t]+pam_tally2.so deny=5 " + \ "unlock_time=900 onerr=fail" if self.ph.manager in("yum", "dnf"): pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) else: pamfiles.append(self.pamauthfile) for pamfile in pamfiles: found = False if not os.path.exists(pamfile): self.detailedresults += "Critical pam file " + pamfile + " doesn't exist\n" compliant = False else: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions aren't correct on " + pamfile + "\n" self.ci3comp = False compliant = False contents = readFile(pamfile, self.logger) if not contents: self.detailedresults += pamfile + " is blank\n" self.ci3comp = False compliant = False else: for line in contents: if re.search(regex, line.strip()): found = True if not found: self.detailedresults += "Didn't find the correct contents in " + pamfile + "\n" self.ci3comp = False compliant = False return compliant def setaccountlockout(self, regex): """ configure the account lockout time in pam :param regex: string; regular expression :return: success :rtype: bool """ success = True pamfiles = [] if self.ph.manager in ("yum", "dnf"): pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) writecontents = self.auth + "\n" + self.acct + "\n" + \ self.password + "\n" + self.session else: pamfiles.append(self.pamauthfile) writecontents = self.auth for pamfile in pamfiles: if not os.path.exists(pamfile): self.detailedresults += pamfile + " doesn't exist.\n" + \ "Stonix will not attempt to create this file " + \ "and the fix for the this rule will not continue\n" return False # """Check permissions on pam file(s)""" for pamfile in pamfiles: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(pamfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set " + \ "correct permissions on " + pamfile + "\n" contents = readFile(pamfile, self.logger) found = False for line in contents: if re.search(regex, line.strip()): found = True if not found: tmpfile = pamfile + ".stonixtmp" if writeFile(tmpfile, writecontents, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': pamfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(pamfile, tmpfile, myid) os.rename(tmpfile, pamfile) os.chown(pamfile, 0, 0) os.chmod(pamfile, 0o644) resetsecon(pamfile) else: self.detailedresults += "Unable to write to " + pamfile + "\n" success = False return success def chkpwquality(self): """ check settings of pwquality pam plugin :return: compliant :rtype: bool """ compliant = True pwqfile = "/etc/security/pwquality.conf" if os.path.exists(pwqfile): tmpfile = pwqfile + ".stonixtmp" if self.environ.getsystemfismacat() == "high": data = {"difok": "7", "minlen": "14", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "4"} else: data = {"difok": "7", "minlen": "8", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "3"} self.pwqeditor = KVEditorStonix(self.statechglogger, self.logger, "conf", pwqfile, tmpfile, data, "present", "openeq") if not self.pwqeditor.report(): compliant = False self.detailedresults += "Not all correct contents were found in " + pwqfile + "\n" else: compliant = False self.detailedresults += "System is using pwquality and crucial file /etc/security/pwquality.conf doesn't exist\n" return compliant def checklogindefs(self): """ Method to check the password hash algorithm settings in login.defs :return: compliant :rtype: bool """ compliant = True if os.path.exists(self.logindefs): if not checkPerms(self.logindefs, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions incorrect for " + \ self.logindefs + " file\n" compliant = False data = {"MD5_CRYPT_ENAB": "no", "ENCRYPT_METHOD": "SHA512", "PASS_MAX_DAYS": "180", "PASS_MIN_DAYS": "1", "PASS_WARN_AGE": "7", "FAIL_DELAY": "4"} tmppath = self.logindefs + ".stonixtmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.logindefs, tmppath, data, "present", "space") if not self.editor2.report(): debug = self.logindefs + " doesn't contain the correct " + \ "contents\n" self.detailedresults += self.logindefs + " doesn't contain " + \ "the correct contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False return compliant def checklibuser(self): """ Private method to check the password hash algorithm settings in libuser.conf @author: Derek Walker :return: compliant :rtype: bool """ compliant = True # """check if libuser is intalled""" if not self.ph.check("libuser"): # """if not, check if available""" if self.ph.checkAvailable("libuser"): self.detailedresults += "libuser available but not installed\n" return False else: # """not available, not a problem""" return True # """create a kveditor for file if it exists, if not, we do it in # the setlibuser method inside the fix""" if os.path.exists(self.libuserfile): data = {"defaults": {"crypt_style": "sha512"}} datatype = "tagconf" intent = "present" tmppath = self.libuserfile + ".stonixtmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") if not self.editor1.report(): debug = "/etc/libuser.conf doesn't contain the correct contents\n" self.detailedresults += "/etc/libuser.conf doesn't contain the correct contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + self.libuserfile + "\n" compliant = False else: self.detailedresults += "Libuser installed but libuser file doesn't exist\n" compliant = False return compliant def setpwquality(self): """ :return: """ success = True created = False pwqfile = "/etc/security/pwquality.conf" if not os.path.exists(pwqfile): createFile(pwqfile, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'creation', 'filepath': pwqfile} self.statechglogger.recordchgevent(myid, event) created = True tmpfile = pwqfile + ".stonixtmp" if self.environ.getsystemfismacat() == "high": data = {"difok": "7", "minlen": "14", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "4"} else: data = {"difok": "7", "minlen": "8", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "3"} self.pwqeditor = KVEditorStonix(self.statechglogger, self.logger, "conf", pwqfile, tmpfile, data, "present", "openeq") self.pwqeditor.report() if self.pwqeditor.fixables: if self.pwqeditor.fix(): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.pwqeditor.setEventID(myid) if not self.pwqeditor.commit(): success = False self.detailedresults += "Unable to correct " + pwqfile + "\n" else: success = False self.detailedresults += "Unable to correct " + pwqfile + "\n" return success def setlibuser(self): """Method to check if libuser is installed and the contents of libuser file. @author: Derek Walker :return: bool """ created = False success = True data = {"defaults": {"crypt_style": "sha512"}} # """check if installed""" if not self.ph.check("libuser"): # """if not installed, check if available""" if self.ph.checkAvailable("libuser"): # """if available, install it""" if not self.ph.install("libuser"): self.detailedresults += "Unable to install libuser\n" return False else: # """since we're just now installing it we know we now # need to create the kveditor""" self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() + "libuser" event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) datatype = "tagconf" intent = "present" tmppath = self.libuserfile + ".stonixtmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") self.editor1.report() else: return True if not os.path.exists(self.libuserfile): if not createFile(self.libuserfile, self.logger): self.detailedresults += "Unable to create libuser file\n" debug = "Unable to create the libuser file\n" self.logger.log(LogPriority.DEBUG, debug) return False created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.libuserfile} self.statechglogger.recordchgevent(myid, event) tmppath = self.libuserfile + ".stonixtmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.libuserfile, tmppath, data, "present", "openeq") self.editor1.report() if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.libuserfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set the permissions on " + self.libuserfile + "\n" elif not setPerms(self.libuserfile, [0, 0, 0o644], self.logger): success = False self.detailedresults += "Unable to set the permissions on " + self.libuserfile + "\n" if self.editor1.fixables: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if self.editor1.fix(): if self.editor1.commit(): debug = "/etc/libuser.conf has been corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.libuserfile, 0, 0) os.chmod(self.libuserfile, 0o644) resetsecon(self.libuserfile) else: self.detailedresults += "/etc/libuser.conf couldn't be corrected\n" success = False else: self.detailedresults += "/etc/libuser.conf couldn't be corrected\n" success = False return success def setlogindefs(self): """ configure login.defs options :return: success :rtype: bool """ success = True if not checkPerms(self.logindefs, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.logindefs, [0, 0, 0o644], self.logger, self.statechglogger, myid): self.detailedresults += "Unable to set permissions on " + self.logindefs + " file\n" success = False if self.editor2: if self.editor2.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if self.editor2.fix(): if self.editor2.commit(): debug = "/etc/login.defs file has been corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.logindefs, 0, 0) os.chmod(self.logindefs, 0o644) resetsecon(self.logindefs) else: debug = "Unable to correct the contents of /etc/login.defs\n" self.detailedresults += "Unable to correct the contents of /etc/login.defs\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: self.detailedresults += "Unable to correct the contents of /etc/login.defs\n" debug = "Unable to correct the contents of /etc/login.defs\n" self.logger.log(LogPriority.DEBUG, debug) success = False return success
class ConfigureScreenLocking(RuleKVEditor): def __init__(self, config, environ, logdispatcher, statechglogger): RuleKVEditor.__init__(self, config, environ, logdispatcher, statechglogger) self.logger = logdispatcher self.rulenumber = 74 self.rulename = "ConfigureScreenLocking" self.mandatory = True self.rootrequired = False self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.effectiveUserID = self.environ.geteuid() self.sethelptext() self.formatDetailedResults("initialize") self.guidance = ["NSA 2.3.5.6.1"] if self.environ.getosfamily() == "darwin": if self.effectiveUserID == 0: self.addKVEditor( "SystemAskForPasswordSystem", "defaults", "/Library/Preferences/com.apple.screensaver", "", {"askForPassword": ["1", "-int 1"]}, "present", "", "Ask for password when system wide " + "screen saver is on.", None, False, {"askForPassword": ["0", "-int 0"]}) self.addKVEditor( "SystemSetScreenSaverIdleTime", "defaults", "/Library/Preferences/com.apple.screensaver", "", {"idleTime": ["840", "-int 840"]}, "present", "", "Sets system the screen saver to " + "activate after 14 minutes of idleTime.", None, False, { "idleTime": [ "The domain/default pair of ( .+" + "com\.apple\.screensaver, " + "idleTime) does not " + "exist", None ] }) self.addKVEditor( "SystemLoginWindowIdleTime", "defaults", "/Library/Preferences/com.apple.screensaver", "", {"loginWindowIdleTime": ["840", "-int 840"]}, "present", "", "Sets system LoginWindowIdleTime to " + "14 minutes.", None, False, { "loginWindowIdleTime": [ "The domain/default pair of ( .+" + "com\.apple\.screensaver, " + "loginWindowIdleTime) does not " + "exist", None ] }) else: self.addKVEditor( "AskForPassword", "defaults", "~/Library/Preferences/com.apple.screensaver", "", {"askForPassword": ["1", "-int 1"]}, "present", "", "Ask for password when screen saver is on.", None, False, {"askForPassword": ["0", "-int 0"]}) self.addKVEditor( "AskForPasswordDelay", "defaults", "~/Library/Preferences/com.apple.screensaver", "", {"askForPasswordDelay": ["0", "-int 0"]}, "present", "", "Delay asking for password by 0 seconds.", None, False, { "askForPasswordDelay": [ "The domain/default pair of ( .+" + "com\.apple\.screensaver, " + "askForPassword) does not " + "exist", None ] }) else: datatype = 'bool' key = 'CONFIGURESCREENLOCKING' instructions = "To prevent the configuration of idle screen locking, set the value of CONFIGURESCREENLOCKING to False." default = True self.ci = self.initCi(datatype, key, instructions, default) #self.gnomeInst variable determines in the fixGnome method #at the beginning if we even proceed. If False we don't proceed #and is fine. Gets set to True in reportGnome method if #either gconftool-2 or gsettings binaries exist. self.gnomeInst = False self.useGconf = True self.iditerator = 0 self.cmdhelper = CommandHelper(self.logger) self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.euid = self.environ.geteuid() def report(self): """ConfigureScreenLocking.report() method to report whether system is configured to screen locking NSA standards. If the system is linux, although many desktops are available, this rule will only check the two most popular desktops, KDE, and Gnome. @author: dwalker :param self: essential if you override this definition :return: self.compliant :rtype: bool """ self.detailedresults = "" self.compliant = True try: compliant = True self.detailedresults = "" if self.environ.osfamily == 'linux': if not self.check_package(): compliant = False if self.euid != 0: self.detailedresults += "\nThis is expected if not running with elevated privileges since STONIX " \ "requires elevated privileges to install packages. Please run STONIX with elevated privileges " \ "and run the fix for this rule again, to fix this issue." if self.ph.check("gdm") or self.ph.check("gdm3"): self.gnomeInstalled = True if not self.reportGnome(): self.detailedresults += "\nGnome GUI environment " + \ "does not appear to be correctly configured " + \ "for screen locking parameters." compliant = False else: self.detailedresults += "\nGnome GUI environment " + \ "appears to be correctly configured for " + \ "screen locking parameters." else: self.gnomeInstalled = False self.detailedresults += "\nGnome not installed. No need to configure for gnome." if self.ph.check("kdm") or self.ph.check("kde-workspace") or \ self.ph.check("sddm") or self.ph.check("patterns-kde-kde_yast"): self.kdeInstalled = True if not self.reportKde(): self.detailedresults += "\nKDE GUI environment " + \ "does not appear to be correctly configured " + \ "for screen locking parameters." compliant = False else: self.detailedresults += "\nKDE GUI environment " + \ "appears to be correctly configured for " + \ "screen locking parameters." else: self.kdeInstalled = False self.detailedresults += "\nKDE not installed. No need to configure for kde." elif self.environ.getosfamily() == "darwin": compliant = self.reportMac() self.compliant = compliant except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def check_package(self): """ for rhel 7 and similar generation linux rpm-based systems, the 'screen' package is required by the STIG for rhel 8 and beyond (and similar), the 'tmux' package is required. :return: installed :rtype: bool """ self.screen_pkg = "" installed = True if self.ph.checkAvailable("tmux"): self.screen_pkg = "tmux" elif self.ph.check("tmux"): self.screen_pkg = "tmux" else: self.screen_pkg = "screen" if not self.ph.check(self.screen_pkg): self.detailedresults += "\nThe required package: " + str( self.screen_pkg) + " is not installed" installed = False else: self.detailedresults += "\nThe required package: " + str( self.screen_pkg) + " is installed" return installed def reportMac(self): """Mac osx specific report submethod @author: dwalker :param self: essential if you override this definition :returns: bool - True if system is compliant, False if it isn't """ success = RuleKVEditor.report(self, True) return success def reportGnome(self): """determines if gnome is installed, if so, checks to see if the return value strings from running the gconftool-2 command are correct. Gconftool-2 command only works in root mode so if not root do not audit gnome and just return true @author: dwalker :param self: essential if you override this definition :returns: bool @change: dwalker - mass refactor, added comments """ compliant = True gsettings = "/usr/bin/gsettings" gconf = "/usr/bin/gconftool-2" self.gesttingsidletime = "" self.gconfidletime = "" self.setcmds = "" #may need to change code in the future to make gconf and #gsettings mutually exclusive with if elif else self.gnomeInstalled = False #if they are found to conflict with each other if os.path.exists(gconf): getcmds = { "/apps/gnome-screensaver/idle_activation_enabled": "true", "/apps/gnome-screensaver/lock_enabled": "true", "/apps/gnome-screensaver/mode": "blank-only", "/desktop/gnome/session/idle_delay": "15" } #make a copy of the getcmds dictionary tempdict = dict(getcmds) #check each value using the gconftool-2 command for cmd in getcmds: #set the get command for each value cmd2 = gconf + " --get " + cmd #execute the command self.cmdhelper.executeCommand(cmd2) #get output and error output output = self.cmdhelper.getOutput() error = self.cmdhelper.getError() #the value is set if output: #check this one separately since the value is #also able to be less than 15 mins and be compliant if cmd == "/desktop/gnome/session/idle_delay": if int(output[0].strip()) > 15: self.detailedresults += "Idle delay value " + \ "is not 15 or lower (value: " + \ output[0].strip() + ")\n" #if they have a value less than 15 mins this is ok #and we set the self.gconfidletime variable which is #used during setting gconf values in the fixGnome method elif int(output[0].strip()) < 15: self.gconfidletime = output[0].strip() del tempdict[cmd] #value is correct so remove it from the tempdic else: del tempdict[cmd] #check if value is correct with associated key elif output[0].strip() != getcmds[cmd]: self.detailedresults += cmd2 + " didn't produce the " + \ "desired value after being run which is " + \ getcmds[cmd] + "\n" #value is correct so remove it from the tempdict else: del tempdict[cmd] #else the value isn't set elif error: self.detailedresults += "There is no value set for:\n" + \ cmd2 + "\n" #if tempdict still has leftover values then #there were values that weren't correctly set #and is non compliant if tempdict: compliant = False #set self.setcmds variable to be a copy of tempdict which #we use later in the fix to determine if we fix this portion #of the rule or not self.setcmds = tempdict if os.path.exists(gsettings): self.gnomeInst = True #use gsettings command to see if the correct values are set #for each key in getcmds dictionary getcmds = { " get org.gnome.desktop.screensaver " + "idle-activation-enabled": "true", " get org.gnome.desktop.screensaver lock-enabled": "true", " get org.gnome.desktop.screensaver lock-delay": "0", " get org.gnome.desktop.screensaver picture-opacity": "100", " get org.gnome.desktop.screensaver picture-uri": "''", " get org.gnome.desktop.session idle-delay": "900" } #run each gsettings get command for each key and get value for cmd in getcmds: cmd2 = gsettings + cmd self.cmdhelper.executeCommand(cmd2) output = self.cmdhelper.getOutput() error = self.cmdhelper.getError() if output: #check this one separately since the value is #also able to be less than 900 secs and be compliant if cmd == " get org.gnome.desktop.session idle-delay": try: splitOut = output[0].split() if len(splitOut) > 1: num = splitOut[1] else: num = splitOut[0] if int(num) > 900: compliant = False self.detailedresults += "Idle delay value " + \ "is not 900 seconds (value: " +\ num + ")\n" #if they have a value less than 900 secs this is ok #and we set the self.gsettingsidletime variable which is #used during setting gsettings values in the fixGnome method # elif int(num) < 900: # self.gsettingsidletime = num elif int(num) == 0: compliant = False self.detailedresults += "Idle delay set " + \ "to 0, meaning it is disabled.\n" else: self.gsettingsidletime = "900" except ValueError: self.detailedresults += "Unexpected result: " + \ '"' + cmd2 + '" output was not a number\n' compliant = False elif cmd == " get org.gnome.desktop.screensaver lock-delay": try: splitOut = output[0].split() if len(splitOut) > 1: num = splitOut[1] else: num = splitOut[0] if int(num) != 0: compliant = False self.detailedresults += "Lock delay is not " + \ "set to 0\n" except ValueError: self.detailedresults += "Unexpected result: " + \ '"' + cmd2 + '" output was not a number\n' compliant = False elif output[0].strip() != getcmds[cmd]: self.detailedresults += '"' + cmd2 + \ "\" produced value: " + output[0].strip() + \ " instead of the desired value: " + getcmds[cmd] + "\n" compliant = False elif error: if re.search("No such key", error[0], re.I): continue self.detailedresults += "There is no value set for:" + \ cmd2 + "\n" compliant = False if self.environ.geteuid() == 0: # instantiate a kveditor to ensure self.dconfsettings file # contains correct contents self.dconfsettings = "/etc/dconf/db/local.d/local.key" if os.path.exists(self.dconfsettings): self.dconfdata = { "org/gnome/desktop/screensaver": { "idle-activation-enabled": "true", "lock-enabled": "true", "lock-delay": "0", "picture-opacity": "100", "picture-uri": "\'\'" }, "org/gnome/desktop/session": { "idle-delay": "uint32 900" } } self.kveditordconf = KVEditorStonix( self.statechglogger, self.logger, "tagconf", self.dconfsettings, self.dconfsettings + ".tmp", self.dconfdata, "present", "closedeq") if not self.kveditordconf.report(): self.detailedresults += self.dconfsettings + \ " doesn't cotain correct contents\n" compliant = False else: compliant = False self.detailedresults += self.dconfsettings + " not found\n" # check self.dconfuserprofile file to ensure existence # and/or correct contents self.dconfuserprofile = "/etc/dconf/profile/user" self.userprofilecontent = "user-db:user\n" + \ "system-db:local" if os.path.exists(self.dconfuserprofile): contents = readFile(self.dconfuserprofile, self.logger) contentstring = "" for line in contents: contentstring += line if not re.search(self.userprofilecontent, contentstring): compliant = False self.detailedresults += "Correct contents weren't " + \ "found in " + self.dconfuserprofile + "\n" else: compliant = False self.detailedresults += self.dconfuserprofile + " not " + \ "found\n" # the following file locks the settings we earlier set with the # gsettings command so that they don't default upon logout and/or # reboot. Check the file for the correct contents self.dconfsettingslock = "/etc/dconf/db/local.d/locks/stonix-settings.conf" self.dconflockdata = [ "/org/gnome/desktop/session/idle-delay", "/org/gnome/desktop/screensaver/idle-activation-enabled", "/org/gnome/desktop/screensaver/lock-enabled", "/org/gnome/desktop/screensaver/lock-delay", "/org/gnome/desktop/screensaver/picture-uri" ] locks_missing = [] if os.path.exists(self.dconfsettingslock): contents = readFile(self.dconfsettingslock, self.logger) for line in contents: if line.strip() not in self.dconflockdata: locks_missing.append(line.strip()) if locks_missing: self.detailedresults += "\nThe following settings should be locked from changes by the user but aren't:\n" + "\n".join( locks_missing) compliant = False else: compliant = False self.detailedresults += "\nGnome settings lock file not found" return compliant def reportKde(self): """determines if kde is installed, if so, ensures kde is configured by enabling screenlocking, automatically going black after 14 minutes and if inactivity ensues after 14 minutes, screen fully locks after 1 additional minute of inactivity for a total of 15 minutes activity @author: dwalker :param self: essential if you override this definition :returns: bool """ self.kdefix = {} if self.ph.manager == "apt-get" or self.ph.manager == "zypper": self.rcpath = ".config/kscreenlockerrc" self.kdeprops = { "Daemon": { "Autolock": "true", "LockGrace": "60", "LockOnResume": "true", "Timeout": "14" } } else: self.rcpath = ".kde/share/config/kscreensaverrc" self.kdeprops = { "ScreenSaver": { "Enabled": "true", "Lock": "true", "LockGrace": "60000", "Timeout": "840" } } if self.environ.geteuid() == 0: contents = readFile("/etc/passwd", self.logger) for line in contents: temp = line.split(":") try: username = temp[0] homepath = temp[5] except IndexError: self.logdispatch.log(LogPriority.DEBUG, [ 'ConfigureScreenLocking', 'IndexError processing ' + str(temp) ]) continue kdeparent1 = os.path.join(homepath, ".kde") kdeparent2 = os.path.join(homepath, ".kde4") kdefile = os.path.join(homepath, self.rcpath) if not os.path.exists(kdeparent1) and not os.path.exists( kdeparent2): # User does not user KDE continue elif not os.path.exists(kdefile): self.kdefix[username] = homepath self.detailedresults += kdefile + " not found for " + \ str(username) + "\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) continue elif not self.searchFile(kdefile): self.detailedresults += "Did not find " + \ "required contents " + "in " + username + \ "'s " + kdefile + "\n" self.kdefix[username] = homepath if self.kdefix: self.detailedresults += "The following users don't " + \ "have kde properly configured for " + \ "screen locking:\n" for user in self.kdefix: self.detailedresults += user + "\n" return False else: return True else: kdeparent1 = os.path.join(self.environ.geteuidhome(), ".kde") kdeparent2 = os.path.join(self.environ.geteuidhome(), ".kde4") kdefile = os.path.join(self.environ.geteuidhome(), self.rcpath) if not os.path.exists(kdeparent1) and not os.path.exists( kdeparent2): self.detailedresults += "Current user doesn't use kde. " + \ "No need to configure.\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: if not os.path.exists(kdefile): self.detailedresults += "Your " + kdefile + \ " file doesn't exist.\n" return False elif not self.searchFile(kdefile): self.detailedresults += "Did not find " + \ "required contents in " + kdefile + "\n" return False else: return True def fix(self): """ConfigureScreenLocking.fix() method to correct screen locking @author: dwalker :param self: essential if you override this definition :returns: bool - True if fix is successful, False if it isn't """ self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: self.detailedresults = "" success = True if self.environ.getosfamily() == "linux": if not self.ci.getcurrvalue(): self.detailedresults += "Rule not enabled so nothing was done\n" self.logger.log( LogPriority.DEBUG, 'Rule was not enabled, so nothing was done') return if self.euid == 0: if not self.ph.install(self.screen_pkg): success = False self.logger.log( LogPriority.DEBUG, "Failed to install required package 'screen'") else: self.detailedresults += "\nNote: Some required packages may not be installed because STONIX is not running with elevated privileges." if self.gnomeInstalled: if not self.fixGnome(): success = False if self.kdeInstalled: if not self.fixKde(): success = False elif self.environ.getosfamily() == "darwin": if self.environ.geteuid() == 0: self.iditerator = 0 eventlist = self.statechglogger.findrulechanges( self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) success = self.fixMac() self.rulesuccess = success except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixKde(self): """This method checks if the kde screenlock file is configured properly. Please note, this rule may fail if the owner and group of configuration file are not that of the user in question but doesn't necessarily mean your system is out of compliance. If the fix fails Please check the logs to determing the real reason of non rule success. @author: dwalker :param self: essential if you override this definition :returns: bool - True if KDE is successfully configured, False if it isn't """ success = True if self.environ.geteuid() == 0: self.logdispatch.log(LogPriority.DEBUG, 'ConfigureScreenLocking.fixKde') if not self.kdefix: return True for user in self.kdefix: homepath = self.kdefix[user] kdefile = os.path.join(homepath, self.rcpath) if not self.correctFile(kdefile, user): success = False self.detailedresults += "Unable to configure " + \ kdefile + "\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) else: username = "" homepath = self.environ.geteuidhome() kdefile = os.path.join(homepath, self.rcpath) uidnum = int(self.environ.geteuid()) passwddata = readFile("/etc/passwd", self.logger) found = False for user in passwddata: user = user.split(':') try: puidnum = int(user[2]) if puidnum == uidnum: username = user[0] found = True except IndexError: continue if not found: self.detailedresults += "Could not obtain your user id.\n" + \ "Stonix couldn't proceed with correcting " + kdefile + "\n" success = False elif not self.correctFile(kdefile, username): self.detailedresults += "Stonix couldn't correct the contents " + \ " of " + kdefile + "\n" success = False return success def fixGnome(self): """ensures gnome is configured to automatically screen lock after 15 minutes of inactivity, if gnome is installed @author: dwalker :param self: essential if you override this definition :returns: bool - True if gnome is successfully configured, False if it isn't """ info = "" success = True gconf = "/usr/bin/gconftool-2" gsettings = "/usr/bin/gsettings" if os.path.exists(gconf): #variable self.setcmds still has items left in its dictionary #which was set in the reportGnome method, meaning some values #either were incorrect or didn't have values. Go through and #set each remaining value that isn't correct cmd = "" if self.setcmds: for item in self.setcmds: if item == "/apps/gnome-screensaver/idle_activation_enabled": cmd = gconf + " --type bool --set /apps/gnome-screensaver/idle_activation_enabled true" elif item == "/apps/gnome-screensaver/lock_enabled": cmd = gconf + " --type bool --set /apps/gnome-screensaver/lock_enabled true" elif item == "/apps/gnome-screensaver/mode": cmd = gconf + ' --type string --set /apps/gnome-screensaver/mode "blank-only"' elif item == "/desktop/gnome/session/idle_delay": if self.gconfidletime: cmd = gconf + " --type int --set /desktop/gnome/session/idle_delay " + \ self.gconfidletime else: cmd = gconf + " --type int --set /desktop/gnome/session/idle_delay 15" if self.cmdhelper.executeCommand(cmd): if self.cmdhelper.getReturnCode() != 0: info += "Unable to set value for " + cmd + "\n" success = False else: info += "Unable to set value for " + cmd + "\n" success = False if os.path.exists(gsettings): setcmds = [ "org.gnome.desktop.screensaver idle-activation-enabled true", "org.gnome.desktop.screensaver lock-enabled true", "org.gnome.desktop.screensaver lock-delay 0", "org.gnome.desktop.screensaver picture-opacity 100", "org.gnome.desktop.screensaver picture-uri ''", "org.gnome.desktop.session idle-delay 900" ] # " set org.gnome.desktop.session idle-delay " + self.gsettingsidletime] for cmd in setcmds: cmd2 = gsettings + " set " + cmd self.cmdhelper.executeCommand(cmd2) if self.cmdhelper.getReturnCode() != 0: success = False info += "Unable to set value for " + cmd + \ " using gsettings\n" # Set gsettings with dconf # Unlock dconf settings # Create dconf settings lock file if self.environ.geteuid() == 0: if not os.path.exists(self.dconfsettingslock): if not createFile(self.dconfsettingslock, self.logger): self.rulesuccess = False self.detailedresults += "Unable to create stonix-settings file\n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False #write correct contents to dconf lock file if os.path.exists(self.dconfsettingslock): # Write to the lock file if self.dconflockdata: contents = "" tmpfile = self.dconfsettingslock + ".tmp" for line in self.dconflockdata: contents += line + "\n" if not writeFile(tmpfile, contents, self.logger): self.rulesuccess = False self.detailedresults += "Unable to write contents to " + \ "stonix-settings file\n" else: os.rename(tmpfile, self.dconfsettingslock) os.chown(self.dconfsettingslock, 0, 0) os.chmod(self.dconfsettingslock, 0o644) resetsecon(self.dconfsettingslock) # Create dconf user profile file if not os.path.exists(self.dconfuserprofile): if not createFile(self.dconfuserprofile, self.logger): self.rulesuccess = False self.detailedresults += "Unable to create dconf " + \ "user profile file\n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False # Write dconf user profile if os.path.exists(self.dconfuserprofile): tmpfile = self.dconfuserprofile + ".tmp" if not writeFile(tmpfile, self.userprofilecontent, self.logger): self.rulesuccess = False self.detailedresults += "Unabled to write to dconf user" + \ " profile file\n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False else: os.rename(tmpfile, self.dconfuserprofile) os.chown(self.dconfuserprofile, 0, 0) os.chmod(self.dconfuserprofile, 0o644) resetsecon(self.dconfuserprofile) # Fix dconf settings if not os.path.exists(self.dconfsettings): if not createFile(self.dconfsettings, self.logger): self.rulesuccess = False self.detailedresults += "Unable to create " + self.dconfsettings + " file \n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False self.dconfdata = { "org/gnome/desktop/screensaver": { "idle-activation-enabled": "true", "lock-enabled": "true", "lock-delay": "0", "picture-opacity": "100", "picture-uri": "\'\'" }, "org/gnome/desktop/session": { "idle-delay": "uint32 900" } } self.kveditordconf = KVEditorStonix( self.statechglogger, self.logger, "tagconf", self.dconfsettings, self.dconfsettings + ".tmp", self.dconfdata, "present", "closedeq") self.kveditordconf.report() if self.kveditordconf.fixables: if not self.kveditordconf.fix(): success = False self.detailedresults += "Unable to put correct settings inside " + \ self.dconfsettings + "\n" elif not self.kveditordconf.commit(): success = False self.detailedresults += "Unable to put correct settings inside " + \ self.dconfsettings + "\n" #run dconf update command to make dconf changes take effect if os.path.exists("/bin/dconf"): cmd = "/bin/dconf update" self.cmdhelper.executeCommand(cmd) elif os.path.exists("/usr/bin/dconf"): cmd = "/usr/bin/dconf update" self.cmdhelper.executeCommand(cmd) self.detailedresults += info return success def fixMac(self): """Mac osx specific fix submethod @author: dwalker :param self: essential if you override this definition :returns: bool - True if system is successfully fix, False if it isn't """ success = RuleKVEditor.fix(self, True) return success def searchFile(self, filehandle): """temporary method to separate the code to find directives from the rest of the code. Will put back all in one method eventually @author: dwalker :param filehandle: string :returns: bool """ self.editor = "" kvt = "tagconf" intent = "present" tpath = filehandle + ".tmp" conftype = "closedeq" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvt, filehandle, tpath, self.kdeprops, intent, conftype) if not self.editor.report(): return False else: return True def correctFile(self, kfile, user): """separate method to find the correct contents of each file passed in as a parameter. @author: dwalker :param kfile: :param user: :returns: bool """ success = True if not os.path.exists(kfile): if not createFile(kfile, self.logger): self.detailedresults += "Unable to create " + kfile + \ " file for the user\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return False if not self.searchFile(kfile): if self.editor.fixables: if not self.editor.fix(): debug = "Kveditor fix is failing for file " + \ kfile + "\n" self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += "Unable to correct contents for " + \ kfile + "\n" return False elif not self.editor.commit(): debug = "Kveditor commit is failing for file " + \ kfile + "\n" self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += "Unable to correct contents for " + \ kfile + "\n" return False uid = getpwnam(user)[2] gid = getpwnam(user)[3] if uid != "" and gid != "": os.chmod(kfile, 0o600) os.chown(kfile, uid, gid) resetsecon(kfile) else: success = False self.detailedresults += "Unable to obtain uid and gid of " + user + "\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return success def undo(self): self.detailedresults += "This rule cannot be reverted\n" self.rulesuccess = False self.formatDetailedResults("undo", self.rulesuccess, self.detailedresults)
class AuditFirefoxUsage(Rule): """ This module will audit firefox browser usage to determine if any site not listed in the APPROVEDDOMAINS CI has been visited while running in root mode """ def __init__(self, config, environ, logger, statechglogger): """ private method to initialize the module :param config: configuration object instance :param environ: environment object instance :param logger: logdispatcher object instance :param statechglogger: statechglogger object instance """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 84 self.rulename = "AuditFirefoxUsage" self.mandatory = True self.rootrequired = True self.sethelptext() self.formatDetailedResults("initialize") self.guidance = [] self.applicable = {'type': 'white', 'family': ['linux']} # Configuration item instantiation datatype = 'list' key = 'APPROVEDDOMAINS' instructions = """This is a list of domains which the root user is \ approved to browse.""" default = LOCALDOMAINS if default == None: default = ["localhost"] elif not default: default = ["localhost"] self.approvedDomainsCi = self.initCi(datatype, key, instructions, default) datatype = 'bool' key = 'DISABLEPROXY' instructions = """To disable Firefox's proxy settings, ensuring that \ browsing is limited to local domains in a proxied environment, set \ DISABLEPROXY to True.""" default = True self.ci = self.initCi(datatype, key, instructions, default) self.ph = Pkghelper(self.logger, self.environ) self.auditonly = True def report(self): """ report the status of the rule's compliance with root-enabled firefox browsing :returns: self.compliant - True if compliant, False if not """ # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [LOCALDOMAINS] if not self.checkConsts(constlist): self.compliant = False self.detailedresults = "\nPlease ensure that the constant: LOCALDOMAINS, in localize.py, is defined and is not None. This rule will not function without it." self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant try: self.detailedresults = "" compliant = True ffDirs = self.getFirefoxDirs() urls = [] package = "" if self.ph.checkAvailable("sqlite3"): package = "sqlite3" elif self.ph.checkAvailable("sqlite"): package = "sqlite" if not self.ph.check(package): self.ph.install(package) for ffDir in ffDirs: placesPath = ffDir + "/places.sqlite" if not os.path.exists(placesPath): continue ch = CommandHelper(self.logger) command = ["/usr/bin/sqlite3", placesPath, ".tables"] ch.executeCommand(command) if not ch.getReturnCode() == 0: # The version of sqlite3 on RHEL 6 cannot read these dbs. # Instead, we will examine the file manually, looking for # entries beginning with %\x08. sqlBin = open(placesPath, "rb").read() urlList = re.findall("%\x08https?://.*?/", sqlBin) for url in urlList: urls.append(url[2:]) else: conn = sqlite3.connect(placesPath) c = conn.cursor() c.execute("SELECT host FROM moz_hosts") results = c.fetchall() debug = "Results of 'SELECT host FROM moz_hosts': " + \ str(results) self.logger.log(LogPriority.DEBUG, debug) # Results are inside of a tuple for item in results: urls.append(item[0]) badUrls = [] urls = list(set(urls)) if urls: self.logger.log(LogPriority.DEBUG, "URLs found: " + str(urls)) for url in urls: approved = self.approvedDomainsCi.getcurrvalue() foundApproved = False for site in approved: if re.search(site, url): foundApproved = True if not foundApproved: compliant = False badUrls.append(url) if badUrls: self.detailedresults = "URLs found in root's " + \ "Firefox history that are not on the approved " + \ "list: " + ", ".join(badUrls) + "\n" self.logger.log(LogPriority.DEBUG, "Bad URLs found: " + str(badUrls)) self.compliant = compliant except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def getFirefoxDirs(self): """Discover the randomly-generated Firefox profile directory(ies) for the root user. :returns: ffDirs - List of Firefox profile directories on the system """ # It's possible to have several FF profiles for each user account. This # method will therefore return a list. ffDirs = [] homeDir = "/root" ffParent = homeDir + "/.mozilla/firefox" if os.path.exists(ffParent): profileDirs = glob(ffParent + "/*.default") debug = "Found the following Firefox profile directories: " + \ str(profileDirs) self.logger.log(LogPriority.DEBUG, debug) for pDir in profileDirs: # Since we gave glob the full path, the returned list will # have the full path for each entry ffDirs.append(pDir) return ffDirs
class ConfigureKerberos(Rule): '''@author: Ekkehard J. Koch''' def __init__(self, config, environ, logdispatcher, statechglogger): """ @param config: @param environ: @param logdispatcher: @param statechglogger: """ Rule.__init__(self, config, environ, logdispatcher, statechglogger) self.rulenumber = 255 self.rulename = 'ConfigureKerberos' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = [] self.applicable = {'type': 'white', 'family': 'linux', 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} # This if/else statement fixes a bug in Configure Kerberos that # occurs on Debian systems due to the fact that Debian has no wheel # group by default. if self.environ.getosfamily() == 'darwin': self.files = {"krb5.conf": {"path": "/etc/krb5.conf", "remove": False, "content": MACKRB5, "permissions": 0o644, "owner": os.getuid(), "group": "wheel", "eventid": str(self.rulenumber).zfill(4) + "krb5"}, "edu.mit.Kerberos": {"path": "/Library/Preferences/edu.mit.Kerberos", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "Kerberos"}, "edu.mit.Kerberos.krb5kdc.launchd": {"path": "/Library/Preferences/edu.mit.Kerberos.krb5kdc.launchd", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "krb5kdc"}, "kerb5.conf": {"path": "/etc/kerb5.conf", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "kerb5"}, "edu.mit.Kerberos.kadmind.launchd": {"path": "/Library/Preferences/edu.mit.Kerberos.kadmind.launchd", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "kadmind"}, } else: self.files = {"krb5.conf": {"path": "/etc/krb5.conf", "remove": False, "content": LINUXKRB5, "permissions": 0o644, "owner": "root", "group": "root", "eventid": str(self.rulenumber).zfill(4) + "krb5"}} self.ch = CommandHelper(self.logdispatch) self.fh = FileHelper(self.logdispatch, self.statechglogger) if self.environ.getosfamily() == 'linux': self.ph = Pkghelper(self.logdispatch, self.environ) self.filepathToConfigure = [] for filelabel, fileinfo in sorted(self.files.items()): if fileinfo["remove"]: msg = "Remove if present " + str(fileinfo["path"]) else: msg = "Add or update if needed " + str(fileinfo["path"]) self.filepathToConfigure.append(msg) self.fh.addFile(filelabel, fileinfo["path"], fileinfo["remove"], fileinfo["content"], fileinfo["permissions"], fileinfo["owner"], fileinfo["group"], fileinfo["eventid"] ) # Configuration item instantiation datatype = "bool" key = "CONFIGUREFILES" instructions = "When Enabled will fix these files: " + \ str(self.filepathToConfigure) default = True self.ci = self.initCi(datatype, key, instructions, default) def report(self): '''run report actions for configure kerberos determine compliance status of the current system return True if compliant, False if non-compliant :returns: self.compliant :rtype: bool @author: ??? @change: Breen Malmberg - 2/23/2017 - added doc string; added const checks preamble to report and fix methods ''' self.compliant = True self.detailedresults = "" # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [MACKRB5, LINUXKRB5] if not self.checkConsts(constlist): self.compliant = False self.detailedresults = "\nPlease ensure that the constants: MACKRB5, LINUXKRB5, in localize.py, are defined and are not None. This rule will not function without them." self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant try: if self.environ.getosfamily() == 'linux': packagesRpm = ["pam_krb5", "krb5-libs", "krb5-workstation", "sssd-krb5", "sssd-krb5-common"] packagesDeb = ["krb5-config", "krb5-user", "libpam-krb5"] packagesSuse = ["pam_krb5", "sssd-krb5", "sssd-krb5-common", "krb5-client", "krb5"] if self.ph.determineMgr() == "apt-get": self.packages = packagesDeb elif self.ph.determineMgr() == "zypper": self.packages = packagesSuse else: self.packages = packagesRpm for package in self.packages: if not self.ph.check(package) and self.ph.checkAvailable(package): self.compliant = False self.detailedresults += package + " is not installed\n" if not self.fh.evaluateFiles(): self.compliant = False self.detailedresults += self.fh.getFileMessage() except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += str(traceback.format_exc()) self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): '''run fix actions :returns: self.rulesuccess :rtype: bool @author: ??? @change: Breen Malmberg - 2/23/2017 - added doc string; added checkconsts preamble to ensure the rule does not attempt to run without requied information (from localize.py) ''' self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [MACKRB5, LINUXKRB5] if not self.checkConsts(constlist): fixsuccess = False self.formatDetailedResults("fix", fixsuccess, self.detailedresults) return fixsuccess try: eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.ci.getcurrvalue(): pkgsToInstall = [] if self.environ.getosfamily() == 'linux': for package in self.packages: if not self.ph.check(package): if self.ph.checkAvailable(package): pkgsToInstall.append(package) for package in pkgsToInstall: if self.ph.install(package): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "pkghelper", "pkgname": package, "startstate": "removed", "endstate": "installed"} self.statechglogger.recordchgevent(myid, event) else: self.rulesuccess = False self.detailedresults += "Installation of " + package + " did not succeed.\n" if not self.fh.fixFiles(): self.rulesuccess = False self.detailedresults += self.fh.getFileMessage() else: self.rulesuccess = False self.detailedresults = str(self.ci.getkey()) + " was disabled. No action was taken!" except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += str(traceback.format_exc()) self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class ConfigureAIDE(Rule): """Install and configure Advanced Intrusion Detection Environment (AIDE). This rule is optional and will install and configure AIDE when it is run.""" def __init__(self, config, environ, logger, statechglogger): """ private method to initialize the module :param config: configuration object instance :param environ: environment object instance :param logger: logdispatcher object instance :param statechglogger: statechglogger object instance """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 110 self.rulename = 'ConfigureAIDE' self.formatDetailedResults("initialize") self.sethelptext() self.guidance = ['NSA(2.1.3)', 'cce-4209-3'] # init CIs datatype = 'bool' key = 'CONFIGUREAIDE' instructions = 'If you set the ConfigureAIDE variable to yes, or ' + \ 'true, ConfigureAIDE will install and set up the Advanced ' + \ 'Intrusion Detection Environment on this system.' default = False self.applicable = {'type': 'white', 'family': ['linux']} self.ci = self.initCi(datatype, key, instructions, default) datatype2 = 'string' key2 = 'AIDEJOBTIME' instructions2 = """This string contains the time when the cron job for /usr/sbin/aide --check will run in /etc/crontab. The default value is 00 18 * * 07 (which means weekly, on sundays at 6pm)""" default2 = "00 18 * * 07" self.aidetime = self.initCi(datatype2, key2, instructions2, default2) pattern = "^([0-9]{1,2})\s+([0-9]{1,2})\s+(\*|(3[0-1]|[0-2]?[0-9]))\s+(\*|(0[0-9]|[0-1]?[0-2]))\s+(\*|([0]?[0-7]))$" self.aidetime.setregexpattern(pattern) def report(self): """Check if AIDE is installed and properly configured. If the config is correct then the self.compliant, self.detailed results and self.currstate properties are updated to reflect the system status. self.rulesuccess will be updated if the rule does not succeed. :return: self.compliant :rtype: bool """ try: self.compliant = True self.detailedresults = "" self.init_objs() try: self.set_aide_paths() self.set_aide_cron_job() except: pass if not self.report_aide_installed(): self.compliant = False else: if not self.report_aide_conf(): self.compliant = False if not self.report_aide_cronjob(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def init_objs(self): """ """ self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) def set_aide_paths(self): """ """ self.aide_conf_file = "" aide_conf_paths = ["/etc/aide/aide.conf", "/etc/aide.conf"] for p in aide_conf_paths: if os.path.isfile(p): self.aide_conf_file = p break self.aide_package = "aide" self.aide_cron_file = "/etc/cron.d/stonix_aide_check" self.aide_bin_path = "" aide_bin_paths = ["/usr/sbin/aide", "/usr/bin/aide"] for p in aide_bin_paths: if os.path.isfile(p): self.aide_bin_path = p break self.crontab_bin = "" crontab_bin_locs = ["/bin/crontab", "/sbin/crontab"] for b in crontab_bin_locs: if os.path.isfile(b): self.crontab_bin = b break def set_aide_cron_job(self): """ """ aidetime = self.aidetime.getcurrvalue() if not aidetime: self.aide_cron_job = "" else: if self.ph.manager == "apt-get": self.aide_cron_job = str( self.aidetime.getcurrvalue() ) + " root nice -n 19 " + self.aide_bin_path + " -c " + self.aide_conf_file + " --check" else: self.aide_cron_job = str(self.aidetime.getcurrvalue( )) + " root nice -n 19 " + self.aide_bin_path + " --check" def report_aide_installed(self): """ :return: compliant :rtype: bool """ compliant = True if not self.ph.check(self.aide_package): compliant = False self.detailedresults += "\naide is not installed" return compliant def report_aide_conf(self): """ :return: compliant :rtype: bool """ compliant = True aide_conf_dict = { "/boot": "R", "/etc": "R", "/lib": "R", "/lib64": "R", "/sbin$": "R" } tmppath = self.aide_conf_file + ".stonixtmp" self.aide_conf_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.aide_conf_file, tmppath, aide_conf_dict, "present", "space") if not self.aide_conf_editor.report(): compliant = False self.detailedresults += "\nThe following aide conf file options are incorrect:\n" + "\n".join( self.aide_conf_editor.fixables) return compliant def report_aide_cronjob(self): """ :return: compliant :rtype: bool """ compliant = True if not os.path.isfile(self.aide_cron_file): compliant = False self.detailedresults += "\naide cron job not found" return compliant def fix(self): """Attempt to install and configure AIDE. self.rulesuccess will be updated if the rule does not succeed. :return: self.rulesuccess - True if fix succeeded; False if not :rtype: bool """ self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: if not self.ci.getcurrvalue(): self.detailedresults += '\nThis rule is currently not enabled, so nothing was done' self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not self.fix_aide_installed(): self.rulesuccess = False if not self.fix_aide_conf(): self.rulesuccess = False if not self.fix_aide_init(): self.rulesuccess = False if not self.fix_aide_cronjob(): self.rulesuccess = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fix_aide_conf(self): """ :return: success :rtype: bool """ success = True self.logger.log(LogPriority.DEBUG, "Fixing aide configuration file") if not self.aide_conf_editor.fix(): success = False elif not self.aide_conf_editor.commit(): success = False if not success: self.logger.log(LogPriority.DEBUG, "Failed to configure aide.conf") return success def fix_aide_cronjob(self): """ :return: success :rtype: bool """ success = True self.logger.log(LogPriority.DEBUG, "Creating aide cron job") try: f = open(self.aide_cron_file, "w") f.write(self.aide_cron_job) f.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.aide_cron_file} self.statechglogger.recordchgevent(myid, event) except: success = False self.logger.log(LogPriority.DEBUG, "Failed to create aide cron job") return success def fix_aide_installed(self): """ :return: success :rtype: bool """ success = True self.logger.log(LogPriority.DEBUG, "Installing aide package") if not self.ph.install(self.aide_package): success = False self.logger.log(LogPriority.DEBUG, "Failed to install aide package") if not self.ph.checkAvailable(self.aide_package): self.logger.log(LogPriority.DEBUG, "aide package not available on this system") else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "pkghelper", "pkgname": self.aide_package, "startstate": "removed", "endstate": "installed" } self.statechglogger.recordchgevent(myid, event) self.set_aide_paths() self.set_aide_cron_job() self.report_aide_conf() self.report_aide_cronjob() return success def fix_aide_init(self): """ :return: success :rtype: bool """ success = True self.logger.log(LogPriority.DEBUG, "Initializing aide database") if self.ph.manager == "apt-get": aide_init_cmd = "aideinit" else: aide_init_cmd = self.aide_bin_path + " --init" self.ch.executeCommand(aide_init_cmd) retcode = self.ch.getReturnCode() if retcode != 0: success = False errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: # to start using the aide database created by aide init, # remove the 'new' part of the aide database string # this must be done before check can be run # (https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/security_guide/sec-using-aide) if os.path.isfile('/var/lib/aide/aide.db.new.gz'): newaidedb = '/var/lib/aide/aide.db.new.gz' aidedb = '/var/lib/aide/aide.db.gz' elif os.path.isfile('/var/lib/aide/aide.db.new'): newaidedb = '/var/lib/aide/aide.db.new' aidedb = '/var/lib/aide/aide.db' else: newaidedb = "" aidedb = "" if bool(newaidedb and aidedb): os.rename(newaidedb, aidedb) os.chmod(aidedb, 0o600) os.chown(aidedb, 0, 0) else: self.logger.log(LogPriority.DEBUG, "Failed to locate aide database") success = False return success
class SSHTimeout(Rule): '''This rule will configure the ssh timeout period for ssh sessions, if ssh is installed. @author: dwalker ''' def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rootrequired = True self.rulenumber = 127 self.rulename = 'SSHTimeout' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.boolCi = self.initCi( "bool", "SSHTIMEOUTON", "To disable this rule set the value " + "of SSHTIMEOUTON to False", True) self.intCi = self.initCi( "int", "SSHTIMEOUT", "Set your preferred timeout value here, " + "in seconds. Default is 900 (15 minutes).", 900) self.guidance = ['NSA 3.5.2.3'] self.iditerator = 0 self.editor = "" self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.ph = Pkghelper(self.logger, self.environ) def report(self): '''SSHTimeout.report(): produce a report on whether or not a valid time for timing out of ssh is set. @author: D.Walker ''' try: self.detailedresults = "" compliant = True results = "" timeout = self.intCi.getcurrvalue() if self.environ.getostype() == "Mac OS X": self.path = '/private/etc/ssh/sshd_config' self.tpath = '/private/etc/ssh/sshd_config.tmp' else: self.path = '/etc/ssh/sshd_config' self.tpath = '/etc/ssh/sshd_config.tmp' if self.ph.manager == "zypper": openssh = "openssh" else: openssh = "openssh-server" if not self.ph.check(openssh): self.compliant = True self.detailedresults += "Package " + openssh + " is not installed.\nNothing to configure." self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant self.ssh = { "ClientAliveInterval": str(timeout), "ClientAliveCountMax": "0" } if os.path.exists(self.path): compliant = True kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tpath, self.ssh, intent, "space") if not self.editor.report(): compliant = False results += "Settings in " + self.path + " are not " + \ "correct\n" if not checkPerms(self.path, [0, 0, 0o644], self.logger): compliant = False results += self.path + " permissions are incorrect\n" else: compliant = False results += self.path + " does not exist\n" self.detailedresults = results self.compliant = compliant except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): '''SSHTimeout.fix(): set the correct values in /etc/ssh/sshd_config so that ssh sessions time out appropriately. @author: D.Walker ''' try: if not self.boolCi.getcurrvalue(): return debug = "inside fix method\n" self.logger.log(LogPriority.DEBUG, debug) created = False self.iditerator = 0 success = True self.detailedresults = "" debug = "" # clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getostype() != "Mac OS X": if self.ph.manager == "zypper": openssh = "openssh" else: openssh = "openssh-server" if not self.ph.check(openssh): debug = "openssh-server is not installed in fix\n" self.logger.log(LogPriority.DEBUG, debug) if self.ph.checkAvailable(openssh): debug = "openssh-server is not available in fix\n" self.logger.log(LogPriority.DEBUG, debug) if not self.ph.install(openssh): debug = "Unable to install openssh-server\n" self.logger.log(LogPriority.DEBUG, debug) self.rulesuccess = False return else: cmd = self.ph.getRemove() + openssh event = { "eventtype": "commandstring", "command": cmd } self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) self.detailedresults += "Installed openssh-server\n" self.editor = KVEditorStonix( self.statechglogger, self.logger, "conf", self.path, self.tpath, self.ssh, "present", "space") self.editor.report() else: debug += "openssh-server not available to install\n" self.logger.log(LogPriority.DEBUG, debug) self.rulesuccess = False return if not os.path.exists(self.path): createFile(self.path, self.logger) created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tpath, self.ssh, "present", "space") self.editor.report() if os.path.exists(self.path): print("path exists\n") if not checkPerms(self.path, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o644], self.logger, self.statechglogger, myid): debug += "Unable to set Permissions \ for: " + self.editor.getPath() + "\n" success = False else: if not setPerms(self.path, [0, 0, 0o644], self.logger): success = False if self.editor.fixables: print(("editor has fixables and they are " + str(self.editor.fixables) + "\n")) if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): debug += "kveditor fix ran successfully\n" if self.editor.commit(): debug += "kveditor commit ran successfully\n" os.chown(self.path, 0, 0) os.chmod(self.path, 0o644) if re.search("linux", self.environ.getosfamily()): resetsecon(self.path) else: debug += "Unable to complete kveditor commit\n" success = False else: debug += "Unable to complete kveditor fix\n" success = False self.rulesuccess = success if debug: self.logger.log(LogPriority.DEBUG, debug) except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class PasswordExpiration(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 42 self.rulename = "PasswordExpiration" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.iditerator = 0 self.guidance = ["2.3.1.7"] self.applicable = {'type': 'black', 'family': ['darwin']} self.universal = "#The following lines were added by stonix\n" datatype = 'bool' key = 'PASSWORDEXPIRATION' instructions = "To disable this rule set the value of " + \ "PASSWORDEXPIRATION to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.libusercreate = False self.libuserinstall = False self.useraddcreate = False self.logindefcreate = False self.fixable, self.shadow = True, True self.editor1, self.editor2 = "", "" self.fixusers = [] ############################################################################### def report(self): try: self.detailedresults = "" self.ch = CommandHelper(self.logger) self.lockedpwds = '^\*LK\*|^!|^\*|^x$' if self.environ.getosfamily() == "linux": self.ph = Pkghelper(self.logger, self.environ) self.specs = {"PASS_MAX_DAYS": "180", "PASS_MIN_DAYS": "1", "PASS_MIN_LEN": "8", "PASS_WARN_AGE": "28"} if self.ph.manager in ("apt-get", "zypper"): # apt-get systems do not set min length in the same file # as other systems(login.defs) del self.specs["PASS_MIN_LEN"] self.shadowfile = "/etc/shadow" self.logdeffile = "/etc/login.defs" self.useraddfile = "/etc/default/useradd" self.libuserfile = "/etc/libuser.conf" self.compliant = self.reportLinux() elif self.environ.getosfamily() == "solaris": self.specs = {"PASSLENGTH": "8", "MINWEEKS": "1", "MAXWEEKS": "26", "WARNWEEKS": "4"} self.shadowfile = "/etc/shadow" self.logdeffile = "/etc/default/passwd" self.compliant = self.reportSolaris() elif self.environ.getosfamily() == "freebsd": self.specs = {"warnpassword": "******", "minpasswordlen": "8", "passwordtime": "180d"} self.shadowfile = "/etc/master.passwd" self.loginfile = "/etc/login.conf" self.compliant = self.reportFreebsd() except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant ############################################################################### def reportLinux(self): compliant1 = self.checklogindefs() compliant2 = self.chkShadow() compliant3 = self.chkUserAdd() compliant4 = self.checklibuser() if compliant1 and compliant2 and compliant3 and compliant4: return True else: return False ############################################################################### def reportSolaris(self): compliant1 = self.checklogindefs() compliant2 = self.chkShadow() if compliant1 and compliant2: return True else: return False ############################################################################### def reportFreebsd(self, specs): compliant1 = self.chkPasswd() compliant2 = self.chkLogin(specs) if compliant1 and compliant2: return True else: return False ############################################################################### def checklogindefs(self): '''report method for various distros of linux and solaris''' compliant = True debug = "" if not os.path.exists(self.logdeffile): compliant = False self.detailedresults += self.logdeffile + " file does not exist\n" elif not checkPerms(self.logdeffile, [0, 0, 0o644], self.logger): compliant = False self.detailedresults += self.logdeffile + " does not have " + \ "the correct permissions. Expected 644, found " + \ str(getOctalPerms(self.logdeffile)) + ".\n" tmpfile = self.logdeffile + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.logdeffile, tmpfile, self.specs, "present", "space") if not self.editor1.report(): self.detailedresults += self.logdeffile + " does not " + \ "contain the correct contents\n" debug = self.logdeffile + " doesn't contain the correct " + \ "contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False return compliant ############################################################################### def chkShadow(self): debug = "" compliant = True if os.path.exists(self.shadowfile): if self.ph.manager == "apt-get": statdata = os.stat(self.shadowfile) mode = stat.S_IMODE(statdata.st_mode) retval = getUserGroupName(self.shadowfile) if retval[0] != "root" or retval[1] != "shadow": compliant = False self.detailedresults += self.shadowfile + " ownership " + \ "is not correct (either owner is not root, or " + \ "group is not shadow).\n" if mode != 0o640: compliant = False self.detailedresults += self.shadowfile + " does not have " + \ "the correct permissions. Expected 640, found " + \ str(getOctalPerms(self.shadowfile)) + ".\n" elif not checkPerms(self.shadowfile, [0, 0, 0o400], self.logger) and \ not checkPerms(self.shadowfile, [0, 0, 0], self.logger): compliant = False self.detailedresults += self.shadowfile + " does not have " + \ "the correct permissions. Expected 400 or 0, found " + \ str(getOctalPerms(self.shadowfile)) + ".\n" contents = readFile(self.shadowfile, self.logger) if self.environ.getosfamily() == "solaris" or \ self.environ.getosfamily() == "linux": if self.environ.getosfamily() == "linux": whichid = "/usr/bin/id" elif self.environ.getosfamily() == "solaris": whichid = "/usr/xpg4/bin/id" for line in contents: badacct = False debug = "" if re.search("^\#", line) or re.match("^\s*$", line): continue if re.search(":", line): field = line.split(":") cmd = [whichid, "-u", field[0]] self.ch.executeCommand(cmd) output = self.ch.getOutputString().strip() error = self.ch.getError() if error: continue if output: if output.isdigit(): uid = int(output) else: uid = 100 else: continue try: if uid >= 500 and not re.search(self.lockedpwds, field[1]): for i in [3, 4, 5, 6]: if field[i]: val = field[i] if val.isdigit(): field[i] = int(field[i]) elif i == 6: field[i] = 99 else: field[i] = 0 elif i == 6: field[i] = 99 else: field[i] = 0 if field[3] != 1 or field[3] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Minimum age is not equal to 1\n" badacct = True if field[4] > 180 or field[4] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Expiration is not 180 or less\n" badacct = True if field[5] != 28 or field[5] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Password expiration warnings are " + \ "not set to 28 days\n" badacct = True if field[6] != 35 or field[6] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Account lock is not set to 35 days\n" badacct = True except IndexError: compliant = False debug = traceback.format_exc() debug += ' Index out of range\n' badacct = True if debug: self.logger.log(LogPriority.DEBUG, debug) if badacct: self.fixusers.append(field[0]) if self.environ.getosfamily() == 'freebsd': for line in contents: debug = "" if re.search("^\#", line) or re.match('^\s*$', line): continue if re.search(':', line): field = line.split(':') message = Popen(['/usr/bin/id', '-u', field[0]], stderr=PIPE, stdout=PIPE, shell=False) uid = message.stdout.readline() uid = uid.strip() message.stdout.close() if uid.isdigit(): uid = int(uid) else: uid = 100 try: if uid >= 500 and not re.search(self.lockedpwds, field[1]): for i in [5, 6]: if field[i]: val = field[i] if not val.isdigit(): field[i] = 0 else: field[i] = 0 if int(field[5]) > 180 or field[5] == "": self.shadow = False compliant = False debug += "expiration is not 180 or less" if int(field[6]) != 1 or field[6] == "": self.shadow = False compliant = False debug += "Account lock is not set to 1" except IndexError: self.shadow = False compliant = False debug = traceback.format_exc() debug += ' Index out of range' self.logger.log(LogPriority.DEBUG, debug) if debug: self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += self.shadowfile + " does not exist\n" compliant = False debug = "chkShadow method is returning " + str(compliant) + \ " compliance\n" self.logger.log(LogPriority.DEBUG, debug) return compliant ############################################################################### def chkUserAdd(self): compliant = True debug = "" if not os.path.exists(self.useraddfile): self.detailedresults += self.useraddfile + " file does not exist\n" compliant = False else: if not checkPerms(self.useraddfile, [0, 0, 0o600], self.logger): compliant = False self.detailedresults += self.useraddfile + " does not have " + \ "the correct permissions. Expected 600, found " + \ str(getOctalPerms(self.useraddfile)) + ".\n" contents = readFile(self.useraddfile, self.logger) found = False valcorrect = True for line in contents: if re.search("^\#", line) or re.match('^\s*$', line): continue if re.search('^INACTIVE', line.strip()) and re.search('=', line): found = True temp = line.split('=') if int(temp[1].strip()) <= -1 or int(temp[1].strip()) > 35: valcorrect = False break if not found: compliant = False self.detailedresults += "INACTIVE key was not found in " + \ self.useraddfile + "\n" if found and not valcorrect: compliant = False self.detailedresults += "INACTIVE key was found in " + \ self.useraddfile + ", but value is incorrect\n" debug += "chkUserAdd method is returning " + str(compliant) + \ " compliance\n" if debug: self.logger.log(LogPriority.DEBUG, debug) return compliant ############################################################################### def checklibuser(self): '''Private method to check the password hash algorithm settings in libuser.conf. @author: dwalker :returns: bool ''' compliant = True '''check if libuser is intalled''' if not self.ph.check("libuser"): '''if not, check if available''' if self.ph.checkAvailable("libuser"): self.detailedresults += "libuser available but not installed\n" return False else: '''not available, not a problem''' return True '''create a kveditor for file if it exists, if not, we do it in the setlibuser method inside the fix''' if os.path.exists(self.libuserfile): data = {"userdefaults": {"LU_SHADOWMAX": "", "LU_SHADOWMIN": "", "LU_SHADOWWARNING": "", "LU_UIDNUMBER": "", "LU_SHADOWINACTIVE": "", "LU_SHADOWEXPIRE": ""}} datatype = "tagconf" intent = "notpresent" tmppath = self.libuserfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") if not self.editor2.report(): debug = "/etc/libuser.conf doesn't contain the correct " + \ "contents\n" self.detailedresults += "/etc/libuser.conf doesn't " + \ "contain the correct contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.libuserfile + "\n" compliant = False else: self.detailedresults += "Libuser installed but libuser " + \ "file doesn't exist\n" compliant = False return compliant ############################################################################### def chkLogin(self): compliant = True if os.path.exists(self.loginfile): if not checkPerms(self.loginfile, [0, 0, 0o644], self.logger): compliant = False self.detailedresults += self.libuserfile + " does not have " + \ "the correct permissions. Expected 644, found " + \ str(getOctalPerms(self.libuserfile)) + ".\n" contents = readFile(self.loginfile, self.logger) iterator1 = 0 for line in contents: if re.search("^#", line) or re.match('^\s*$', line): iterator1 += 1 elif re.search('^default:\\\\$', line.strip()): found = True temp = contents[iterator1 + 1:] length2 = len(temp) - 1 iterator2 = 0 for line2 in temp: if re.search('^[^:][^:]*:\\\\$', line2): contents2 = temp[:iterator2] break elif iterator2 < length2: iterator2 += 1 elif iterator2 == length2: contents2 = temp[:iterator2] break else: iterator1 += 1 if contents2: for key in self.Fspecs: found = False for line in contents2: if re.search("^#", line) or re.match('^\s*$', line): continue elif re.search('^:' + key, line.strip()): if re.search('=', line): temp = line.split('=') if re.search(str(self.Fspecs[key]) + '(:\\\\|:|\\\\|\s)', temp[1]): found = True continue else: found = False break if not found: compliant = False return compliant else: self.detailedresults += self.loginfile + "does not exist. " + \ "Please note that the fix for this rule will not attempt " + \ "to create this file.\n" compliant = False debug = "chkLogin method is returning " + (compliant) + " compliance\n" self.logger.log(LogPriority.DEBUG, debug) return compliant def fix(self): try: if not self.ci.getcurrvalue(): return self.detailedresults = "" # clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getosfamily() == "linux": self.rulesuccess = self.fixLinux() if self.environ.getosfamily() == "solaris": self.rulesuccess = self.fixSolaris() if self.environ.getosfamily() == "freebsd": self.rulesuccess = self.fixFreebsd() except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess ############################################################################### def fixLinux(self): success1 = self.fixLogDef(self.specs) success2 = self.fixShadow() success3 = self.fixUserAdd() success4 = self.setlibuser() if success1 and success2 and success3 and success4: return True else: return False ############################################################################### def fixSolaris(self): success1 = self.fixLogDef() success2 = self.fixShadow() if success1 and success2: return True else: return False ############################################################################### def fixFreebsd(self): success1 = self.fixPasswd() success2 = self.fixLogin() if success1 and success2: return True else: return False ############################################################################### def fixLogDef(self, specs): success = True debug = "" if not os.path.exists(self.logdeffile): if createFile(self.logdeffile, self.logger): self.logindefcreate = True setPerms(self.logdeffile, [0, 0, 0o644], self.logger) tmpfile = self.logdeffile + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.logdeffile, tmpfile, specs, "present", "space") else: self.detailedresults += "Was not able to create " + \ self.logdeffile + " file\n" success = False if self.logindefcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.logdeffile} self.statechglogger.recordchgevent(myid, event) elif not checkPerms(self.logdeffile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.logdeffile, [0, 0, 0o644], self.logger, self.statechglogger, myid): debug += "permissions not correct on: " + \ self.logdeffile + "\n" success = False if self.editor1.fixables or self.editor1.removeables: if not self.logindefcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): debug += "fixLogDef editor.fix did not complete successfully\n" success = False elif not self.editor1.commit(): debug += "fixLogDef editor.commit did not complete successfully\n" success = False os.chown(self.logdeffile, 0, 0) os.chmod(self.logdeffile, 0o644) resetsecon(self.logdeffile) if debug: self.logger.log(LogPriority.DEBUG, debug) return success ############################################################################### def fixShadow(self): success = True if not os.path.exists(self.shadowfile): self.detailedresults += self.shadowfile + "does not exist. \ Will not perform fix on shadow file\n" return False if self.fixusers: contents = readFile(self.shadowfile, self.logger) if self.ph.manager == "apt-get": perms = [0, 42, 0o640] else: perms = [0, 0, 0o400] if not checkPerms(self.shadowfile, perms, self.logger) and \ not checkPerms(self.shadowfile, [0, 0, 0], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) setPerms(self.shadowfile, perms, self.logger, self.statechglogger, myid) tmpdate = strftime("%Y%m%d") tmpdate = list(tmpdate) date = tmpdate[0] + tmpdate[1] + tmpdate[2] + tmpdate[3] + "-" + \ tmpdate[4] + tmpdate[5] + "-" + tmpdate[6] + tmpdate[7] for user in self.fixusers: cmd = ["chage", "-d", date, "-m", "1", "-M", "180", "-W", "28", "-I", "35", user] self.ch.executeCommand(cmd) # We have to do some gymnastics here, because chage writes directly # to /etc/shadow, but statechglogger expects the new contents to # be in a temp file. newContents = readFile(self.shadowfile, self.logger) shadowTmp = "/tmp/shadow.stonixtmp" createFile(shadowTmp, self.logger) writeFile(shadowTmp, "".join(newContents) + "\n", self.logger) writeFile(self.shadowfile, "".join(contents) + "\n", self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': self.shadowfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.shadowfile, shadowTmp, myid) shutil.move(shadowTmp, self.shadowfile) os.chmod(self.shadowfile, perms[2]) os.chown(self.shadowfile, perms[0], perms[1]) resetsecon(self.shadowfile) return success ############################################################################### def fixUserAdd(self): success = True if not os.path.exists(self.useraddfile): if createFile(self.useraddfile, self.logger): self.useraddcreate = True setPerms(self.useraddfile, [0, 0, 0o600], self.logger) else: self.detailedresults += self.useraddfile + \ " could not be created\n" success = False if self.useraddcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.useraddfile} self.statechglogger.recordchgevent(myid, event) if not checkPerms(self.useraddfile, [0, 0, 0o600], self.logger): if not self.useraddcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.useraddfile, [0, 0, 0o600], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions on " + \ self.useraddfile success = False tempstring = "" contents = readFile(self.useraddfile, self.logger) found = False for line in contents: if re.search("^\#", line) or re.match('^\s*$', line): tempstring += line continue if re.search("^INACTIVE", line.strip()): if re.search("=", line): temp = line.split("=") if int(temp[1].strip()) <= -1 or \ int(temp[1].strip()) > 35: continue else: found = True tempstring += line else: continue elif re.search("^" + self.universal, line.strip()): continue else: tempstring += line if not found: tempstring += "INACTIVE=35\n" tmpfile = self.useraddfile + ".tmp" if not writeFile(tmpfile, tempstring, self.logger): return False if not self.useraddcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': self.useraddfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.useraddfile, tmpfile, myid) shutil.move(tmpfile, self.useraddfile) os.chown(self.useraddfile, 0, 0) os.chmod(self.useraddfile, 0o600) resetsecon(self.useraddfile) return success ############################################################################### def setlibuser(self): success = True debug = "" created = False data = {"userdefaults": {"LU_SHADOWMAX": "", "LU_SHADOWMIN": "", "LU_SHADOWWARNING": "", "LU_UIDNUMBER": "", "LU_SHADOWINACTIVE": "", "LU_SHADOWEXPIRE": ""}} '''check if installed''' if not self.ph.check("libuser"): '''if not installed, check if available''' if self.ph.checkAvailable("libuser"): '''if available, install it''' if not self.ph.install("libuser"): self.detailedresults += "Unable to install libuser\n" return False else: '''since we're just now installing it we know we now need to create the kveditor''' self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) datatype = "tagconf" intent = "notpresent" tmppath = self.libuserfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") self.editor2.report() else: return True if not os.path.exists(self.libuserfile): if not createFile(self.libuserfile, self.logger): self.detailedresults += "Unable to create libuser file\n" debug = "Unable to create the libuser file\n" self.logger.log(LogPriority.DEBUG, debug) return False created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.libuserfile} self.statechglogger.recordchgevent(myid, event) tmppath = self.libuserfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.libuserfile, tmppath, data, "notpresent", "openeq") self.editor2.report() if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.libuserfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions on " + \ self.libuserfile success = False elif not setPerms(self.libuserfile, [0, 0, 0o644], self.logger): success = False self.detailedresults += "Unable to set the " + \ "permissions on " + self.libuserfile + "\n" if self.editor2.removeables: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if self.editor2.fix(): if self.editor2.commit(): debug += "/etc/libuser.conf has been corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.libuserfile, 0, 0) os.chmod(self.libuserfile, 0o644) resetsecon(self.libuserfile) else: self.detailedresults += "/etc/libuser.conf " + \ "couldn't be corrected\n" success = False else: self.detailedresults += "/etc/libuser.conf couldn't " + \ "be corrected\n" success = False return success ############################################################################### def fixLogin(self): success = True tempstring = "" debug = "" if not os.path.exists(self.loginfile): self.detailedresults = self.loginfile + "does not exist. \ Will not perform fix on useradd file\n" return False if not checkPerms(self.loginfile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.loginfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False contents = readFile(self.loginfile, self.logger) iterator1 = 0 for line in contents: if re.search("^#", line) or re.match('^\s*$', line): iterator1 += 1 elif re.search('^default:\\\\$', line.strip()): contents1 = contents[:iterator1 + 1] temp = contents[iterator1 + 1:] length2 = len(temp) - 1 iterator2 = 0 for line2 in temp: if re.search('^[^:][^:]*:\\\\$', line2): contents3 = temp[iterator2:] contents2 = temp[:iterator2] break elif iterator2 < length2: iterator2 += 1 elif iterator2 == length2: contents2 = temp[:iterator2] break else: iterator1 += 1 if contents2: for key in self.Fspecs: iterator = 0 found = False for line in contents2: if re.search("^#", line) or re.match('^\s*$', line): iterator += 1 continue elif re.search('^:' + key, line.strip()): if re.search('=', line): temp = line.split('=') if re.search(str(self.Fspecs[key]) + '(:\\\\|:|\\\\|\s)', temp[1]): iterator += 1 found = True else: contents2.pop(iterator) else: iterator += 1 if not found: contents2.append('\t' + key + '=' + str(self.Fspecs[key]) + ':\\\\\n') final = [] for line in contents1: final.append(line) for line in contents2: final.append(line) for line in contents3: final.append(line) for line in final: tempstring += line debug += "tempstring to be written to: " + self.loginfile + "\n" self.logger.log(LogPriority.DEBUG, debug) tmpfile = self.loginfile + ".tmp" if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': self.loginfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.loginfile, tmpfile, myid) shutil.move(tmpfile, self.loginfile) os.chown(self.loginfile, 0, 0) os.chmod(self.loginfile, 0o644) resetsecon(self.loginfile) else: success = False return success