class DisableUbuntuDataCollection(Rule): 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.environ = environ self.rulenumber = 311 self.rulename = "DisableUbuntuDataCollection" self.mandatory = True self.rootrequired = True self.formatDetailedResults("initialize") self.guidance = [] self.applicable = {'type': 'white', 'os': {'Ubuntu': ['16.04', '+']}} self.sethelptext() datatype = "bool" key = "DISABLEUBUNTUDATACOLLECTION" instructions = """To prevent the diabling of data collection from this system, set the value of DISABLEUBUNTUDATACOLLECTION to False.""" default = True self.enabledCI = self.initCi(datatype, key, instructions, default) def report(self): '''Check for the existence of any of a number of data-collection and reporting utilities on the system report compliance status as not compliant if any exist report compliance status as compliant if none exist :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.ph = Pkghelper(self.logger, self.environ) self.compliant = True self.pkgslist = ["popularity-contest", "apport", "ubuntu-report"] self.removepkgs = [] try: for pkg in self.pkgslist: if self.ph.check(pkg): self.compliant = False self.detailedresults += "\nData collection utility: " + str( pkg) + " is still installed" self.removepkgs.append(pkg) except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = 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): '''Remove any data-collection and reporting utilities from the system report success status as True if all are removed report success status as False if any remain :returns: self.rulesuccess :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: if self.enabledCI.getcurrvalue(): eventlist = self.statechglogger.findrulechanges( self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) for pkg in self.removepkgs: if not self.ph.remove(pkg): self.detailedresults += "\nUnable to remove package: " + str( pkg) self.rulesuccess = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() + pkg event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) self.logger.log(LogPriority.DEBUG, "Removing package: " + str(pkg)) else: self.logger.log(LogPriority.DEBUG, "Rule was NOT enabled. Nothing was done.") 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 InstallVLock(Rule): '''This class installs the vlock package to enable screen locking vlock is the package name on opensuse 15+, debian, ubuntu kbd is the package name on opensuse 42.3-, rhel, fedora, centos (contains vlock package) references: https://pkgs.org/download/vlock https://access.redhat.com/discussions/3543671 ''' def __init__(self, config, environ, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 121 self.rulename = "InstallVLock" self.mandatory = True self.rootrequired = True self.formatDetailedResults("initialize") self.guidance = ["NSA 2.3.5.6"] self.applicable = {'type': 'white', 'family': ['linux', 'freebsd']} # Configuration item instantiation datatype = 'bool' key = 'INSTALLVLOCK' instructions = "To disable installation of the command line " + \ "screen lock program vlock set the value of INSTALLVLOCK to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.sethelptext() def set_pkg(self): '''set package name based on distro''' majorver = self.environ.getosmajorver() if self.ph.manager in ["yum", "dnf"]: self.pkg = "kbd" elif bool(self.ph.manager == "zypper" and majorver == "15"): self.pkg = "kbd" else: self.pkg = "vlock" def report(self): '''Perform a check to see if package is already installed. If so, there is no need to run Fix method :returns: self.compliant :rtype: bool @author: Derek T Walker ''' try: self.detailedresults = "" self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.compliant = True self.set_pkg() if not self.ph.check(self.pkg): self.compliant = False self.detailedresults += "\nvlock Package is NOT installed" else: self.detailedresults += "\nvlock Package is installed" except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = 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): '''The fix method will apply the required settings to the system. self.rulesuccess will be updated if the rule does not succeed. Attempt to install Vlock, record success or failure in event logger. :returns: self.rulesuccess :rtype: bool @author: Derek T Walker ''' try: self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 if not self.ci.getcurrvalue(): return self.rulesuccess # Clear out event history so only the latest fix is recorded eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) undocmd = self.ph.getRemove() if not self.ph.install(self.pkg): self.rulesuccess = False self.detailedresults += "\nFailed to install vlock package" else: undocmd += self.pkg self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": undocmd} self.statechglogger.recordchgevent(myid, event) self.detailedresults += "\nvlock Package was installed successfully" 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 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 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 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