class InstalledSoftwareVerification(Rule): def __init__(self, config, environ, logger, statechglogger): """Constructor""" Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.statechglogger = statechglogger self.rulenumber = 230 self.rulename = 'InstalledSoftwareVerification' self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = True self.guidance = [ 'NSA 2.1.3.2', 'CCE 14931-0', 'CCE-RHEL7-CCE-TBD 2.1.3.2.1' ] self.applicable = { 'type': 'white', 'os': { 'Red Hat Enterprise Linux': ['6.0', '+'], 'CentOS Linux': ['7.0', '+'] } } datatype = 'bool' key = 'FIXPERMISSIONS' instructions = 'If set to True, this rule will fix the permissions \ of the package for any file which has a permission deviation from the vendor \ default.' default = True self.fixPermsCi = self.initCi(datatype, key, instructions, default) self.sethelptext() def getInstalledPackages(self): """return a list of installed packages (as reported by rpm database) :returns: installedpackages :rtype: list @author: Breen Malmberg """ installedpackages = [] listinstalledcmd = "/usr/bin/rpm -qa" self.ch.executeCommand(listinstalledcmd) outputlist = self.ch.getOutput() retcode = self.ch.getReturnCode() if retcode == 0: installedpackages = outputlist else: errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) return installedpackages def report(self): """Compile a list of files not conforming to rpm package database permissions (Mode) report non-compliant if any are found else report compliant :returns: self.compliant :rtype: bool @author: Eric Ball @author: Breen Malmberg @change: Breen Malmberg - 07/30/2018 - complete re-write of method """ self.detailedresults = "" self.compliant = True self.ch = CommandHelper(self.logger) reportcmd = "/usr/bin/rpm -V --nosignature --nolinkto --nofiledigest --nosize --nomtime --nordev --nocaps " self.badpermfiles = [] self.badpermpkgs = {} self.badgroupfiles = [] self.badownerfiles = [] self.badhashfiles = [] try: self.logger.log( LogPriority.DEBUG, "Searching for files with incorrect permissions...") installedpkgs = self.getInstalledPackages() for pkg in installedpkgs: self.ch.executeCommand(reportcmd + pkg) outputlist = self.ch.getOutput() self.badpermpkgs[pkg] = [] for line in outputlist: # search for bad permissions if re.search("^.*(\.+M|M\.+)", line, re.IGNORECASE): sline = line.split() self.badpermpkgs[pkg].append(sline[len(sline) - 1]) self.badpermfiles.append(sline[len(sline) - 1]) # search for bad group ownership if re.search("^.*(\.+G|G\.+)", line, re.IGNORECASE): sline = line.split() self.badgroupfiles.append(sline[len(sline) - 1]) # search for bad ownership (user) if re.search("^.*(\.+U|U\.+)", line, re.IGNORECASE): sline = line.split() self.badownerfiles.append(sline[len(sline) - 1]) # search for bad md5 hash if re.search("^.*(\.+5|5\.+)", line, re.IGNORECASE): sline = line.split() self.badhashfiles.append(sline[len(sline) - 1]) if self.badpermfiles: self.compliant = False self.detailedresults += "\nThe following package files have incorrect permissions:\n" + "\n".join( self.badpermfiles) if self.badgroupfiles: self.compliant = False self.detailedresults += "\n\nThe following package files have bad group ownership:\n" + "\n".join( self.badgroupfiles) if self.badownerfiles: self.compliant = False self.detailedresults += "\n\nThe following package files have bad ownership:\n" + "\n".join( self.badownerfiles) if self.badhashfiles: self.compliant = False self.detailedresults += "\n\nThe following package files have bad MD5 checksums:\n" + "\n".join( self.badhashfiles) except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.compliant = False 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 fix(self): """The fix method changes permissions to the package defaults. :returns: self.rulesuccess :rtype: bool @author: Eric Ball @author: Breen Malmberg @change: Breen Malmberg - 07/30/2018 - re-write of entire method """ self.detailedresults = "" self.rulesuccess = True fixpermscmd = "/usr/bin/rpm --setperms " try: if not self.fixPermsCi.getcurrvalue(): return self.rulesuccess for pkg in self.badpermpkgs: if self.badpermpkgs[pkg]: self.ch.executeCommand(fixpermscmd + pkg) retcode = self.ch.getReturnCode() if retcode != 0: self.rulesuccess = False self.detailedresults += "\n\nPlease note that we will not attempt to fix ownership, group ownership, or bad md5 checksums. For suggestions on what to do if files are found with these issues, please see the rule's help text." self.detailedresults += "\nIt is expected that this rule will still be non-compliant after fix if files are found with incorrect ownership or group ownership." except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += 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 KVEditor(object): '''The main parent class for The Key Value Editor Class group. Call the validate def to see if the specified values are either present or not based on the intent desired, "present" or "notpresent", where, "present" means the values you set are desired in the configuration file and "notpresent" means the values you set are not desired in the configuration file. If you have a mixture of desired key-values and undesired key-values you must set intent for each time you change intents then setData with the new data. Do not run commit until all''' def __init__(self, stchlgr, logger, kvtype, path, tmpPath, data, intent="", configType="", output=""): ''' KVEditor constructor @param stchlgr: StateChgLogger object @param logger: logger object @param kvtype: Type of key-value file. Valid values: "tagconf", "conf", "defaults", "profiles" @param path: Path to key-value file @param tmpPath: Path to temp file for key-value list @param data: Dict of key-value data @param intent: "present" or "notpresent" @param configType: Specify how the config options are separated. Valid values: "space", "openeq", "closedeq" @param output: Output of profiler command, used only by KVAProfiles ''' self.kvtype = kvtype self.path = path self.tmpPath = tmpPath self.logger = logger self.configType = configType self.data = data self.output = output self.detailedresults = "" self.missing = [] self.fixables = {} self.fixlist = [] self.removeables = {} self.intent = intent self.container = {} self.iditerator = 0 self.idcontainer = [] if self.kvtype == "tagconf": if not self.getPath: return None self.editor = KVATaggedConf.KVATaggedConf(self.path, self.tmpPath, self.intent, self.configType, self.logger) elif self.kvtype == "conf": if not self.getPath: return None self.editor = KVAConf.KVAConf(self.path, self.tmpPath, self.intent, self.configType, self.logger) elif self.kvtype == "defaults": self.editor = KVADefault.KVADefault(self.path, self.logger, self.data) elif self.kvtype == "profiles": self.editor = KVAProfiles.KVAProfiles(self.logger, self.path) else: self.detailedresults = "Not one of the supported kveditor types" self.logger.log(LogPriority.DEBUG, ["KVEditor.__init__", self.detailedresults]) return None def setData(self, data): if data is None: return False elif data == "": return False else: self.data = data return True def getData(self): return self.data def updateData(self, data): self.data = data def setIntent(self, intent): if intent == "present" or intent == "notpresent": self.intent = intent self.editor.setIntent(intent) return True else: return False def getIntent(self): return self.intent def setPath(self, path): if not os.path.exists(path): self.detailedresults = "File path does not exist" self.logger.log(LogPriority.INFO, ["KVEditor", self.detailedresults]) return False else: self.path = path self.editor.setPath(path) return True def getPath(self): if not os.path.exists(self.path): debug = "File path does not exist\n" self.logger.log(LogPriority.DEBUG, debug) return False else: return self.path def setTmpPath(self, tmpPath): self.tmpPath = tmpPath self.editor.setTmpPath(tmpPath) return True def getTmpPath(self): return self.tmpPath def getType(self): return self.kvtype def setConfigType(self, configType): self.configType = configType self.editor.setConfigType(configType) return True def getConfigType(self): return self.configType def validate(self): try: status = False if self.kvtype == "defaults": status = self.validateDefaults() elif self.kvtype == "plist": status = self.validatePlist() elif self.kvtype == "conf": status = self.validateConf() elif self.kvtype == "tagconf": status = self.validateTag() elif self.kvtype == "profiles": status = self.validateProfiles() else: status = "invalid" debug = "KVEditor is returning " + str(status) + " back to " + \ "KVEditorStonix.report()\n" self.logger.log(LogPriority.DEBUG, debug) return status except(KeyboardInterrupt, SystemExit): raise except Exception: raise def update(self): try: status = False if self.kvtype == "defaults": status = self.updateDefaults() elif self.kvtype == "plist": status = self.updatePlist() elif self.kvtype == "conf": status = self.updateConf() elif self.kvtype == "tagconf": status = self.updateTag() elif self.kvtype == "profiles": status = self.updateProfiles() else: status = False debug = "KVEditor is returning " + str(status) + " back to " + \ "KVEditorStonix.fix()\n" self.logger.log(LogPriority.DEBUG, debug) return status except(KeyboardInterrupt, SystemExit): raise except Exception: raise def validateDefaults(self): if isinstance(self.data, dict): if not self.checkDefaults(self.data): return False else: return False if self.editor.validate(): return True else: return False def updateDefaults(self): if self.editor.update(): debug = "KVEditor.updateDefaults() is returning True to " + \ "KVEditor.update()\n" self.logger.log(LogPriority.DEBUG, debug) return True else: debug = "KVEditor.updateDefaults() is returning False to " + \ "KVEditor.update()\n" self.logger.log(LogPriority.DEBUG, debug) return False def checkDefaults(self, data): for k, v in data.iteritems(): if isinstance(v, dict): retval = self.checkDefaults(v) return retval elif isinstance(v, list): if len(v) == 2 or len(v) == 3: return True else: return False else: return False def setCurrentHostbool(self, val): self.editor.currentHost = val def validateConf(self): validate = True if not self.checkConf(): return False if self.intent == "present": for k, v in self.data.iteritems(): retval = self.editor.validate(k, v) if retval == "invalid": validate = "invalid" elif isinstance(retval, list): self.fixables[k] = retval validate = False elif not retval: validate = False self.fixables[k] = v if self.intent == "notpresent": for k, v in self.data.iteritems(): retval = self.editor.validate(k, v) if retval == "invalid": validate = "invalid" elif isinstance(retval, list): self.removeables[k] = retval validate = False elif retval is True: validate = False self.removeables[k] = v if validate == "invalid": debug = "KVEditor.validateConf() is returning invalid to " + \ "KVEditor.validate()\n" elif validate: debug = "KVEditor.validateConf() is returning True to " + \ "KVEditor.validate()\n" else: debug = "KVEditor.validateConf() is returning False to " + \ "KVEditor.validate()\n" self.logger.log(LogPriority.DEBUG, debug) return validate def updateConf(self): if self.fixables or self.removeables: if self.editor.update(self.fixables, self.removeables): debug = "KVEditor.updateConf() is returning True to " + \ "KVEditor.update()\n" self.logger.log(LogPriority.DEBUG, debug) return True else: debug = "KVEditor.updateConf() is returning False to " + \ "KVEditor.update()\n" self.logger.log(LogPriority.DEBUG, debug) return False def checkConf(self): if isinstance(self.data, dict): return True else: return False def validateTag(self): validate = True keyvals = {} if not self.checkTag(): return False if self.intent == "present": for tag in self.data: keyvals = self.editor.getValue(tag, self.data[tag]) if keyvals == "invalid": validate = "invalid" elif isinstance(keyvals, dict): self.fixables[tag] = keyvals validate = False if self.intent == "notpresent": for tag in self.data: keyvals = self.editor.getValue(tag, self.data[tag]) if keyvals == "invalid": validate = "invalid" elif isinstance(keyvals, dict): self.removeables[tag] = keyvals validate = False if validate == "invalid": debug = "KVEditor.validateTag() is returning invalid to " + \ "KVEditor.validate()\n" elif validate: debug = "KVEditor.validateTag() is returning True to " + \ "KVEditor.validate()\n" else: debug = "KVEditor.validateTag() is returning False to " + \ "KVEditor.validate()\n" self.logger.log(LogPriority.DEBUG, debug) return validate def updateTag(self): if self.editor.setValue(self.fixables, self.removeables): debug = "KVEditor.updateTag() is returning True to " + \ "KVEditor.update()\n" self.logger.log(LogPriority.DEBUG, debug) return True else: debug = "KVEditor.updateTag() is returning False to " + \ "KVEditor.update()\n" self.logger.log(LogPriority.DEBUG, debug) return False def checkTag(self): if isinstance(self.data, dict): for tag in self.data: if not isinstance(self.data[tag], dict): return False return True else: return False def validateProfiles(self): ''' @since: 3/10/2016 @author: dwalker @var self.data: A dictionary in the form of {k: {v: ["numberValue", "datatype", "acceptableDeviation"(optional)], v: ["", "", ""], v: ["", "", ""], . . .}} @var: k: The profile sub-identifier e.g. com.apple.mobiledevice.passwordpolicy @var v: The profile data key-value pairs in a dictionary e.g. allowSimple that will appear in the output of the system_profiler command within the first opening brace after the profile sub-identifier. v also contains an associated list containing: [a,b,c] a) the value on the other side of the = sign b) whether that value is an integer(int) or a boolean(bool) c) (optional) whether the value present after the = sign(a), if an int, can be lower(less) or higher(more) in order to detect and represent stringency (see self.data description above). @return: Value returned from validate method in factory sub-class @rtype: bool ''' cmd = ["/usr/sbin/system_profiler", "SPConfigurationProfileDataType"] self.ch = CommandHelper(self.logger) if self.ch.executeCommand(cmd): self.output = self.ch.getOutput() retval = True if self.output: for k, v in self.data.iteritems(): retval = self.editor.validate(self.output, k, v) if not retval: return False else: debug = "There are no profiles installed" self.logger.log(LogPriority.DEBUG, debug) return False return True def updateProfiles(self): retval = self.editor.update() return retval def commit(self): if self.kvtype == "defaults" or self.kvtype == "profiles": retval = self.editor.commit() return retval else: retval = self.editor.commit() self.fixables = {} self.removeables = {} return retval def removekey(self, d, key): r = dict(d) del r[key] return r
class Zypper(object): """The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. @author: Derek T Walker @change: 2012/08/08 dwalker - Original Implementation @change: 2014/09/10 dkennel - Added -n option to search command string @change: 2014/12/24 bemalmbe - fixed a typo in the old search string @change: 2014/12/24 bemalmbe - changed search strings to be match exact and search for installed or available separately @change: 2014/12/24 bemalmbe - fixed multiple pep8 violations @change: 2015/08/20 eball - Added getPackageFromFile and self.rpm var """ def __init__(self, logger): self.logger = logger self.detailedresults = "" self.ch = CommandHelper(self.logger) self.install = "/usr/bin/zypper --non-interactive install " self.remove = "/usr/bin/zypper --non-interactive remove " self.searchi = "/usr/bin/zypper --non-interactive search --match-exact -i " self.searchu = "/usr/bin/zypper --non-interactive search --match-exact -u " self.rpm = "/bin/rpm -q " ############################################################################### def installpackage(self, package): """Install a package. Return a bool indicating success or failure. @param string package : Name of the package to be installed, must be recognizable to the underlying package manager. @return: bool @author: dwalker @change: 12/24/2014 - bemalmbe - fixed method doc string formatting """ try: installed = False self.ch.executeCommand(self.install + package) output = self.ch.getOutputString() if self.ch.getReturnCode() == 0: if search("Abort, retry, ignore", output): self.detailedresults += "There is an error contacting " + "one or more repos, aborting\n" return False self.detailedresults += package + " pkg installed successfully\n" installed = True else: self.detailedresults += package + " pkg not able to install\n" self.logger.log(LogPriority.INFO, self.detailedresults) return installed except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) ############################################################################### def removepackage(self, package): """Remove a package. Return a bool indicating success or failure. @param string package : Name of the package to be removed, must be recognizable to the underlying package manager. @return: bool @author: dwalker @change: 12/24/2014 - bemalmbe - fixed method doc string formatting @change: 12/24/2014 - bemalmbe - fixed an issue with var 'removed' not being initialized before it was called """ removed = False try: self.ch.executeCommand(self.remove + package) output = self.ch.getOutputString() if self.ch.getReturnCode() == 0: if search("Abort, retry, ignore", output): self.detailedresults += "There is an error contacting " + "one or more repos, aborting\n" return False self.detailedresults += package + " pkg removed successfully\n" removed = True else: self.detailedresults += package + " pkg not able to be removed\n" self.logger.log(LogPriority.INFO, self.detailedresults) return removed except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) ############################################################################### def checkInstall(self, package): """ Check the installation status of a package. Return a bool; True if the package is installed. @param string package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return: bool @author: dwalker @change: 12/24/2014 - bemalmbe - fixed method doc string formatting @change: 12/24/2014 - bemalmbe - changed var name 'found' to 'installed' @change: 12/24/2014 - bemalmbe - now uses correct search syntax @change: 12/24/2014 - bemalmbe - removed detailedresults update on 'found but not installed' as this no longer applies to this method """ try: installed = False self.ch.executeCommand(self.searchi + package) if self.ch.getReturnCode() == 0: output = self.ch.getOutput() outputStr = self.ch.getOutputString() if search("Abort, retry, ignore", outputStr): self.detailedresults += "There is an error contacting " + "one or more repos, aborting\n" return False for line in output: if search(package, line): installed = True if installed: self.detailedresults += package + " pkg is installed\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: installed = False self.detailedresults += ( package + " pkg not found or may be \ misspelled\n" ) self.logger.log(LogPriority.DEBUG, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) ############################################################################### def checkAvailable(self, package): """ check if given package is available to install on the current system @param: package string name of package to search for @return: bool @author: dwalker @change: 12/24/2014 - bemalmbe - added method documentation @change: 12/24/2014 - bemalmbe - changed var name 'found' to 'available' @change: 12/24/2014 - bemalmbe - fixed search syntax and updated search variable name """ try: available = False self.ch.executeCommand(self.searchu + package) if self.ch.getReturnCode() == 0: output = self.ch.getOutput() for line in output: if search(package, line): available = True if available: self.detailedresults += package + " pkg is available\n" else: self.detailedresults += package + " pkg is not available\n" else: self.detailedresults = ( package + " pkg not found or may be \ misspelled\n" ) self.logger.log(LogPriority.INFO, self.detailedresults) return available except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) ############################################################################### def getPackageFromFile(self, filename): """Returns the name of the package that provides the given filename/path. @param: string filename : The name or path of the file to resolve @return: string name of package if found, None otherwise @author: Eric Ball """ try: self.ch.executeCommand(self.rpm + "-f " + filename) if self.ch.getReturnCode() == 0: return self.ch.getOutputString() else: return None except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def getInstall(self): """ return the install command string for the zypper pkg manager @return: string @author: dwalker @change: 12/24/2014 - bemalmbe - added method documentation """ return self.install ############################################################################### def getRemove(self): """ return the uninstall/remove command string for the zypper pkg manager @return: string @author: dwalker @change: 12/24/2014 - bemalmbe - added method documentation """ return self.remove
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 AptGet(object): """Linux specific package manager for distributions that use the apt-get command to install packages. @author: Derek T Walker @change: 2012/08/06 dwalker - Original Implementation @change: 2015/08/20 eball - Added getPackageFromFile """ def __init__(self, logger): self.logger = logger self.detailedresults = "" self.ch = CommandHelper(self.logger) self.install = "sudo DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get -y --force-yes install " self.remove = "/usr/bin/apt-get -y remove " ############################################################################### def installpackage(self, package): """Install a package. Return a bool indicating success or failure. @param string package : Name of the package to be installed, must be recognizable to the underlying package manager. @return bool : @author dwalker""" try: self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg installed successfully" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: # try to install for a second time self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg installed successfully" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: self.detailedresults = package + " pkg not able to install" self.logger.log(LogPriority.DEBUG, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def removepackage(self, package): """Remove a package. Return a bool indicating success or failure. @param string package : Name of the package to be removed, must be recognizable to the underlying package manager. @return bool : @author""" try: self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg removed successfully" self.logger.log(LogPriority.INFO, self.detailedresults) return True else: self.detailedresults = package + " pkg not able to be removed" self.logger.log(LogPriority.INFO, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def checkInstall(self, package): """Check the installation status of a package. Return a bool; True if the package is installed. @param: string package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return: bool : @author: dwalker""" try: stringToMatch = "(.*)" + package + "(.*)" self.ch.executeCommand(["/usr/bin/dpkg", "-l", package]) info = self.ch.getOutput() match = False for line in info: if search(stringToMatch, line): parts = line.split() if parts[0] == "ii": match = True break else: continue if match: self.detailedresults = package + " pkg found and installed\n" self.logger.log(LogPriority.INFO, self.detailedresults) return True else: self.detailedresults = package + " pkg not installed\n" self.logger.log(LogPriority.INFO, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def checkAvailable(self, package): try: found = False retval = call(["/usr/bin/apt-cache", "search", package], stdout=PIPE, stderr=PIPE, shell=False) if retval == 0: message = Popen(["/usr/bin/apt-cache", "search", package], stdout=PIPE, stderr=PIPE, shell=False) info = message.stdout.readlines() while message.poll() is None: continue message.stdout.close() for line in info: if search(package, line): found = True if found: self.detailedresults = package + " pkg is available" else: self.detailedresults = package + " pkg is not available" else: self.detailedresults = ( package + " pkg not found or may be \ misspelled" ) self.logger.log(LogPriority.DEBUG, self.detailedresults) return found except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise ############################################################################### def getPackageFromFile(self, filename): """Returns the name of the package that provides the given filename/path. @param: string filename : The name or path of the file to resolve @return: string name of package if found, None otherwise @author: Eric Ball """ try: self.ch.executeCommand("dpkg -S " + filename) if self.ch.getReturnCode() == 0: output = self.ch.getOutputString() pkgname = output.split(":")[0] return pkgname else: return None except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove
class AuditSSHKeys(Rule): """ This class audits for password-less ssh keys on the system """ def __init__(self, config, environ, logger, statechglogger): """ private method to initialize 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.environ = environ self.rulenumber = 62 self.rulename = 'AuditSSHKeys' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = ['LANL CAP', 'OpenSSH Security Best Practices'] self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} datatype = 'bool' key = 'AUDITSSHKEYS' instructions = "To prevent this rule from modifying permissions on ssh keys, set the value of AUDITSSHKEYS to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.localize() def localize(self): """determine which OS the system is, and set certain variables accordingly """ self.logger.log(LogPriority.DEBUG, "Running localize() ...") self.mac = False self.linux = False os = self.environ.getosfamily() if os == 'darwin': self.logger.log(LogPriority.DEBUG, "System OS type detected as: darwin") self.mac = True else: self.logger.log(LogPriority.DEBUG, "System OS type detected as: linux") self.linux = True def report(self): """check status of private ssh keys (whether they are encrypted with passwords or not) :returns: self.compliant - boolean; True if compliant, False if not compliant """ searchterm = "Proc-Type:" self.searchdirs = [] keylist = [] self.keydict = {} self.compliant = True self.detailedresults = "" self.ch = CommandHelper(self.logger) try: self.logger.log(LogPriority.DEBUG, "Getting list of user home directories...") self.searchdirs = self.get_search_dirs() self.logger.log(LogPriority.DEBUG, "Getting list of ssh keys...") keylist = self.get_key_list(self.searchdirs) if keylist: self.logger.log(LogPriority.DEBUG, "Searching list of ssh keys...") for key in keylist: self.keydict[key] = False f = open(key, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search(searchterm, line): self.keydict[key] = True for key in self.keydict: if not self.keydict[key]: self.compliant = False self.detailedresults += "\nThe SSH key: " + str(key) + " was made without a password!" if getOctalPerms(key) != 600: self.compliant = False self.detailedresults += "\nThe SSH key: " + str(key) + " has incorrect permissions" if self.compliant: self.detailedresults += "\nAll SSH keys on this system are encrypted" else: self.detailedresults += "\nNo SSH keys were found on this system." if not self.compliant: self.detailedresults += "\n\nThis rule's fix only changes permissions on insecure keys. We cannot fix keys which were made without a password." except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults = str(traceback.format_exc()) self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant def get_key_list(self, searchdirs): """walk the ssh directory/ies and build and return a list of private keys (file names) :param searchdirs: list of directories to search for private ssh keys :returns: keylist - list; list of ssh key files """ keylist = [] if not searchdirs: self.logger.log(LogPriority.DEBUG, "Parameter searchdirs was empty! Returning empty keylist...") return keylist try: self.logger.log(LogPriority.DEBUG, "Building keylist...") for loc in searchdirs: files = glob(loc + "*") for f in files: if os.path.isfile(f): fh = open(f, "r") contentlines = fh.readlines() fh.close() for line in contentlines: if re.search("BEGIN\s+\w+\s+PRIVATE KEY", line): keylist.append(f) self.logger.log(LogPriority.DEBUG, "Adding SSH key file: " + str(f) + " to keylist...") self.logger.log(LogPriority.DEBUG, "Finished building keylist") except Exception: raise return keylist def get_search_dirs(self): """build and return a list of search directories to look for ssh keys :returns: searchdirs - list; directories to search for ssh keys in """ searchdirs = [] try: if self.mac: # the system is mac-based getuserscmd = "/usr/bin/dscl . -list /Users NFSHomeDirectory" self.ch.executeCommand(getuserscmd) retcode = self.ch.getReturnCode() if retcode == "0": self.logger.log(LogPriority.DEBUG, "Command to get list of users' home directories ran successfully") output = self.ch.getOutput() self.logger.log(LogPriority.DEBUG, "Searching command output and building searchdirs list...") for line in output: sline = line.split() if sline[1] not in ["/var/empty", "/dev/null"]: if os.path.exists(sline[1] + "/.ssh/"): searchdirs.append(sline[1] + "/.ssh/") else: # the system is linux-based # determine the start of the user id's on this system (500 or 1000) self.logger.log(LogPriority.DEBUG, "Setting default uidstart to 500...") uidstart = 500 if os.path.exists('/etc/login.defs'): self.logger.log(LogPriority.DEBUG, "login defs file exists. Getting actual uid start value...") f = open('/etc/login.defs') contentlines = f.readlines() f.close() for line in contentlines: if re.search('^UID\_MIN\s+500', line, re.IGNORECASE): uidstart = 500 self.logger.log(LogPriority.DEBUG, "Actual uid start value is 500") if re.search('^UID\_MIN\s+1000', line, re.IGNORECASE): uidstart = 1000 self.logger.log(LogPriority.DEBUG, "Actual uid start value is 1000") self.logger.log(LogPriority.DEBUG, "Building list of searchdirs...") # get list of user home directories from /etc/passwd f = open("/etc/passwd", "r") contentlines = f.readlines() f.close() for line in contentlines: sline = line.split(":") if len(sline) > 2: if int(sline[2]) >= uidstart: # build list of search directories based on home directories if os.path.exists(sline[5] + "/.ssh/"): searchdirs.append(sline[5] + "/.ssh/") self.logger.log(LogPriority.DEBUG, "Adding directory: " + str(sline[5]) + "/.ssh/ to list of searchdirs...") # add the root ssh directory if it exists if os.path.exists("/root/.ssh/"): searchdirs.append("/root/.ssh/") self.logger.log(LogPriority.DEBUG, "Adding /root/.ssh/ to list of searchdirs...") self.logger.log(LogPriority.DEBUG, "Finished building searchdirs list") except Exception: raise return searchdirs def fix(self): """set permissions on all ssh keys to 0600 (384; -rw------) :returns: self.rulesuccess - boolean; True if fix operations succeeded, False if not """ fixedkeys = [] self.rulesuccess = True self.detailedresults = "" try: keylist = self.get_key_list(self.searchdirs) for key in keylist: self.logger.log(LogPriority.DEBUG, "Setting permissions on file: " + str(key) + " to 600...") os.chmod(key, 0o600) fixedkeys.append(key) if fixedkeys: self.detailedresults += "\nCorrected permissions on the following files:\n" + "\n".join(fixedkeys) else: self.detailedresults += "\nNo keys were modified." except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults = str(traceback.format_exc()) self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return self.rulesuccess
class ConfigurePowerManagement(Rule): '''This Mac Only rule does the following: - Sets the Mac to stay awake if power is plugged-in. - Set the display to sleep after 30 minutes if inactivity (if plugged-in) or after 15 minutes of inactivity (if on laptop battery power) - Sets the hard drives to sleep after ten minutes of inactivity. - Sets the computer not to wake up if the computer is connected to a phone modem and the phone rings. - Sets the hard drives to sleep after ten minutes of inactivity. - Sets the computer not to wake up if the computer is connected to a phone modem and the phone rings. - Sets the Mac's wake-on-magic-ping option (Wake for Network Access or WakeOnLAN) off. - Disables the Start up automatically after a power failure feature in the Energy Saver System Pref. This feature is not available on all computers. This rule should be called disableAutoRestart, but it's not. Sorry. @author: ekkehard j. koch ''' ############################################################################### def __init__(self, config, environ, logdispatcher, statechglogger): Rule.__init__(self, config, environ, logdispatcher, statechglogger) self.rulenumber = 258 self.rulename = 'ConfigurePowerManagement' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = [] self.applicable = {'type': 'white', 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.psconfiguration = \ {"ACDisableSystemSleep": {"HelpText": "Sets the Mac to stay awake if power is plugged-in. Default(AC Power, sleep, 0).", "PowerType": "AC Power", "PowerSetting": "sleep", "PowerSettingValue": 0, "PowerSettingMinimum": 0, "PowerSettingMaximum": 60}, "ACDisplaySleep": {"HelpText": "Set Display Sleep minutes on AC Power. Default(AC Power, displaysleep, 30).", "PowerType": "AC Power", "PowerSetting": "displaysleep", "PowerSettingValue": 30, "PowerSettingMinimum": 0, "PowerSettingMaximum": 60}, "ACDiskSleep": {"HelpText": "Set Disk Sleep minutes on AC Power. Default(AC Power, disksleep, 10).", "PowerType": "AC Power", "PowerSetting": "disksleep", "PowerSettingValue": 10, "PowerSettingMinimum": 0, "PowerSettingMaximum": 60}, "BatteryDisplaySleep": {"HelpText": "Set Display Sleep minutes on Battery Power. Default(Battery Power, displaysleep, 15).", "PowerType": "Battery Power", "PowerSetting": "displaysleep", "PowerSettingValue": 15, "PowerSettingMinimum": 0, "PowerSettingMaximum": 60}, "BatteryDiskSleep": {"HelpText": "Set Disk Sleep minutes on Battery Power. Default(Battery Power, disksleep, 10).", "PowerType": "Battery Power", "PowerSetting": "disksleep", "PowerSettingValue": 10, "PowerSettingMinimum": 0, "PowerSettingMaximum": 60} } self.ci = {} index = -1 for pslabel, psinfo in sorted(self.psconfiguration.items()): datatype = 'int' key = pslabel instructions = psinfo["HelpText"] defaultInteger = psinfo["PowerSettingValue"] index = index + 1 self.ci[pslabel] = self.initCi(datatype, key, instructions, defaultInteger) self.pmset = "/usr/bin/pmset" self.psd = {} self.psACPowerAvailable = False self.psACBatteryPowerAvailable = False self.ch = CommandHelper(self.logdispatch) def report(self): '''Go through power settings dictionary and see if they match :param self: essential if you override this definition :returns: boolean - true if applicable false if not ''' try: self.detailedresults = "" self.initializePowerSetting(True) self.compliant = True for pslabel, psinfo in sorted(self.psconfiguration.items()): powerType = psinfo["PowerType"] if (powerType == "AC Power" and self.psACPowerAvailable) or (powerType == "Battery Power" and self.psACBatteryPowerAvailable): powerSetting = psinfo["PowerSetting"] powerSettingValue = self.ci[pslabel].getcurrvalue() powerSettingActual = self.getPowerSetting(powerType, powerSetting, False) powerSettingActualValue = int(powerSettingActual) powerSettingInfo = "(" + str(powerType) + ", " + str(powerSetting) + \ ", [desired, actual][" + str(powerSettingValue) + ", " + str(powerSettingActual) +"])" if powerSettingValue == powerSettingActualValue: self.resultAppend(pslabel + " is compliant. " + powerSettingInfo) else: self.resultAppend(pslabel + " is not compliant! " + powerSettingInfo) self.compliant = False self.logdispatch.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + 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): '''Go through power settings and fix the one we are supposed to :param self: essential if you override this definition :returns: boolean - true if applicable false if not ''' try: self.detailedresults = "" success = True self.initializePowerSetting(True) for pslabel, psinfo in sorted(self.psconfiguration.items()): powerType = psinfo["PowerType"] if (powerType == "AC Power" and self.psACPowerAvailable) or (powerType == "Battery Power" and self.psACBatteryPowerAvailable): powerSetting = psinfo["PowerSetting"] powerSettingValue = self.ci[pslabel].getcurrvalue() powerSettingActual = self.getPowerSetting(powerType, powerSetting, False) powerSettingActualValue = int(powerSettingActual) powerSettingInfo = "(" + str(powerType) + ", " + str(powerSetting) + \ ", [desired, actual][" + str(powerSettingValue) + ", " + str(powerSettingActual)+"])" if powerSettingValue == powerSettingActualValue: self.resultAppend(pslabel + " was correctly set. " + powerSettingInfo) else: newPowerSettingValue = self.setPowerSetting(powerType, powerSetting, powerSettingValue) powerSettingActual = self.getPowerSetting(powerType, powerSetting, True) powerSettingActualValue = int(powerSettingActual) powerSettingInfo = "(" + str(powerType) + ", " + str(powerSetting) + \ ", [desired, actual][" + str(powerSettingValue) + ", " + str(powerSettingActual)+"])" if powerSettingValue == powerSettingActualValue: self.resultAppend(pslabel + " is now compliant! " + powerSettingInfo) else: self.resultAppend(pslabel + " setting still not compliant! " + powerSettingInfo) success = False self.rulesuccess = success except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + 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 ############################################################################### def initializePowerSetting(self, forceUpdate=False): '''Initialize Power Setting Dictionary (psd) with output from pmset command :param self: essential if you override this definition :param forceUpdate: Force Dictionary Reset (Default value = False) :returns: boolean - true if applicable false if not ''' success = True if forceUpdate: self.psd = {} self.psACPowerAvailable = False self.psACBatteryPowerAvailable = False if self.psd == {}: itemType = "" item = {} command = [self.pmset, "-g", "disk"] self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: linestripped = line.strip() values = linestripped.split() if linestripped == "Battery Power:": if not itemType == "": self.psd[itemType] = item item = {} itemType = "Battery Power" self.psACBatteryPowerAvailable = True elif linestripped == "AC Power:": if not itemType == "": self.psd[itemType] = item item = {} itemType = "AC Power" self.psACPowerAvailable = True elif not itemType == "": name = "" for namepart in values[:-1]: name = name + " " + str(namepart) name = name.strip() value = str(values[-1]) try: item[name] = int(value) except Exception: item[name] = value messagestring = "[" + str(itemType) + ", " + str(name) + "] = " + \ str(item[name]) self.logdispatch.log(LogPriority.DEBUG, messagestring) if not itemType == "": self.psd[itemType] = item return success ############################################################################### def getPowerSetting(self, powerType, powerSetting, forceUpdate=False): '''Get a power setting on a system @author: ekkehard j. koch :param self: essential if you override this definition :param powerType: Like AC Power or Battery Power :param powerSetting: Like sleep or disksleep :param forceUpdate: Get new values from system if true (Default value = False) :returns: boolean - true @note: None ''' try: self.initializePowerSetting(forceUpdate) powerSettingValueString = self.psd[powerType][powerSetting] if powerSettingValueString == "": powerSettingValue = 0 else: powerSettingValue = int(powerSettingValueString) messagestring = "[" + str(powerType) + ", " + str(powerSetting) + \ "] = " + str(powerSettingValue) self.logdispatch.log(LogPriority.DEBUG, messagestring) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: powerSettingValue = 0 return powerSettingValue ############################################################################### def setPowerSetting(self, powerType, powerSetting, powerSettingValue): '''Set a power setting on a system @author: ekkehard j. koch :param self: essential if you override this definition :param powerType: Like AC Power or Battery Power :param powerSetting: Like sleep or disksleep :param powerSettingValue: Value to set setting to :returns: boolean - true @note: None ''' success = False if not powerSettingValue == self.getPowerSetting(powerType, powerSetting): if powerType == "Battery Power": command = [self.pmset, "-b", powerSetting, str(powerSettingValue)] self.ch.executeCommand(command) elif powerType == "AC Power": command = [self.pmset, "-c", powerSetting, str(powerSettingValue)] self.ch.executeCommand(command) if powerSettingValue == self.getPowerSetting(powerType, powerSetting, True): success = True else: success = True return success ############################################################################### def resultAppend(self, pMessage=""): '''reset the current kveditor values to their defaults. @author: ekkehard j. koch :param self: essential if you override this definition :param pMessage: (Default value = "") :returns: boolean - true @note: kveditorName is essential ''' datatype = type(pMessage) if datatype == str: if not (pMessage == ""): messagestring = pMessage if (self.detailedresults == ""): self.detailedresults = messagestring else: self.detailedresults = self.detailedresults + "\n" + \ messagestring elif datatype == list: if not (pMessage == []): for item in pMessage: messagestring = item if (self.detailedresults == ""): self.detailedresults = messagestring else: self.detailedresults = self.detailedresults + "\n" + \ messagestring else: raise TypeError("pMessage with value" + str(pMessage) + \ "is of type " + str(datatype) + " not of " + \ "type " + str(str) + \ " or type " + str(list) + \ " as expected!")
class DisableFTP(Rule): def __init__(self, config, environ, logdispatcher, statechglogger): Rule.__init__(self, config, environ, logdispatcher, statechglogger) self.rulenumber = 266 self.rulename = 'DisableFTP' self.logger = logdispatcher self.formatDetailedResults("initialize") self.mandatory = True self.helptext = "This rule disables FTP services for the Mac" self.applicable = {'type': 'white', 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} # init CIs datatype = 'bool' key = 'DISABLEFTP' instructions = "To prevent DisableFTP from being disabled, set " + \ "the value of DISABLEFTP to False." default = True self.ci = self.initCi(datatype, key, instructions, default) def report(self): try: self.detailedresults = "" compliant = True cmd = ["/bin/launchctl", "list"] self.ch = CommandHelper(self.logger) if self.ch.executeCommand(cmd): output = self.ch.getOutput() for line in output: if re.search("com\.apple\.ftpd", line): self.detailedresults += "FTP is running and it shouldn't\n" compliant = False break else: self.detailedresults += "Unable to list running services\n" compliant = False self.compliant = compliant except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False 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): try: if not self.ci.getcurrvalue(): return success = True 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) cmd = ["/bin/launchctl", "disable", "system/com.apple.ftpd"] self.ch.executeCommand(cmd) cmd = ["/bin/launchctl", "unload", "/System/Library/LaunchDaemons/ftp.plist"] if not self.ch.executeCommand(cmd): success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = ["/bin/launchctl", "enable", "system/com.apple.ftpd"] event = {"eventtype": "comm", "command": cmd} self.statechglogger.recordchgevent(myid, event) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = ["/bin/launchctl", "load", "-w", "/System/Library/LaunchDaemons/ftp.plist"] event = {"eventtype": "comm", "command": cmd} self.statechglogger.recordchgevent(myid, event) self.rulesuccess = success 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", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class DisableSIRIandContinuityFeatures(Rule): def __init__(self, config, environ, logdispatch, statechglogger): '''Constructor''' Rule.__init__(self, config, environ, logdispatch, statechglogger) self.logger = logdispatch self.rulenumber = 310 self.rulename = "DisableSIRIandContinuityFeatures" self.formatDetailedResults("initialize") self.rootrequired = True self.applicable = {'type': 'white', 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}, 'fisma': 'low'} datatype = "bool" key = "SIRICONTINUITY" instructions = "To disable this rule set the value of " + \ "SIRICONTINUITY to False" default = True self.ci = self.initCi(datatype, key, instructions, default) datatype = "string" key = "MACHOMEDIR" instructions = "Enter the current user's home directory here " + \ " which is usually in the location of: /Users/username\n" + \ "If left blank, we will try to retrieve the home directory " + \ "inside the rule\n" default = "" self.homeci = self.initCi(datatype, key, instructions, default) '''Directory location for testing only''' # self.profile = "/Users/username/stonix/src/" + \ # "stonix_resources/files/" + \ # "stonix4macRestrictionsiCloudDictationSpeech.mobileconfig" self.profile = "/Applications/stonix4mac.app/Contents/" + \ "Resources/stonix.app/Contents/MacOS/" + \ "stonix_resources/files/" + \ "stonix4macRestrictionsiCloudDictationSpeech.mobileconfig" self.identifier = "097AD858-A863-4130-989F-D87CCE7E393A" self.home = "" self.ch = CommandHelper(self.logger) self.siripath1 = "/Library/Containers/com.apple.SiriNCService" + \ "/Data/Library/Preferences/com.apple.Siri.plist" self.siriparams1 = "StatusMenuVisible" self.siripath2 = "/Library/Preferences/com.apple.assistant.support" + \ ".plist" self.siriparams2 = "Assistant\ Enabled" self.sethelptext() def setupHomeDir(self): home = "" cmd = "/bin/echo $HOME" if self.ch.executeCommand(cmd): output = self.ch.getOutputString() if output: home = output.strip() return home def report(self): try: self.detailedresults = "" self.defaults1 = True self.defaults2 = True self.profilecomp = True compliant = True if not self.homeci.getcurrvalue(): self.home = self.setupHomeDir() if self.home: if os.path.exists(self.home): if os.path.exists(self.home + self.siripath1): cmd = "/usr/bin/defaults read " + \ self.home + self.siripath1 + " " + \ self.siriparams1 if self.ch.executeCommand(cmd): output = self.ch.getOutputString().strip() if output != "1": self.undo1 = output self.detailedresults += "Didn't get the " + \ "desired results for StatusMenuVisible\n" self.defaults1 = False else: self.detailedresults += "Unable to run defaults " + \ "read command on " + self.siripath1 + "\n" self.defaults1 = False if os.path.exists(self.home + self.siripath2): cmd = "/usr/bin/defaults read " + \ self.home + self.siripath2 + " " + \ self.siriparams2 if self.ch.executeCommand(cmd): output = self.ch.getOutputString().strip() if output != "0": self.undo2 = output self.detailedresults += "Didn't get the " + \ "desired results for " + \ "Assistant Enabled\n" self.defaults2 = False else: self.detailedresults += "Unable to run defaults " + \ "read command on " + self.siripath2 + "\n" self.defaults2 = False else: self.detailedresults += "Home directory entered does not exist\n" compliant = False else: self.detailedresults += "Unable to retrieve your home directory\n" compliant = False found = False cmd = ["/usr/bin/profiles", "-P"] if not self.ch.executeCommand(cmd): self.detailedresults += "Unable to run profiles command\n" else: output = self.ch.getOutput() if output: for line in output: if search("^There are no configuration profiles installed", line.strip()): self.detailedresults += "There are no configuration profiles installed\n" break elif search(escape(self.identifier) + "$", line.strip()): found = True break if not found: self.detailedresults += "All desired profiles aren't isntalled\n" self.profilecomp = False self.compliant = self.defaults1 & self.defaults2 & \ self.profilecomp & 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 fix(self): try: if not self.ci.getcurrvalue(): return success = True self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not self.defaults1: cmd = ["/usr/bin/defaults", "write", self.home + self.siripath1, self.siriparams1, "-bool", "yes"] if self.ch.executeCommand(cmd): if self.ch.getReturnCode() != 0: success = False else: undocmd = ["/usr/bin/defaults", "write", self.home + \ self.siripath1, self.siriparams1, self.undo1] self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": undocmd} self.statechglogger.recordchgevent(myid, event) if not self.defaults2: cmd = "/usr/bin/defaults write " + self.home + \ self.siripath2 + " " + self.siriparams2 + " -bool no" if self.ch.executeCommand(cmd): if self.ch.getReturnCode() != 0: success = False else: undocmd = "/usr/bin/defaults write " + self.home + \ self.siripath2 + " " + self.siriparams2 + \ " " + self.undo2 self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "commandstring", "command": undocmd} self.statechglogger.recordchgevent(myid, event) if not self.profilecomp: if os.path.exists(self.profile): cmd = ["/usr/bin/profiles", "-I", "-F", self.profile] if not self.ch.executeCommand(cmd): success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = ["/usr/bin/profiles", "-R", "-p", self.identifier] event = {"eventtype": "comm", "command": cmd} self.statechglogger.recordchgevent(myid, event) else: success = False self.rulesuccess = success 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 InstallBanners(RuleKVEditor): '''Install and configure warning banners, to be displayed at startup.''' def __init__(self, config, environ, logger, statechglogger): """ Constructor """ RuleKVEditor.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 51 self.rulename = 'InstallBanners' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.compliant = False self.guidance = [ 'CIS', 'NSA 2.3.7.2', 'CCE 4188-9', 'CCE 4431-3', 'CCE 3717-6', 'CCE 4554-2', 'CCE 4603-7', 'CCE 4760-5', 'CCE 4301-8', 'CCE 4698-7', 'CCE 4222-6', 'CCE 4103-8', 'CCE 4870-2', 'CCE 4896-7' ] self.iditerator = 0 self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # init CIs datatype = 'bool' key = 'INSTALLBANNERS' instructions = "To prevent the installation of warning banners, " + \ "set the value of InstallBanners to False.\n\n!DEBIAN USERS! Due to " + \ "a bug in gdm3 (gnome 3) which has not been patched on debian, we are forced to " + \ "change the user's display manager to something else in order to be compliant " + \ "with the login banner requirement. As a result, the login banner changes will " + \ "only take effect after a system reboot." default = True self.ci = self.initCi(datatype, key, instructions, default) # Initial setup and deterministic resolution of variables constlist = [ WARNINGBANNER, GDMWARNINGBANNER, GDM3WARNINGBANNER, ALTWARNINGBANNER, OSXSHORTWARNINGBANNER ] if not self.checkConsts(constlist): self.applicable = {'type': 'white', 'family': []} return self.initobjs() self.setcommon() self.islinux() self.ismac() def initobjs(self): ''' ''' try: self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.linux = False self.mac = False self.gnome2 = False self.gnome3 = False self.kde = False self.lightdm = False self.badline = False except Exception: raise def setgnome2(self): '''set up all variables for use with gnome2-based systems @author: Breen Malmberg ''' self.gnome2 = True self.gnome2bannertext = GDMWARNINGBANNER def forcelightdm(self): '''force debian systems using gdm3 to change to the lightdm display manager in order to comply with login/display banner requirements :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True self.logger.log(LogPriority.DEBUG, "Forcing display manager to be lightdm...") if self.gnome2: cmd = '/usr/sbin/dpkg-reconfigure --frontend=noninteractive gdm' elif self.gnome3: cmd = '/usr/sbin/dpkg-reconfigure --frontend=noninteractive gdm3' else: cmd = '/usr/sbin/dpkg-reconfigure --frontend=noninteractive lightdm' cmd2 = "/usr/sbin/dpkg --configure lightdm" self.lightdm = True path = '/etc/X11/default-display-manager' opt = '/usr/sbin/lightdm\n' mode = 0o644 uid = 0 gid = 0 try: if not self.setFileContents(path, opt, [uid, gid, mode]): retval = False if not self.ph.check('lightdm'): self.logger.log( LogPriority.DEBUG, "Package: lightdm not installed yet. Installing lightdm..." ) if not self.ph.install('lightdm'): retval = False self.detailedresults += "\nFailed to install lightdm" if not self.ch.executeCommand(cmd): retval = False self.detailedresults += "\nFailed to set lightdm as default display manager" self.ch.executeCommand(cmd2) if retval: self.detailedresults += "\nThe display manager has been changed to lightdm. These changes will only take effect once the system has been restarted." else: self.detailedresults += "\nFailed to change default display manager to lightdm. Login banner may not display." except Exception: raise return retval def setgnome3(self): '''set up all variables for use with gnome3-based systems @author: Breen Malmberg ''' self.gnome3 = True self.gnome3bannertext = GDM3WARNINGBANNER def setkde(self): '''set up all variables for use with kde-based systems @author: Breen Malmberg ''' self.kde = True self.kdebannertext = ALTWARNINGBANNER self.kdelocs = [ '/etc/kde/kdm/kdmrc', '/etc/kde3/kdm/kdmrc', '/etc/kde4/kdm/kdmrc', '/usr/share/config/kdm/kdmrc', '/usr/share/kde4/config/kdm/kdmrc' ] self.kdefile = '/usr/share/kde4/config/kdm/kdmrc' for loc in self.kdelocs: if os.path.exists(loc): self.kdefile = str(loc) tmpfile = self.kdefile + '.stonixtmp' key1 = 'GreetString' val1 = '"' + str(self.kdebannertext) + '"' self.greetbanner = [key1, val1] key2 = 'UserList' val2 = 'false' key3 = 'UseTheme' val3 = 'false' key4 = 'PreselectUser' val4 = 'None' key5 = 'LogoArea' val5 = 'None' key6 = 'GreeterPos' val6 = '45,45' key7 = 'AntiAliasing' val7 = 'false' key8 = 'SortUsers' val8 = 'false' key9 = 'GreetFont' val9 = 'Serif,20,-1,5,50,0,0,0,0,0' key10 = 'FailFont' val10 = 'Sans Serif,10,-1,5,75,0,0,0,0,0' bkey1 = 'PreselectUser' bval1 = 'None' self.kdedict = { "X-*-Greeter": { key2: val2, key3: val3, key4: val4, key5: val5, key6: val6, key7: val7, key8: val8, key9: val9, key10: val10 }, "X-:*-Greeter": { bkey1: bval1 } } self.kdeditor = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.kdefile, tmpfile, self.kdedict, "present", "closedeq") def setlightdm(self): '''set up all variables for use with lightdm-based systems @author: Breen Malmberg ''' self.lightdm = True self.ldmbannertext = ALTWARNINGBANNER key1 = '/usr/share/lightdm/lightdm.conf.d/50-ubuntu.conf' val1 = [ '[SeatDefaults]', 'allow-guest=false', 'greeter-hide-users=true', 'greeter-show-manual-login=true', 'autologin-user='******'/etc/lightdm/lightdm.conf.d/stonixlightdm.conf' val3 = [ '[SeatDefaults]', 'greeter-setup-script=/bin/sh -c "until /usr/bin/zenity ' + '--width=600 --height=400 --title=WARNING --text-info ' + '--filename=' + str(self.loginbannerfile) + '; do :; done"' ] self.lightdmdict = {key1: val1, key2: val2, key3: val3} def setlinuxcommon(self): '''set up all variables for use with linux-based systems @author: Breen Malmberg ''' if not self.sshdfile: self.sshdfile = '/etc/ssh/sshd_config' self.bannerfiles = [ "/etc/banners/in.ftpd", "/etc/banners/in.rlogind", "/etc/banners/in.rshd", "/etc/banners/in.telnetd", "/etc/banner" ] def setcommon(self): '''set up all variables for use with all systems @author: Breen Malmberg ''' self.loginbannerfile = "/etc/issue" self.sshbannerfile = "/etc/banner" self.sshdfile = "" self.sshdlocs = [ "/etc/sshd_config", "/etc/ssh/sshd_config", "/private/etc/ssh/sshd_config", "/private/etc/sshd_config" ] for loc in self.sshdlocs: if os.path.exists(loc): self.sshdfile = str(loc) self.sshbannerdict = {"Banner": self.sshbannerfile} def setmac(self): '''set up all variables for use with darwin-based systems @author: Breen Malmberg ''' self.mac = True self.bannertext = WARNINGBANNER if not self.sshdfile: self.sshdfile = '/private/etc/sshd_config' self.ftpwelcomelocs = ["/etc/ftpwelcome", "/private/etc/ftpwelcome"] self.ftpwelcomefile = '/private/etc/ftpwelcome' for loc in self.ftpwelcomelocs: if os.path.exists(loc): self.ftpwelcomefile = str(loc) self.policybanner = "/Library/Security/PolicyBanner.txt" self.addKVEditor( "FileServerBannerText", "defaults", "/Library/Preferences/com.apple.AppleFileServer", "", { "loginGreeting": [re.escape(WARNINGBANNER), "'\"" + WARNINGBANNER + "\"'"] }, "present", "", "To prevent the installation of a warning banner," + " set the value of InstallBanners to False", self.ci) self.addKVEditor( "loginwindowBannerText", "defaults", "/Library/Preferences/com.apple.loginwindow", "", { "LoginwindowText": [ re.escape(OSXSHORTWARNINGBANNER), '"' + OSXSHORTWARNINGBANNER + '"' ] }, "present", "", "To prevent the installation of a warning banner," + " set the value of InstallBanners to False", self.ci) def ismac(self): '''determine whether the current system is macintosh, or darwin-based @author: Breen Malmberg ''' try: if self.environ.getosfamily() == 'darwin': self.setmac() self.logger.log( LogPriority.DEBUG, "System is Mac OS. Configuring Mac banners...") except Exception: raise def islinux(self): '''determine whether the current system is linux-based, and set all distro-specific variables @author: Breen Malmberg ''' try: if self.environ.getosfamily() == 'linux': self.linux = True self.setlinuxcommon() if os.path.exists("/usr/sbin/gdm3"): self.setgnome3() elif os.path.exists("/usr/sbin/gdm"): self.ch.executeCommand("/usr/sbin/gdm --version") gnomeversionstring = self.ch.getOutputString() if re.search("GDM\s+3\.", gnomeversionstring, re.IGNORECASE): self.setgnome3() else: self.setgnome2() if os.path.exists("/usr/sbin/lightdm"): self.setlightdm() if os.path.exists("/usr/bin/startkde"): self.setkde() except Exception: raise def getFileContents(self, filepath, returntype='list'): '''Retrieve and return file contents (in list format) of a given file path :param filepath: string full path to file to read :param returntype: string valid values: 'list', 'string' (Default value = 'list') :returns: filecontents :rtype: list @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "Retrieving contents of file " + filepath) try: if returntype == 'list': filecontents = [] elif returntype == 'string': filecontents = '' else: filecontents = '' self.detailedresults += "\nReturntype parameter must be " + \ "either 'list' or 'string!'" if os.path.exists(filepath): f = open(filepath, 'r') if returntype == 'list': filecontents = f.readlines() elif returntype == 'string': filecontents = f.read() f.close() else: self.detailedresults += '\nCould not find specified file: ' + \ str(filepath) + '. Returning empty value...' except Exception: raise return filecontents def setFileContents(self, filepath, contents, perms=[0, 0, 0o644]): '''write (or append) specified contents to specified file :param filepath: string full path to file :param contents: list/string object to write to file (can be either list or string) :param mode: string indicates the IO method to use to open the file. Valid values are 'w' for write, and 'a' for append :param perms: integer-list of size 3. first index/position in list indicates octal permissions flag; second indicates uid; third indicates gid (Default value = [0) :param 0: :param 0644]: :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True tmpPath = filepath + ".stonixtmp" self.logger.log(LogPriority.DEBUG, "Writing changes to file " + filepath) try: if os.path.exists(filepath): f = open(tmpPath, "w") if isinstance(contents, list): f.writelines(contents) else: f.write(contents) f.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': filepath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(filepath, tmpPath, myid) os.rename(tmpPath, filepath) os.chmod(filepath, perms[2]) os.chown(filepath, perms[0], perms[1]) else: # get the parent directory of filepath basepath = ("/").join(filepath.split("/")[:-1]) # create parent directory if it doesn't exist if not os.path.exists(basepath): os.makedirs(basepath, 0o755) # then create the file f = open(filepath, "w") if isinstance(contents, list): f.writelines(contents) else: f.write(contents) f.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'creation', 'filepath': filepath} self.statechglogger.recordchgevent(myid, event) os.chmod(filepath, perms[2]) os.chown(filepath, perms[0], perms[1]) if not fixInflation(filepath, self.logger, perms[2], [perms[0], perms[1]]): retval = False except Exception: retval = False raise return retval def replaceFileContents(self, filepath, contentdict): '''replace key from contentdict with value from contentdict, in filepath :param filepath: string full path to file :param contentdict: dictionary of strings :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True appends = [] self.logger.log(LogPriority.DEBUG, "Replacing any existing, incorrect configurations...") try: contentlines = self.getFileContents(filepath) for line in contentlines: for key in contentdict: if any((re.search("\*", key), re.search("\*", line))): if re.search("^" + re.escape(key), re.escape(line)): contentlines = [ c.replace(line, key + contentdict[key]) for c in contentlines ] else: if re.search("^" + key, line): contentlines = [ c.replace(line, key + contentdict[key]) for c in contentlines ] for key in contentdict: if key.strip() not in contentlines: appends.append(key + contentdict[key]) self.appendFileContents(filepath, contentlines, appends) except Exception: self.rulesuccess = False raise return retval def appendFileContents(self, filepath, contents, appends): ''' :param filepath: :param contents: :param appends: ''' self.logger.log(LogPriority.DEBUG, "Appending missing configuration lines...") for a in appends: contents.append(a + "\n") self.setFileContents(filepath, contents) def reportFileContents(self, filepath, searchparams): '''verify that the give key:value pairs in contentdict exist in filepath :param filepath: string full path to file :param searchparams: list or string to search in file contents :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True foundDict = {} findresult = 999 foundstring = False try: if not os.path.exists(filepath): retval = False self.detailedresults += "\nRequired configuration file " + filepath + " does not exist" return retval if isinstance(searchparams, str): contents = self.getFileContents(filepath, "string") findresult = contents.find(searchparams) if findresult != -1: foundstring = True if not foundstring: retval = False self.logger.log( LogPriority.DEBUG, "str.find() return code = " + str(findresult)) self.detailedresults += "\nSearch parameter:\n" + searchparams + "\nis missing from " + filepath elif isinstance(searchparams, list): for p in searchparams: foundDict[p] = False contentlines = self.getFileContents(filepath) for line in contentlines: for p in searchparams: if line.find(p) != -1: foundDict[p] = True for i in foundDict: if not foundDict[i]: retval = False self.detailedresults += "\nSearch parameter:\n" + i + "\nis missing from " + filepath except Exception: raise return retval def checkCommand(self, cmd, val="", regex=True): '''check the output of a given command to see if it matches given value :param cmd: :param val: (Default value = "") :param regex: (Default value = True) :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True try: self.ch.executeCommand(cmd) retcode = self.ch.getReturnCode() # gconftool-2 returns either 0 or -1 on error if re.search("gconftool-2", cmd): if retcode == -1: retval = False errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) self.detailedresults += "\nCommand:\n" + cmd + "\nFailed" else: # all other programs return 0 on success if retcode != 0: retval = False errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) if val: outputstr = self.ch.getOutputString() if not regex: # outputstr = outputstr.strip() # val = val.strip() if outputstr.find(val) == -1: retval = False self.detailedresults += "\nDesired output:\n" self.detailedresults += val self.detailedresults += "\n\nActual output:\n" self.detailedresults += outputstr else: if not re.search(val, outputstr, re.IGNORECASE): retval = False self.logger.log( LogPriority.DEBUG, "Could not find search regex:\n" + val + "\nin command output") except Exception: raise return retval def report(self): '''The report method examines the current configuration and determines whether or not it is correct. 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. :returns: self.compliant :rtype: boolean @author Breen Malmberg ''' self.compliant = True self.detailedresults = "" try: if self.linux: if not self.reportlinux(): self.compliant = False elif self.mac: if not self.reportmac(): self.compliant = False else: self.compliant = False self.detailedresults += '\nCould not identify operating system, or operating system not supported.' except (KeyboardInterrupt, SystemExit): raise except Exception as err: 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 getgnome3version(self): '''get the gnome3 version as a float :returns: g3ver :rtype: float @author: Breen Malmberg ''' g3ver = 0.0 cmd = '/usr/sbin/gdm3 --version' backupcmd = '/usr/bin/gnome-session --version' if not any((self.checkCommand(cmd), self.checkCommand(backupcmd))): return g3ver else: outlines = self.ch.getOutput() for line in outlines: sline = line.split() if len(sline) > 1: splitver = sline[1].split('.') combinedver = splitver[0] + '.' + splitver[1] g3ver = float(combinedver) else: try: g3ver = float(sline[0]) except Exception: return g3ver return g3ver def reportlinux(self): '''run report functionality for linux-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: if all((self.gnome3, re.search('debian', self.environ.getostype(), re.IGNORECASE))): if self.getgnome3version() < 3.15: self.gnome3 = False self.forcelightdm() self.setlightdm() if not self.reportlinuxcommon(): compliant = False if self.gnome2: if not self.reportgnome2(): compliant = False elif self.gnome3: if not self.reportgnome3(): compliant = False if self.lightdm: if not self.reportlightdm(): compliant = False if self.kde: if not self.reportkde(): compliant = False except Exception: raise return compliant def reportlinuxcommon(self): '''run report functionality which is common to linux platforms :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: for f in self.bannerfiles: if not self.reportFileContents(f, WARNINGBANNER): compliant = False if not self.reportcommon(): compliant = False except Exception: raise return compliant def reportcommon(self): '''run report functionality which is common to all platforms :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True if not os.path.exists(self.sshdfile): self.detailedresults += "Required configuration file " + self.ssdhfile + \ " does not exist.\n" return False contents = readFile(self.sshdfile, self.logger) self.commoneditor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sshdfile, self.sshdfile + ".tmp", self.sshbannerdict, "present", "space") if not self.commoneditor.report(): compliant = False self.detailedresults += self.sshdfile + " doesn't contain correct contents\n" return compliant def reportgnome2(self): '''run report functionality for gnome2-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: gconf = "/usr/bin/gconftool-2" confDict = { "/apps/gdm/simple-greeter/disable_user_list": "true", "/apps/gdm/simple-greeter/banner_message_enable": "true", "/apps/gdm/simple-greeter/banner_message_text": ALTWARNINGBANNER } # gdm 2 db cannot reliably store/preserve/interpret newline characters gconfcommands = [] for item in confDict: gconfcommands.append(gconf + " -g " + item) for gcmd in gconfcommands: if not self.checkCommand(gcmd, confDict[gcmd.split()[2]], False): compliant = False except Exception: raise return compliant def reportgnome3(self): '''run report functionality for gnome3-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: if not self.checkprofilecfg(): compliant = False if not self.checkkeyfilecfg(): compliant = False except Exception: raise return compliant def checkprofilecfg(self): ''' ''' compliant = True profilefile = "/etc/dconf/profile/gdm" profileoptslist = ["user-db:user", "system-db:gdm"] if not self.reportFileContents(profilefile, profileoptslist): compliant = False return compliant def checkkeyfilecfg(self): ''' ''' compliant = True keyfile = "/etc/dconf/db/gdm.d/01-banner-message" keyfileoptslist = [ "[org/gnome/login-screen]", "banner-message-enable=true", "banner-message-text='" + GDM3WARNINGBANNER + "'", "disable-user-list=true" ] if not self.reportFileContents(keyfile, keyfileoptslist): compliant = False return compliant def reportlightdm(self): '''run report functionality for lightdm-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: for item in self.lightdmdict: if not self.reportFileContents(item, self.lightdmdict[item]): compliant = False self.detailedresults += '\nRequired configuration text not found in: ' + str( item) except Exception: raise return compliant def reportkde(self): '''run report functionality for kde-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True try: if not os.path.exists(self.kdefile): retval = False self.detailedresults += 'Required configuration file: ' + str( self.kdefile) + ' not found' else: if not self.kdeditor.report(): retval = False if self.kdeditor.fixables: self.detailedresults += '\nThe following required options are missing from ' + \ str(self.kdefile) + ':\n' + '\n'.join(str(f) for f in self.kdeditor.fixables) \ + '\n' except Exception: raise return retval def reportmac(self): '''run report functionality for macintosh, or darwin-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True try: if not RuleKVEditor.report(self, True): self.detailedresults += '\nEither file server banner text ' + \ 'or login window banner text is incorrect, or both' retval = False if os.path.exists(self.ftpwelcomefile): if not self.reportFileContents(self.ftpwelcomefile, self.bannertext): retval = False self.detailedresults += '\nIncorrect configuration ' + \ 'text in: ' + str(self.ftpwelcomefile) else: retval = False self.detailedresults += '\nRequired configuration file: ' + \ str(self.ftpwelcomefile) + ' not found' if os.path.exists(self.policybanner): if not self.reportFileContents(self.policybanner, self.bannertext): retval = False self.detailedresults += '\nIncorrect configuration ' + \ 'text in: ' + str(self.policybanner) else: retval = False self.detailedresults += '\nRequired configuration file: ' + \ str(self.policybanner) + ' not found' if not self.reportcommon(): retval = False except Exception: raise return retval def fix(self): '''Install warning banners, set the warning text and configure the file permissions for the warning banner files. :returns: success :rtype: boolean @author Breen Malmberg ''' self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) try: if self.ci.getcurrvalue(): if not self.fixcommon(): self.rulesuccess = False if self.linux: if not self.fixlinux(): self.rulesuccess = False elif self.mac: if not self.fixmac(): self.rulesuccess = False else: self.rulesuccess = False self.detailedresults += '\nCould not identify ' + \ 'operating system, or operating system not supported.' else: self.detailedresults += '\nThe configuration item for ' + \ 'this rule was not enabled so nothing was done.' except (KeyboardInterrupt, SystemExit): raise except Exception as err: 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): '''run fix functionality for linux-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: if not self.fixlinuxcommon(): success = False if self.gnome2: if not self.fixgnome2(): success = False elif self.gnome3: if not self.fixgnome3(): success = False if self.lightdm: if not self.fixlightdm(): success = False if self.kde: if not self.fixkde(): success = False except Exception: raise return success def fixlinuxcommon(self): '''run fix functionality which is common to linux platforms :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: for f in self.bannerfiles: if not self.setFileContents(f, WARNINGBANNER): success = False except Exception: raise return success def fixcommon(self): '''run fix functionlity which is common to all platforms :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True if self.commoneditor.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.commoneditor.setEventID(myid) if not self.commoneditor.fix(): debug = self.sshdfile + " editor fix is returning false\n" self.logger.log(LogPriority.DEBUG, debug) success = False elif not self.commoneditor.commit(): debug = self.sshdfile + " editor commit is returning false\n" self.logger.log(LogPriority.DEBUG, debug) success = False if not success: self.detailedresults += "Unable to correct contents of " + \ self.sshdfile + "\n" return success def fixgnome2(self): '''run fix functionality for gnome2-based systems (implementation based on: https://access.redhat.com/solutions/32480) :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True gconf = "/usr/bin/gconftool-2" confDict = { "/apps/gdm/simple-greeter/disable_user_list": "boolean true", "/apps/gdm/simple-greeter/banner_message_enable": "boolean true", "/apps/gdm/simple-greeter/banner_message_text": 'string "$(cat /etc/issue)"' } gconfcommands = [] for item in confDict: gconfcommands.append( gconf + " -s " + item + " --config-source=xml:readwrite:/etc/gconf/gconf.xml.system --direct --type " + confDict[item]) try: if not self.setFileContents("/etc/issue", GDMWARNINGBANNER): success = False self.detailedresults += "\nFailed to properly configure the banner text in file /etc/issue" # write configuration options to gnome system-wide database for gcmd in gconfcommands: if not self.checkCommand(gcmd): success = False except Exception: raise return success def fixgnome3(self): '''run fix functionality for gnome3-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: if not self.setFileContents("/etc/issue", GDM3WARNINGBANNER): success = False self.detailedresults += "\nFailed to properly configure the banner text in file /etc/issue" self.logger.log(LogPriority.DEBUG, "Applying gdm profile configurations...") # configure the gdm profile greeterdefaults = "" gdefaultslocs = [ "/usr/share/gdm/greeter-dconf-defaults", "/usr/share/gdm/greeter.dconf-defaults" ] for loc in gdefaultslocs: if os.path.exists(loc): greeterdefaults = loc profilebasedir = "/etc/dconf/profile" profilepath = profilebasedir + "/gdm" profileconflist = [ "user-db:user\n", "system-db:gdm\n", "file-db:" + greeterdefaults + "\n" ] if not os.path.exists(profilebasedir): self.logger.log( LogPriority.DEBUG, "gdm profile base directory does not exist. Creating it..." ) os.makedirs(profilebasedir, 0o755) if not self.setFileContents(profilepath, profileconflist): success = False self.logger.log(LogPriority.DEBUG, "Applying gdm keyfile configurations...") # configure the gdm keyfile gdmkeyfilebase = "/etc/dconf/db/gdm.d" gdmkeyfile = gdmkeyfilebase + "/01-banner-message" gdmkeyfileconflist = [ "[org/gnome/login-screen]\n", "banner-message-enable=true\n", "banner-message-text='" + GDM3WARNINGBANNER + "'\n", "disable-user-list=true\n" ] if not os.path.exists(gdmkeyfilebase): self.logger.log( LogPriority.DEBUG, "gdm keyfile base directory does not exist. Creating it..." ) os.makedirs(gdmkeyfilebase, 0o755) if not self.setFileContents(gdmkeyfile, gdmkeyfileconflist): success = False self.logger.log( LogPriority.DEBUG, "Updating dconf with new configuration settings...") # update dconf dconfupdate = "/usr/bin/dconf update" if not self.checkCommand(dconfupdate): success = False if success: self.detailedresults += "\nSuccessfully applied new banner configuration settings. On some systems, a reboot may be required to view updated settings" except Exception: raise return success def fixlightdm(self): '''run fix functionality for lightdm-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True contentlines = [] try: if not os.path.exists('/etc/lightdm/lightdm.conf.d'): os.makedirs('/etc/lightdm/lightdm.conf.d/', 0o755) for f in self.lightdmdict: contentlines = [] if isinstance(self.lightdmdict[f], list): for opt in self.lightdmdict[f]: contentlines.append(opt + '\n') if not self.setFileContents(f, contentlines): success = False else: if not self.setFileContents(f, self.lightdmdict[f]): success = False except Exception: raise return success def fixkde(self): '''run fix functionality for kde-based systems :returns: success :rtype: boolean @author: Breen Malmberg @change: Breen Malmberg - 5/25/2016 - moved the commit calls to ensure they are only called if the fix calls completed successfully ''' success = True try: if not self.kdefile: self.logger.log( LogPriority.DEBUG, "Unable to identify kde configuration file. " + "Can not continue with fix") self.detailedresults += "Unable to identify kde " + \ "configuration file. Can not continue with fix." success = False return success fileCreated = False if not os.path.exists(self.kdefile): if createFile(self.kdefile, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.kdefile} self.statechglogger.recordchgevent(myid, event) fileCreated = True debug = "kdmrc file successfully created at " + \ self.kdefile self.logger.log(LogPriority.DEBUG, debug) setPerms(self.kdefile, [0, 0, 0o644], self.logger) self.reportkde() else: self.detailedresults += "\nCould not create kdmrc file " + \ "at " + self.kdefile return False if not self.kdeditor.fix(): success = False self.detailedresults += "KVEditor fix failed. Fix not applied" else: if not fileCreated: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.kdeditor.setEventID(myid) if not self.kdeditor.commit(): success = False self.detailedresults += 'kdeditor commit failed. ' + \ 'Fix not applied' key1 = 'GreetString' val1 = '"' + self.kdebannertext + '"' data = {"X-*-Greeter": {key1: val1}} tmpfile = self.kdefile + ".stonixtmp2" greeteditor = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.kdefile, tmpfile, data, "present", "closedeq") if not greeteditor.report(): if greeteditor.fix(): if not greeteditor.commit(): if not fileCreated: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.kdeditor.setEventID(myid) success = False self.detailedresults += "\nGreetString commit failed" else: success = False self.detailedresults += "\nGreetString fix failed" except Exception: raise return success def fixmac(self): '''run fix functionality for macintosh, or darwin-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: if not RuleKVEditor.fix(self, True): success = False if not self.setFileContents(self.ftpwelcomefile, self.bannertext): success = False self.detailedresults += '\nUnable to set warning banner text in ' + str( self.ftpwelcomefile) if not self.setFileContents(self.policybanner, self.bannertext): success = False self.detailedresults += '\nUnable to set warning banner text in ' + str( self.policybanner) except Exception: raise return success
class Freebsd(object): '''The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. :version: :author:Derek T Walker 08-06-2012''' def __init__(self, logger): self.logger = logger self.detailedresults = "" self.ch = CommandHelper(self.logger) self.install = "/usr/sbin/pkg_add -r -f " self.remove = "/usr/sbin/pkg_delete " ############################################################################### def installpackage(self, package): ''' Install a package. Return a bool indicating success or failure. @param string package : Name of the package to be installed, must be recognizable to the underlying package manager. @return bool : @author''' try: installed = False # retval = call(self.install + package,stdout=None,shell=True) self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: # if retval == 0: self.detailedresults += package + " pkg installed successfully" installed = True else: self.detailedresults += package + " pkg not able to install" self.logger.log(LogPriority.INFO, self.detailedresults) return installed except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) ############################################################################### def removepackage(self, package): ''' Remove a package. Return a bool indicating success or failure. @param string package : Name of the package to be removed, must be recognizable to the underlying package manager. @return : bool @author''' try: self.detailedresults = "" removed = False stringToMatch = package + "(.*)" self.ch.executeCommand(["/usr/sbin/pkg_info"]) output = self.ch.getOutput() # temp = Popen(["/usr/sbin/pkg_info"],stdout=PIPE,shell=True) # details = temp.stdout.readlines() # temp.stdout.close() for cell in output: cell2 = cell.split(" ") if re.search(stringToMatch, cell2[0]): retval = call(self.remove + cell2[0], stdout=None, shell=True) if retval == 0: self.detailedresults += package + " pkg removed \ successfully" removed = True else: self.detailedresults += package + " pkg not able\ to be removed" if not self.detailedresults: self.detailedresults += package + " pkg not found to remove" self.logger.log(LogPriority.INFO, self.detailedresults) return removed except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) ############################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. @param string package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return : bool @author''' try: self.detailedresults = "" present = False stringToMatch = package + "(.*)" self.ch.executeCommand(["/usr/sbin/pkg_info"]) output = self.ch.getOutput() # temp = Popen(["/usr/sbin/pkg_info"],stdout=PIPE,shell=False) # info = temp.stdout.readlines() # temp.stdout.close() for cell in output: cell2 = cell.split(" ") if re.search(stringToMatch, cell2[0]): self.detailedresults += package + " pkg found" present = True if not self.detailedresults: self.detailedresults += package + " pkg not found" self.logger.log(LogPriority.INFO, self.detailedresults) return present except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove
class SecureCUPS(Rule): '''With this rule, you can: Disable the CUPS service Configure CUPS service Disable Printer Browsing Limit Printer Browsing Disable Print Server Capabilities Set the Default Auth Type Setup default set of policy blocks for CUPS ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.statechglogger = statechglogger self.rulenumber = 128 self.rulename = 'SecureCUPS' self.rulesuccess = True self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = ['CCE 4420-6', 'CCE 4407-3'] self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] }, 'family': ['linux'] } # init CIs datatype1 = 'bool' key1 = 'SECURECUPS' instructions1 = 'To prevent to STONIX from securing the CUPS service, set the value of ' + \ 'SecureCUPS to False.' default1 = True self.SecureCUPS = self.initCi(datatype1, key1, instructions1, default1) datatype2 = 'bool' key2 = 'DISABLECUPS' instructions2 = 'To have STONIX completely disable the CUPS service on this system, set ' + \ 'the value of DisableCUPS to True.' default2 = False self.DisableCUPS = self.initCi(datatype2, key2, instructions2, default2) datatype3 = 'bool' key3 = 'DISABLEPRINTBROWSING' instructions3 = 'If this machine is to be used as a print server and you wish to enable automatic client discovery of ' \ 'its shared printers/print resources, then set the value of DISABLEPRINTBROWSING TO False' default3 = True self.DisablePrintBrowsing = self.initCi(datatype3, key3, instructions3, default3) datatype4 = 'string' key4 = 'PRINTBROWSESUBNET' instructions4 = 'If this machine is to be used as a print server and you wish to enable automatic client discovery of ' \ 'its shared printers/print resources, you may wish to limit that discovery to clients only on a certain subnet. ' \ 'Please customize the value of PRINTBROWSESUBNET to match your networks requirements. Ex: 192.168.0.0/16' default4 = '' self.PrintBrowseSubnet = self.initCi(datatype4, key4, instructions4, default4) datatype5 = 'bool' key5 = 'DISABLEGENERICPORT' instructions5 = 'To prevent remote users from potentially connecting ' + \ 'to and using locally configured printers by disabling the CUPS print ' + \ 'server sharing capabilities, set the value of DisableGenericPort to ' + \ 'True.' default5 = False self.DisableGenericPort = self.initCi(datatype5, key5, instructions5, default5) datatype6 = 'bool' key6 = 'SETDEFAULTAUTHTYPE' instructions6 = 'To prevent the defaultauthtype for cups from being ' + \ 'set to Digest, set the value of SetDefaultAuthType to False.' default6 = True self.SetDefaultAuthType = self.initCi(datatype6, key6, instructions6, default6) datatype7 = 'bool' key7 = 'SETUPDEFAULTPOLICYBLOCKS' instructions7 = "To prevent default policy blocks for cups from " + \ "being defined in the cups config file, set the value of " + \ "SetupDefaultPolicyBlocks to False. Note that if you choose to setup " + \ "the default set of policy blocks you can (and probably should) edit " + \ "them in the cups config file afterward to customize these policies to " + \ "your site's particular needs." default7 = False self.SetupDefaultPolicyBlocks = self.initCi(datatype7, key7, instructions7, default7) self.localize() def localize(self): '''set various settings and variables and objects based on which OS is currently running :returns: void @author: Breen Malmberg ''' self.linux = False self.darwin = False if self.environ.getosfamily() == 'darwin': self.darwin = True if self.environ.getosfamily() == 'linux': self.linux = True self.initObjs() self.setVars() def initObjs(self): '''initialize all required class objects :returns: void @author: Breen Malmberg ''' if self.linux: self.ph = Pkghelper(self.logger, self.environ) if self.darwin: pass self.sh = ServiceHelper(self.environ, self.logger) self.serviceTarget = "" self.ch = CommandHelper(self.logger) def setVars(self): '''set all class variables depending on which OS is currently running :returns: void @author: Breen Malmberg @change: Breen Malmberg - 2/8/2017 - fixed a typo with the var name of the dict for cupsd conf options; ''' try: # linux config if self.linux: self.configfileperms = '0640' errorlog = "/var/log/cups/error_log" logfileperms = '0644' self.pkgname = "cups" self.svcname = "cups" accesslog = "/var/log/cups/access_log" self.cupsfilesopts = {} self.cupsfilesconf = "" self.cupsdconf = "" self.cupsdconfremopts = {} # darwin config if self.darwin: accesslog = "/private/var/log/cups/access_log" # a value of 'strict' causes issues connecting to printers on some mac systems self.configfileperms = '0644' logfileperms = '0644' errorlog = "/private/var/log/cups/error_log" self.svclongname = "/System/Library/LaunchDaemons/org.cups.cupsd.plist" self.svcname = "org.cups.cupsd" # common config cupsdconflocs = [ '/etc/cups/cupsd.conf', '/private/etc/cups/cupsd.conf' ] for loc in cupsdconflocs: if os.path.exists(loc): self.cupsdconf = loc self.tmpcupsdconf = self.cupsdconf + ".stonixtmp" cupsfileslocs = [ '/etc/cups/cups-files.conf', '/private/etc/cups/cups-files.conf' ] for loc in cupsfileslocs: if os.path.exists(loc): self.cupsfilesconf = loc self.tmpcupsfilesconf = self.cupsfilesconf + ".stonixtmp" # options for cups-files.conf self.cupsfilesopts["ConfigFilePerm"] = self.configfileperms self.cupsfilesopts["ErrorLog"] = errorlog self.cupsfilesopts["LogFilePerm"] = logfileperms # cupsd conf default configuration options loglevel = "warn" self.cupsdconfopts = {"LogLevel": loglevel} self.cupsdconfopts["AccessLog"] = accesslog self.cupsdconfopts["AccessLogLevel"] = "config" # cupsd conf remove these options if self.DisableGenericPort.getcurrvalue(): self.cupsdconfremopts = {"Port": "631"} # this line added to prevent kvaconf trying to load files # it can't access during __init__, if the program is being # run in user mode if self.environ.geteuid() == 0: ## create kveditor objects if os.path.exists(self.cupsfilesconf): kvtype1 = "conf" path1 = self.cupsfilesconf tmpPath1 = path1 + ".stonixtmp" data1 = self.cupsfilesopts intent1 = "present" configType1 = "space" self.KVcupsfiles = KVEditorStonix(self.statechglogger, self.logger, kvtype1, path1, tmpPath1, data1, intent1, configType1) if os.path.exists(self.cupsdconf): kvtype2 = "conf" path2 = self.cupsdconf tmpPath2 = path2 + ".stonixtmp" data2 = self.cupsdconfopts intent2 = "present" configType2 = "space" self.KVcupsd = KVEditorStonix(self.statechglogger, self.logger, kvtype2, path2, tmpPath2, data2, intent2, configType2) # policy blocks self.serveraccess = """# Restrict access to the server... <Location /> Encryption Required Order allow,deny </Location>""" self.adminpagesaccess = """# Restrict access to the admin pages... <Location /admin> Encryption Required Order allow,deny </Location>""" self.configfilesaccess = """# Restrict access to configuration files... <Location /admin/conf> AuthType Default Encryption IfRequested Require user @SYSTEM Order allow,deny </Location>""" self.logfileaccess = """# Restrict access to log files... <Location /admin/log> AuthType Default Encryption IfRequested Require user @SYSTEM Order allow,deny </Location>""" self.defaultprinterpolicies = """# Set the default printer/job policies... <Policy default> # Job-related operations must be done by the owner or an administrator... <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job CUPS-Move-Job> Require user @OWNER @SYSTEM Order deny,allow </Limit> # All administration operations require an administrator to authenticate... <Limit CUPS-Add-Modify-Printer CUPS-Delete-Printer CUPS-Add-Modify-Class CUPS-Delete-Class CUPS-Set-Default> AuthType Default Require user @SYSTEM Order deny,allow </Limit> # All printer operations require a printer operator to authenticate... <Limit Pause-Printer Resume-Printer Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After CUPS-Accept-Jobs CUPS-Reject-Jobs> AuthType Default Require user @SYSTEM Order deny,allow </Limit> # Only the owner or an administrator can cancel or authenticate a job... <Limit Cancel-Job CUPS-Authenticate-Job> Require user @OWNER @SYSTEM Order deny,allow </Limit> <Limit All> Order deny,allow </Limit> </Policy>""" except Exception: raise def sanityCheck(self): '''perform sanity check on the cups configuration files :returns: sane :rtype: bool @author: Breen Malmberg ''' sane = True sanitycheck = "/usr/sbin/cupsd -t" try: self.ch.executeCommand(sanitycheck) retcode = self.ch.getReturnCode() output = self.ch.getOutput() if retcode != 0: sane = False self.detailedresults += "\nError while running command: " + str( sanitycheck) for line in output: if re.search("Bad|Missing|Incorrect|Error|Wrong", line, re.IGNORECASE): sane = False self.detailedresults += "\n" + line except Exception: raise return sane def updateOpts(self): '''update the kveditor values for the different CIs based on their current user-specified values :returns: void @author: Breen Malmberg @change: Breen Malmberg - 2/8/2017 - KVcupsdrem will now only be processed if self.DisableGenericPort CI is True ''' try: if self.DisableCUPS.getcurrvalue(): self.PrintBrowseSubnet.updatecurrvalue(False) if self.DisablePrintBrowsing.getcurrvalue(): self.cupsdconfopts["Browsing"] = "Off" self.cupsdconfopts["BrowseAllow"] = "none" self.cupsdconfopts["BrowseWebIF"] = "No" if self.PrintBrowseSubnet.getcurrvalue(): self.cupsdconfopts["Browsing"] = "On" self.cupsdconfopts["BrowseOrder"] = "allow,deny" self.cupsdconfopts["BrowseDeny"] = "all" self.cupsdconfopts[ "BrowseAllow"] = self.PrintBrowseSubnet.getcurrvalue() if self.DisableGenericPort.getcurrvalue(): self.cupsdconfremopts["Port"] = "631" if self.SetDefaultAuthType.getcurrvalue(): self.cupsdconfopts["DefaultAuthType"] = "Negotiate" # don't try to create, or update, the kv object, # if the file it's based on doesn't exist. # this would cause tracebacks in kveditor if os.path.exists(self.cupsfilesconf): kvtype1 = "conf" path1 = self.cupsfilesconf tmpPath1 = path1 + ".stonixtmp" data1 = self.cupsfilesopts intent1 = "present" configType1 = "space" self.KVcupsfiles = KVEditorStonix(self.statechglogger, self.logger, kvtype1, path1, tmpPath1, data1, intent1, configType1) else: self.logger.log( LogPriority.DEBUG, "Location of required configuration file cups-files.conf could not be determined" ) if os.path.exists(self.cupsdconf): kvtype2 = "conf" path2 = self.cupsdconf tmpPath2 = path2 + ".stonixtmp" data2 = self.cupsdconfopts intent2 = "present" configType2 = "space" self.KVcupsd = KVEditorStonix(self.statechglogger, self.logger, kvtype2, path2, tmpPath2, data2, intent2, configType2) else: self.logger.log( LogPriority.DEBUG, "Location of required configuration file cupsd.conf could not be determined" ) except Exception: raise def report(self): '''run report methods/actions appropriate for the current OS and return compliancy status :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "\n\nREPORT()\n\n") # DEFAULTS self.detailedresults = "" self.compliant = True badopts = [ "^HostNameLookups\s+Double", "^Sandboxing\s+strict", "^FatalErrors\s+config" ] badoptsfiles = [self.cupsdconf, self.cupsfilesconf] try: # check for bad config options in files for f in badoptsfiles: if os.path.exists(f): fh = open(f, 'r') contentlines = fh.readlines() fh.close() for line in contentlines: for opt in badopts: if re.search(opt, line, re.IGNORECASE): self.compliant = False # if these opts exist, then we don't want to do anything else ## except just remove them if not self.compliant: self.logger.log( LogPriority.DEBUG, "Bad configuration options found in cups config files. Will now remove them." ) self.formatDetailedResults('report', self.compliant, self.detailedresults) return self.compliant if self.linux: if not self.ph.check(self.pkgname): self.detailedresults += "\nCUPS not installed on this system. Nothing to secure." self.formatDetailedResults('report', self.compliant, self.detailedresults) return self.compliant # update kv objects with any new # user-specified information self.updateOpts() if self.SecureCUPS.getcurrvalue(): # is cups secured? if not self.reportSecure(): self.compliant = False # make sure config syntax is correct if not self.sanityCheck(): self.compliant = False if self.DisableCUPS.getcurrvalue(): # is cups disabled? if not self.reportDisabled(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults('report', self.compliant, self.detailedresults) return self.compliant def reportDisabled(self): '''return True if cups is disabled return False if cups is enabled :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True try: if self.linux: if self.sh.auditService(self.svcname, serviceTarget=self.serviceTarget): retval = False self.detailedresults += "\nThe " + str( self.svcname) + " service is still configured to run" elif self.darwin: if self.sh.auditService(self.svclongname, serviceTarget=self.svcname): retval = False self.detailedresults += "\nThe " + str( self.svcname) + " service is still configured to run" except Exception: raise return retval def checkPolicyBlocks(self): '''report on whether default policy blocks are currently set up in cups configuration. Note that if these already exist, we do not want to overwrite them as the local admin may have them customised to their specific environment. :returns: retval :rtype: bool @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "\n\nCHECKPOLICYBLOCKS()\n\n") retval = True rootfound = False adminfound = False adminconffound = False defpolicyfound = False try: if os.path.exists(self.cupsdconf): self.logger.log( LogPriority.DEBUG, "\n\nCUPSD CONF EXISTS. OPENING FILE AND READING CONTENTS...\n\n" ) f = open(self.cupsdconf, 'r') contentlines = f.readlines() f.close() self.logger.log( LogPriority.DEBUG, "\n\nCONTENTS READ. CHECKING FOR POLICY BLOCKS...\n\n") for line in contentlines: if re.search('\<Location \/\>', line, re.IGNORECASE): rootfound = True if re.search('\<Location \/admin\>', line, re.IGNORECASE): adminfound = True if re.search('\<Location \/admin\/conf\>', line, re.IGNORECASE): adminconffound = True if re.search('\<Policy default\>', line, re.IGNORECASE): defpolicyfound = True else: self.logger.log( LogPriority.DEBUG, "\n\ncupsd.conf file does not exist. Nothing to check...\n\n" ) return retval if not rootfound: retval = False self.detailedresults += "\nCUPS Root location policy not defined" if not adminfound: retval = False self.detailedresults += "\nCUPS admin location policy not defined" if not adminconffound: retval = False self.detailedresults += "\nCUPS admin/conf location policy not defined" if not defpolicyfound: retval = False self.detailedresults += "\nCUPS Default Policy block not defined" if retval: self.logger.log(LogPriority.DEBUG, "\n\nALL POLICY BLOCKS OK\n\n") except Exception: raise return retval def reportSecure(self): '''run report actions common to all platforms :returns: retval :rtype: bool @author: Breen Malmberg @change: Breen Malmberg - 2/8/2017 - KVcupsdrem will now only be run if self.DisableGenericPort CI is True ''' self.logger.log(LogPriority.DEBUG, "\n\nREPORTSECURE()\n\n") retval = True try: if self.SecureCUPS.getcurrvalue(): if os.path.exists(self.cupsdconf): # Report on cupsd.conf change/add options self.KVcupsd.setData(self.cupsdconfopts) self.KVcupsd.setIntent("present") self.KVcupsd.report() # Report on cupsd.conf remove options self.KVcupsd.setData(self.cupsdconfremopts) self.KVcupsd.setIntent("notpresent") self.KVcupsd.report() if self.KVcupsd.fixables: retval = False self.detailedresults += "\nThe following configuration options, in " + str( self.cupsdconf) + ", are incorrect:\n" + "\n".join( self.KVcupsd.fixables) if not self.checkPolicyBlocks(): retval = False else: pass if os.path.exists(self.cupsfilesconf): self.KVcupsfiles.report() if self.KVcupsfiles.fixables: retval = False self.detailedresults += "\nThe following configuration options, in " + str( self.cupsfilesconf ) + ", are incorrect:\n" + "\n".join( self.KVcupsfiles.fixables) else: pass else: self.detailedresults += "\nNeither SecureCUPS nor DisableCUPS CI's was enabled. Nothing was done." except Exception: raise return retval def fix(self): '''run fix methods/actions appropriate for the current OS and return success status of fix :returns: success :rtype: bool @author: Breen Malmberg ''' # DEFAULTS self.detailedresults = "" success = True self.iditerator = 0 badopts = [ "^HostNameLookups\s+Double", "^Sandboxing\s+strict", "^FatalErrors\s+config" ] badoptsfiles = [self.cupsdconf, self.cupsfilesconf] foundbadopts = False try: # fix bad opts; do not record state change, ## so that a possible revert, afterward, will ## not revert back to the bad state ## this code is a one-off for a hotfix and ## should probably be discontinued in some future ## release at some point for f in badoptsfiles: contentlines = [] if os.path.exists(f): fh = open(f, 'r') contentlines = fh.readlines() fh.close() for line in contentlines: for opt in badopts: if re.search(opt, line, re.IGNORECASE): foundbadopts = True contentlines = [ c.replace(line, '\n') for c in contentlines ] # finished building new contentlines; now write them for each file fn = open(f, 'w') fn.writelines(contentlines) fn.close() os.chmod(f, 0o644) if foundbadopts: # do not continue with rest of fix because that would save state for this fix run self.logger.log( LogPriority.DEBUG, "Reloading cups service to read configuration changes...") if self.darwin: self.sh.reloadService(self.svclongname, serviceTarget=self.svcname) else: self.sh.reloadService(self.svcname, serviceTarget=self.serviceTarget) self.logger.log( LogPriority.DEBUG, "Removed bad configuration options from cups config files. Exiting..." ) self.formatDetailedResults('fix', success, self.detailedresults) return success # Are any of the CIs enabled? # If not, then exit, returning True if not self.SecureCUPS.getcurrvalue() and \ not self.DisableCUPS.getcurrvalue(): self.detailedresults += "\nNo CI was enabled, so nothing was done." self.logger.log( LogPriority.DEBUG, "SecureCUPS rule was run, but neither SecureCUPS, nor DisableCUPS CI's were enabled so nothing was done!" ) self.formatDetailedResults('fix', success, self.detailedresults) return success if self.linux and not self.ph.check("cups"): self.detailedresults += "\nCUPS is not installed. Nothing to do." self.formatDetailedResults('fix', success, self.detailedresults) return success if self.SecureCUPS.getcurrvalue(): if not self.fixSecure(): success = False if self.DisableCUPS.getcurrvalue(): if not self.disableCUPS(): success = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults('fix', success, self.detailedresults) return success def fixSecure(self): '''run fix actions common to all platforms :returns: retval :rtype: bool @author: Breen Malmberg @change: Breen Malmberg - 2/8/2017 - added inline comments; changed the way KVCupsdrem was being handled (will not be run if there are no options to remove; aka if self.DisableGenericPort CI is False) ''' retval = True pdefaultfound = False serveraccessfound = False adminaccessfound = False configaccessfound = False logfileaccessfound = False try: if os.path.exists(self.cupsdconf): # Fix cupsd.conf add/change/remove options self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.KVcupsd.setEventID(myid) if self.KVcupsd.fix(): if not self.KVcupsd.commit(): self.detailedresults += "\nCommit failed for cupsd.conf" self.logger.log(LogPriority.DEBUG, "Commit failed for KVcupsd") retval = False # cups-files conf add/change options if os.path.exists(self.cupsfilesconf): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.KVcupsfiles.setEventID(myid) if self.KVcupsfiles.fix(): if not self.KVcupsfiles.commit(): self.detailedresults += "\nCommit failed for cups-files.conf" self.logger.log(LogPriority.DEBUG, "Commit failed for KVcupsfiles") retval = False # cupsdconf default policy blocks # this portion cannot be handled by kveditor because of its # xml style formatting if os.path.exists(self.cupsdconf): if self.SetupDefaultPolicyBlocks.getcurrvalue(): f = open(self.cupsdconf, 'r') contentlines = f.readlines() f.close() for line in contentlines: if re.search("\<Location \/\>", line, re.IGNORECASE): serveraccessfound = True for line in contentlines: if re.search("\<Location \/admin\>", line, re.IGNORECASE): adminaccessfound = True for line in contentlines: if re.search("\<Location \/admin\/conf\>", line, re.IGNORECASE): configaccessfound = True for line in contentlines: if re.search("\<Location \/admin\/log\>", line, re.IGNORECASE): logfileaccessfound = True for line in contentlines: if re.search("\<Policy default\>", line, re.IGNORECASE): pdefaultfound = True if not serveraccessfound: contentlines.append("\n" + self.serveraccess + "\n") self.logger.log( LogPriority.DEBUG, "\n\nroot access policy block not found. adding it...\n\n" ) if not adminaccessfound: contentlines.append("\n" + self.adminpagesaccess + "\n") self.logger.log( LogPriority.DEBUG, "\n\nadmin access policy block not found. adding it...\n\n" ) if not configaccessfound: contentlines.append("\n" + self.configfilesaccess + "\n") self.logger.log( LogPriority.DEBUG, "\n\nconfig access policy block not found. adding it...\n\n" ) if not logfileaccessfound: contentlines.append("\n" + self.logfileaccess + "\n") self.logger.log( LogPriority.DEBUG, "\n\nlog file access policy block not found. adding it...\n\n" ) if not pdefaultfound: contentlines.append("\n" + self.defaultprinterpolicies + "\n") self.logger.log( LogPriority.DEBUG, "\n\ndefault policy block not found. adding it...\n\n" ) tf = open(self.tmpcupsdconf, 'w') tf.writelines(contentlines) tf.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filename": self.cupsdconf} self.statechglogger.recordfilechange( self.cupsdconf, self.tmpcupsdconf, myid) self.statechglogger.recordchgevent(myid, event) self.logger.log( LogPriority.DEBUG, "\n\nwriting changes to " + str(self.cupsdconf) + " file...\n\n") os.rename(self.tmpcupsdconf, self.cupsdconf) self.logger.log( LogPriority.DEBUG, "\n\nsetting permissions and ownership for " + str(self.cupsdconf) + " file...\n\n") os.chown(self.cupsdconf, 0, 0) if self.linux: os.chmod(self.cupsdconf, 0o640) elif self.darwin: os.chmod(self.cupsdconf, 0o644) if not self.reloadCUPS(): retval = False except Exception: raise return retval def reloadCUPS(self): '''reload the cups service to read the new configurations :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True try: # do not attempt to reload the service # if the rule is set to disable it if self.DisableCUPS.getcurrvalue(): return retval if self.linux: if not self.sh.reloadService(self.svcname, serviceTarget=self.serviceTarget): retval = False self.detailedresults += "|nThere was a problem reloading the " + str( self.svcname) + " service" elif self.darwin: if not self.sh.reloadService(self.svclongname, serviceTarget=self.svcname): retval = False self.detailedresults += "|nThere was a problem reloading the " + str( self.svcname) + " service" except Exception: raise return retval def disableCUPS(self): '''disable the cups service :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True try: if self.linux: if not self.sh.disableService( self.svcname, serviceTarget=self.serviceTarget): retval = False self.detailedresults += "\nThere was a problem disabling the " + str( self.svcname) + " service" elif self.darwin: if not self.sh.disableService(self.svclongname, serviceTarget=self.svcname): retval = False self.detailedresults += "\nThere was a problem disabling the " + str( self.svcname) + " service" except Exception: raise return retval
class DisableRoot(Rule): def __init__(self, config, environ, logger, statechglogger): '''Constructor''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 160 self.rulename = "DisableRoot" self.formatDetailedResults("initialize") self.mandatory = True self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # configuration item instantiation datatype = 'bool' key = 'DISABLEROOT' instructions = "To disable this rule set the value of DISABLEROOT " + \ "to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.cmdhelper = CommandHelper(self.logger) self.guidance = ["NSA 1.3.14"] self.sethelptext() def report(self): '''DisableRoot.report() method to report whether root is disabled or not @author: dwalker :param self: essential if you override this definition :returns: boolean - True if system is compliant, False if not ''' try: self.detailedresults = "" compliant = False cmd = [ "/usr/bin/dscl", ".", "-read", "/Users/root", "AuthenticationAuthority" ] if not self.cmdhelper.executeCommand(cmd): self.detailedresults += "Unable to run the /usr/bin/dscl " + \ "command." compliant = False else: output = self.cmdhelper.getOutput() error = self.cmdhelper.getError() if output: for line in output: if search("No such key", line): compliant = True break elif error: for line in error: if search("No such key", line): compliant = True break 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 fix(self): '''DisableRoot.fix() method to run the command necessary to disable root on the mac. @author: dwalker :param self: essential if you override this definition :returns: boolean - True if able to fix successfully, False if not ''' try: if not self.ci.getcurrvalue(): return self.detailedresults = "" delete = [ "/usr/bin/dscl", ".", "-delete", "/Users/root", "AuthenticationAuthority" ] create = [ "/usr/bin/dscl", ".", "-create", "/Users/root", "passwd", "*" ] if not self.cmdhelper.executeCommand(delete): self.detailedresults += "wasn't able to run the command " + \ str(delete) + "\n" elif not self.cmdhelper.executeCommand(create): self.detailedresults += "wasn't able to run the command " + \ str(create) + "\n" 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
class CheckDupIDs(Rule): """This class checks the local accounts database for duplicate IDs. All accounts on a system must have unique UIDs. This class inherits the base Rule class, which in turn inherits observable. @note: Per RedHat STIG - CCE-RHEL7-CCE-TBD 2.4.1.2.3, check group references. All GIDs referenced in /etc/passwd must be defined in /etc/group Add a group to the system for each GID referenced without a corresponding group. Inconsistency in GIDs between /etc/passwd and /etc/group could lead to a user having unintended rights. Watch for LDAP issues (e.g. user default group changed to a group coming from LDAP). For Mac, also check that all the user's primary group ID's are in the local directory. """ def __init__(self, config, environ, logger, statechglogger): """ private method to initialize the module :param config: :param environ: :param logger: :param statechglogger: """ Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.commandhelper = CommandHelper(self.logger) self.statechglogger = statechglogger self.rulenumber = 58 self.rulename = 'CheckDupIDs' self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = True self.guidance = ['CCE-RHEL7-CCE-TBD 2.4.1.2.3'] self.sethelptext() self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.issuelist = [] self.auditonly = True def report(self): """CheckDuplicateIds.report(): produce a report on whether or not local accounts databases have duplicate UIDs present. :return: self.compliant - boolean; True if compliant, False if not """ try: self.detailedresults = "" self.issuelist = [] if self.environ.getosfamily() == 'darwin': self.compliant = self.osxcheck() else: self.compliant = self.nixcheck() if self.compliant: self.detailedresults = "No duplicate IDs detected." self.currstate = 'configured' else: self.detailedresults = "One or more Duplicate IDs was " + \ "detected on this system. For accountability purposes " + \ "all accounts are required to have unique UID values. " + \ str(self.issuelist) 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 osxcheck(self): """This version of the check should work for all OS X systems. @author: Ekkehard :return: result - boolean; True if no duplicates found; False if duplicates found """ try: result = False nixcheckresult = self.nixcheck() oscheckresult = True issue = "" # Check for duplicate users cmd = ["/usr/bin/dscl", ".", "list", "/users", "uid"] self.commandhelper.executeCommand(cmd) output = self.commandhelper.getOutput() userlist = [] uidlist = [] for line in output: linelist = line.split() user = linelist[0] uid = linelist[1] if user not in userlist: userlist.append(user) else: issue = "Duplicate User: '******' (UID = '" + uid + "')" self.issuelist.append(issue) oscheckresult = False if uid not in uidlist: uidlist.append(uid) else: issue = "Duplicate UID: '" + uid + "' (User = '******')" self.issuelist.append(issue) oscheckresult = False # Check for duplicate groups cmd = ["/usr/bin/dscl", ".", "list", "/groups", "gid"] self.commandhelper.executeCommand(cmd) output = self.commandhelper.getOutput() grouplist = [] gidlist = [] for line in output: linelist = line.split() group = linelist[0] gid = linelist[1] if group not in grouplist: grouplist.append(group) else: issue = "Duplicate Group: '" + group + "' (GID = '" + gid + "')" self.issuelist.append(issue) oscheckresult = False if gid not in gidlist: gidlist.append(gid) else: issue = "Duplicate GID: '" + gid + "' (Group = '" + group + "')" self.issuelist.append(issue) oscheckresult = False if (nixcheckresult & oscheckresult): result = True else: result = False return result except: raise def nixcheck(self): """This version of the check should work for all u systems. This is borrowed from the STOR code so the check methodology is well tested. :return: retval - boolean; True if no duplicates; False if duplicates found """ try: retval = True filelist = ['/etc/passwd', '/etc/group'] for adb in filelist: if os.path.exists(adb): self.logger.log( LogPriority.DEBUG, ['CheckDuplicateIds.nixcheck', "Checking : " + adb]) namelist = [] idlist = [] fdata = open(adb, 'r') for line in fdata: line = line.split(':') try: if len(line) > 2: self.logger.log(LogPriority.DEBUG, [ 'CheckDuplicateIds.nixcheck', "Checking line: " + str(line) ]) name = line[0] uid = line[2] self.logger.log( LogPriority.DEBUG, "Checking account: " + name + ' ' + uid) if name not in namelist: namelist.append(name) else: issue = "Duplicate Name: NAME('" + name + "'; UID('" + uid + "')" self.issuelist.append(issue) retval = False if uid not in idlist: idlist.append(uid) else: issue = "Duplicate UID: NAME('" + name + "'; UID('" + uid + "')" self.issuelist.append(issue) except (IndexError): # Some systems have malformed lines in the # accounts db due to poor administration # practices. Go to the next record. continue self.logger.log(LogPriority.DEBUG, "NAMELIST: " + str(namelist)) self.logger.log(LogPriority.DEBUG, "IDLIST: " + str(idlist)) fdata.close() return retval except: raise def getcolumn(self, file_to_read="", column=0, separator=":"): """Get the data out of <file_to_read> column <column> using <separator> Intended for use with the /etc/group and /etc/password files for getting and comparing group information. :param file_to_read: (Default value = "") :param column: (Default value = 0) :param separator: (Default value = ":") :return: column_data - dictionary; columnized data set of specified file """ if file_to_read and isinstance(column, int) and separator: reading = open(file_to_read, "r") for line in reading.readlines(): try: column_data = line.split(separator)[column] except IndexError: continue return column_data def checkgrouprefs(self): """Per RedHat STIG - CCE-RHEL7-CCE-TBD 2.4.1.2.3, check group references. All GIDs referenced in /etc/passwd must be defined in /etc/group Add a group to the system for each GID referenced without a corresponding group. Inconsistency in GIDs between /etc/passwd and /etc/group could lead to a user having unintended rights. Watch for LDAP issues (e.g. user default group changed to a group coming from LDAP). """ group_groups = self.getcolumn("/etc/group", 2) pwd_groups = self.getcolumn("/etc/passwd", 3) for group in pwd_groups: if not group in group_groups: message = "Group: " + str( group) + " is not in the passwd file." self.logger.log(LogPriority.INFO, message) self.issuelist.append(message) def checkmacgrouprefs(self): """Per RedHat STIG - CCE-RHEL7-CCE-TBD 2.4.1.2.3, check group references. All GIDs referenced in /etc/passwd must be defined in /etc/group Add a group to the system for each GID referenced without a corresponding group. Inconsistency in GIDs between /etc/passwd and /etc/group could lead to a user having unintended rights. Watch for LDAP issues (e.g. user default group changed to a group coming from LDAP). For Mac, check that al the user's primary group ID's are in the local directory. """ self.dscl = "/usr/bin/dscl" user_groups = [] dscl_users = [self.dscl, ".", "list", "/users"] self.commandhelper.executeCommand(dscl_users) output = self.commandhelper.getOutput() dscl_users = output system_users = ["avahi", "daemon", "nobody", "root"] for user in dscl_users: if re.match("^_", user) or user in system_users: continue dscl_user_group = [ self.dscl, ".", "read", "/Users/" + str(user), "gid" ] self.commandhelper.executeCommand(dscl_user_group) output = self.commandhelper.getOutput() self.logger.log(LogPriority.INFO, "output: " + str(output)) try: mygroup = output[0].split()[1] user_groups.append(mygroup) except (KeyError, IndexError): pass dscl_groups = [self.dscl, ".", "list", "/Groups"] self.commandhelper.executeCommand(dscl_groups) output = self.commandhelper.getOutput() group_groups = output for group in user_groups: if not group in group_groups: message = "Group: " + str( group) + " is not in the passwd file." self.logger.log(LogPriority.INFO, message) self.issuelist.append(message)
class STIGConfigureLoginWindowPolicy(Rule): '''Deploy LoginWindow Policy configuration profiles for OS X Yosemite 10.10''' def __init__(self, config, environ, logdispatch, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logdispatch, statechglogger) self.logger = logdispatch self.rulenumber = 362 self.rulename = "STIGConfigureLoginWindowPolicy" self.formatDetailedResults("initialize") self.sethelptext() self.rootrequired = True self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] }, 'fisma': 'high' } datatype = "bool" key = "STIGLOGINCONFIG" instructions = "To disable the installation of the login window " + \ "profile set the value of STIGLOGINCONFIG to False" default = True self.ci = self.initCi(datatype, key, instructions, default) self.iditerator = 0 self.identifier = "mil.disa.STIG.loginwindow.alacarte" if search("10\.10.*", self.environ.getosver()): # self.profile = "/Users/username/stonix/src/" + \ # "stonix_resources/files/" + \ # "U_Apple_OS_X_10-10_Workstation_V1R2_STIG_Login_Window_Policy.mobileconfig" self.profile = "/Applications/stonix4mac.app/Contents/" + \ "Resources/stonix.app/Contents/MacOS/" + \ "stonix_resources/files/" + \ "U_Apple_OS_X_10-10_Workstation_V1R2_STIG_Login_Window_Policy.mobileconfig" elif search("10\.11\.*", self.environ.getosver()): # self.profile = "/Users/username/stonix/src/" + \ # "stonix_resources/files/" + \ # "U_Apple_OS_X_10-11_V1R1_STIG_Login_Window_Policy.mobileconfig" self.profile = "/Applications/stonix4mac.app/Contents/" + \ "Resources/stonix.app/Contents/MacOS/" + \ "stonix_resources/files/" + \ "U_Apple_OS_X_10-11_V1R1_STIG_Login_Window_Policy.mobileconfig" else: # self.profile = "/Users/username/stonix/src/" + \ # "stonix_resources/files/" + \ # "U_Apple_macOS_10-12_V1R1_STIG_Login_Window_Policy.mobileconfig " self.profile = "/Applications/stonix4mac.app/Contents/" + \ "Resources/stonix.app/Contents/MacOS/" + \ "stonix_resources/files/" + \ "U_Apple_macOS_10-12_V1R1_STIG_Login_Window_Policy.mobileconfig" def report(self): try: compliant = False self.detailedresults = "" self.ch = CommandHelper(self.logger) cmd = ["/usr/bin/profiles", "-P"] if not self.ch.executeCommand(cmd): compliant = False self.detailedresults += "Unable to run profiles command\n" else: output = self.ch.getOutput() if output: for line in output: if search( "^There are no configuration profiles installed", line.strip()): compliant = False self.detailedresults += "There are no configuration profiles installed\n" break elif search("mil\.disa\.STIG\.loginwindow\.alacarte$", line.strip()): self.detailedresults += "Couldn't find loginwindow profile\n" compliant = True break 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 fix(self): try: if not self.ci.getcurrvalue(): return success = True if os.path.exists(self.profile): success = True self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges( self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) cmd = ["/usr/bin/profiles", "-I", "-F", self.profile] if not self.ch.executeCommand(cmd): debug = "Unable to install profile\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = ["/usr/bin/profiles", "-R", "-p", self.identifier] event = {"eventtype": "comm", "command": cmd} self.statechglogger.recordchgevent(myid, event) else: success = False self.rulesuccess = success 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 ConfigureDotFiles(Rule): '''A user who can modify another user's configuration files can likely execute commands with the other user's privileges, including stealing data, destroying files, or launching further attacks on the system. This rule ensures that no dot files within users' home directories possess the world/other - writable permission. ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.statechglogger = statechglogger self.rulenumber = 46 self.rulename = 'ConfigureDotFiles' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = False self.compliant = False datatype = 'bool' key = 'CONFIGUREDOTFILES' instructions = '''To prevent dot files in user home directories from \ being made non-world-writable, set the value of ConfigureDotFiles to False.''' default = True self.ConfigureDotFiles = self.initCi(datatype, key, instructions, default) self.guidance = ['CIS', 'NSA 2.3.4.3', 'CCE-4561-7'] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } def report(self): '''The report method examines the current configuration and determines whether or not it is correct. If the config is correct then the self.compliant and self.detailed results properties are updated to reflect the system status. :returns: self.compliant :rtype: boolean @author: Breen Malmberg ''' # defaults dotfilelist = [] self.compliant = True self.detailedresults = "" self.cmdhelper = CommandHelper(self.logger) try: if self.environ.getostype() == 'Mac OS X': dotfilelist = self.buildmacdotfilelist() else: dotfilelist = self.buildlinuxdotfilelist() for item in dotfilelist: # is item world writable? if isWritable(self.logger, item, 'other'): self.compliant = False self.detailedresults += '\nFound world writable dot file: ' \ + str(item) except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + 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 buildlinuxdotfilelist(self): '''build a list of linux dot files for the current user :returns: dotfilelist :rtype: list @author: Breen Malmberg ''' dotfilelist = [] try: f = open('/etc/passwd', 'r') contentlines = f.readlines() f.close() for line in contentlines: line = line.split(':') if len(line) >= 6 and line[5] and \ self.environ.geteuidhome() == str(line[5]): line[2] = int(line[2]) if line[2] >= 500 and not re.search('nfsnobody', line[0]): if os.path.exists(line[5]): filelist = os.listdir(line[5]) for i in range(len(filelist)): if re.search('^\.', filelist[i]): dotfilelist.append(line[5] + '/' + filelist[i]) except Exception: raise return dotfilelist def buildmacdotfilelist(self): '''build a list of mac dot files for the current user :returns: dotfilelist :rtype: list @author: Breen Malmberg ''' dotfilelist = [] users = [] homedirs = [] try: cmd = ["/usr/bin/dscl", ".", "-list", "/Users"] try: self.cmdhelper.executeCommand(cmd) except OSError as oser: if re.search('DSOpenDirServiceErr', str(oser)): self.detailedresults += '\n' + str(oser) self.logger.log(LogPriority.DEBUG, self.detailedresults) output = self.cmdhelper.getOutput() error = self.cmdhelper.getError() if error: self.detailedresults += '\nCould not get a list of user ' + \ 'home directories. Returning empty list...' return dotfilelist if output: for user in output: if not re.search('^_|^root|^\/$', user): users.append(user.strip()) debug = "Adding user " + user.strip() + " to users " \ + "list" self.logger.log(LogPriority.DEBUG, debug) if users: for user in users: try: currpwd = pwd.getpwnam(user) homedirs.append(currpwd[5]) except KeyError: continue if homedirs: for homedir in homedirs: if self.environ.geteuidhome() == homedir: filelist = os.listdir(homedir) for i in range(len(filelist)): if re.search('^\.', filelist[i]): dotfilelist.append(homedir + '/' + filelist[i]) except Exception: raise return dotfilelist def fix(self): '''remove any world writable flags from any dot files in user's home directory @author: Breen Malmberg ''' # defaults dotfilelist = [] self.detailedresults = "" self.rulesuccess = True if self.ConfigureDotFiles.getcurrvalue(): try: if self.environ.getostype() == 'Mac OS X': dotfilelist = self.buildmacdotfilelist() else: dotfilelist = self.buildlinuxdotfilelist() if dotfilelist: for item in dotfilelist: if os.path.isfile(item): try: os.system('chmod o-w ' + item) except (OSError, IOError): self.rulesuccess = False self.detailedresults += '\nCould not chmod: ' \ + str(item) except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + \ str(err) + " - " + 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 BootSecurity(Rule): """The Boot Security rule configures the system to run a job at system boot time that handles turning off potential vulnerability points such as: wifi, bluetooth, microphones, and cameras. """ 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.rulenumber = 18 self.rulename = 'BootSecurity' 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'] } } self.servicehelper = ServiceHelper(environ, logger) self.ch = CommandHelper(self.logdispatch) if os.path.exists('/bin/systemctl'): self.type = 'systemd' elif os.path.exists('/sbin/launchd'): self.type = 'mac' else: self.type = 'rclocal' self.rclocalpath = '/etc/rc.local' if os.path.islink(self.rclocalpath): paths = ['/etc/rc.d/rc.local', '/etc/init.d/rc.local'] for rcpath in paths: if os.path.isfile(rcpath): self.rclocalpath = rcpath self.logdispatch.log(LogPriority.DEBUG, 'Using rc.local file ' + self.rclocalpath) datatype = 'bool' key = 'BOOTSECURITY' instructions = """To disable this rule set the value of BOOTSECURITY to False.""" default = True self.bootci = self.initCi(datatype, key, instructions, default) datatype2 = 'bool' key2 = 'ENABLEFIPS' instructions2 = """!WARNING! DO NOT ENABLE THIS OPTION IF YOUR SYSTEM IS ARLEADY FDE ENCRYPTED! To enable full fips compliance on this system, at boot, set the value of ENABLEFIPS to True.""" default2 = False self.fips_ci = self.initCi(datatype2, key2, instructions2, default2) self.set_paths() def set_paths(self): """ """ self.systemd_boot_service_file = "/etc/systemd/system/stonixBootSecurity.service" self.rc_boot_script = "/usr/bin/stonix_resources/stonixBootSecurityLinux.py" self.systemd_service_name = "stonixBootSecurity.service" self.stonix_launchd_plist = "/Library/LaunchDaemons/gov.lanl.stonix.bootsecurity.plist" self.stonix_launchd_name = "gov.lanl.stonix.bootsecurity" self.grub_file = "/etc/default/grub" self.grub_config_file = "" grub_configs = [ "/boot/grub2/grub.cfg", "/boot/efi/EFI/redhat/grub.cfg" ] for c in grub_configs: if os.path.isfile(c): self.grub_config_file = c break self.grub_updater_cmd = "" grub_updater_locs = [ "/usr/sbin/grub2-mkconfig", "/sbin/grub2-mkconfig", "/usr/sbin/update-grub", "/sbin/update-grub" ] for l in grub_updater_locs: if os.path.isfile(l): self.grub_updater_cmd = l break if self.grub_updater_cmd in [ "/usr/sbin/grub2-mkconfig", "/sbin/grub2-mkconfig" ]: self.grub_updater_cmd += " -o " + self.grub_config_file def auditsystemd(self): """ check whether the stonixbootsecurity.service service module is loaded :return: boolean; True if the stonixbootsecurity.service service module is loaded, False if not """ compliant = True self.stonix_boot_service_contents = """[Unit] Description=Stonix Boot Security After=network.target [Service] ExecStart=/usr/bin/stonix_resources/stonixBootSecurityLinux.py [Install] WantedBy=multi-user.target """ try: # check if service file exists if not os.path.isfile(self.systemd_boot_service_file): compliant = False self.detailedresults += "\nstonix boot service unit does not exist" else: # check contents of service file f = open(self.systemd_boot_service_file, "r") contents = f.read() f.close() if contents != self.stonix_boot_service_contents: compliant = False self.detailedresults += "\nstonix boot service unit contents incorrect" # check if service is enabled if not self.servicehelper.auditService(self.systemd_service_name): compliant = False self.detailedresults += "\nstonix boot service is not enabled" except: raise return compliant def auditrclocal(self): """ check whether the rclocal configuration file contains the correct stonixBootSecurity line entry :return: compliant - boolean; True if compliant, False if not """ compliant = True try: tmppath = self.rc_boot_script + ".stonixtmp" data = {"python3": self.rc_boot_script} self.rc_boot_security_editor = KVEditorStonix( self.statechglogger, self.logdispatch, "conf", self.rc_boot_script, tmppath, data, "present", "space") if not self.rc_boot_security_editor.report(): self.detailedresults += "\nThe following config line is missing or incorrect from " + str( self.rc_boot_script) + "\n" + "\n".join( self.rc_boot_security_editor.fixables) compliant = False except: raise return compliant def auditmac(self): """ check whether the stonixbootsecurity launchd job exists :return: """ compliant = True self.logdispatch.log(LogPriority.DEBUG, "Looking for macOS launchd job") self.stonix_plist_contents = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>gov.lanl.stonix.bootsecurity</string> <key>Program</key> <string>/Applications/stonix4mac.app/Contents/Resources/stonix.app/Contents/MacOS/stonix_resources/stonixBootSecurityMac</string> <key>RunAtLoad</key> <true/> </dict> </plist>""" try: if not os.path.exists(self.stonix_launchd_plist): compliant = False self.detailedresults += "\nCould not locate stonix boot security launchd job" except: raise return compliant def report_boot_fips(self): """ :return: """ found_fips = False compliant = True try: if not self.fips_ci.getcurrvalue(): self.logdispatch.log( LogPriority.DEBUG, "fips compliance check disabled. Skipping fips compliance check." ) return compliant else: self.logdispatch.log( LogPriority.DEBUG, "fips compliance check enabled. Checking for fips compliance..." ) # check grub template file for fips if self.grub_file: f = open(self.grub_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search('^GRUB_CMDLINE_LINUX_DEFAULT=', line, re.I): if re.search("fips=1", line, re.I): found_fips = True # check permanent grub config file for fips if self.grub_config_file: f = open(self.grub_config_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("fips=1", line, re.I): found_fips = True if self.is_luks_encrypted(): if found_fips: # fips=1 will break boot config if luks encrypted compliant = False self.detailedresults += "\nfips=1 config option found in boot config line. This will break system boot while the system is luks encrypted. Will remove this line and fips compatibility for luks will be configured instead." else: self.detailedresults += "\nNo problems detected with boot config" else: if not found_fips: compliant = False self.detailedresults += "\nfips=1 config option not found in boot config line. As this system is not luks encrypted, this line will be added to the boot config." else: self.detailedresults += "\nfips enabled in boot config" except: raise return compliant def report(self): """ check whether the current system complies with the boot security settings to disable wifi, bluetooth and microphone at boot time :return: self.compliant - boolean; True if system is compliant, False if not """ self.detailedresults = "" self.compliant = True try: if self.type == 'mac': self.logdispatch.log(LogPriority.DEBUG, 'Checking for Mac plist') if not self.auditmac(): self.compliant = False self.detailedresults += '\nPlist for stonixBootSecurity Launch Daemon not found.' elif self.type == 'systemd': self.logdispatch.log(LogPriority.DEBUG, 'Checking for systemd service') if not self.auditsystemd(): self.compliant = False self.detailedresults += '\nService for stonixBootSecurity not active.' elif self.type == 'rclocal': self.logdispatch.log(LogPriority.DEBUG, 'Checking rc.local') if not self.auditrclocal(): self.compliant = False self.detailedresults += '\nstonixBootSecurity-Linux not scheduled in rc.local.' else: self.compliant = False self.detailedresults += "\nThis platform is not supported by STONIX" if self.compliant: self.detailedresults += '\nstonixBootSecurity correctly scheduled for execution at boot.' if not self.report_boot_fips(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except: self.compliant = False self.detailedresults += 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 setsystemd(self): """ create a systemd service unit which will run an installed script to disable wifi, bluetooth and microphone at boot time """ self.logdispatch.log(LogPriority.DEBUG, "Creating stonix boot security service unit") try: # create the new service unit f = open(self.systemd_boot_service_file, "w") f.write(self.stonix_boot_service_contents) f.close() myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": self.systemd_boot_service_file } self.statechglogger.recordchgevent(myid, event) os.chown(self.systemd_boot_service_file, 0, 0) os.chmod(self.systemd_boot_service_file, 0o644) # make the service manager aware of the new service unit reloadcmd = '/bin/systemctl daemon-reload' try: self.ch.executeCommand(reloadcmd) except: pass # ensure that the new service is enabled self.servicehelper.enableService('stonixBootSecurity') except: raise def setrclocal(self): """ install and run a boot security script which will disable wifi, bluetooth and microphone at boot time """ success = True try: if not self.rc_boot_security_editor.fix(): self.logdispatch.log(LogPriority.DEBUG, "KVEditor failed to fix") success = False elif not self.rc_boot_security_editor.commit(): self.logdispatch.log(LogPriority.DEBUG, "KVEditor failed to commit changes") success = False except: raise return success def setmac(self): """ install a boot security plist on mac, which will run an oascript to disable microphone on mac at boot time """ success = True try: whandle = open(self.stonix_launchd_plist, 'w') whandle.write(self.stonix_plist_contents) whandle.close() os.chown(self.stonix_launchd_plist, 0, 0) os.chmod(self.stonix_launchd_plist, 0o644) except: raise return success def fix_boot_fips(self): """ :return: """ success = True fixed_fips = False tmpfile = self.grub_file + ".stonixtmp" try: if not self.fips_ci.getcurrvalue(): self.logdispatch.log( LogPriority.DEBUG, "fips compliance option disabled. Skipping fips compliance fix..." ) return success else: self.logdispatch.log( LogPriority.DEBUG, "fips compliance option enabled. Proceeding with fix compliance fix..." ) if self.environ.getosname() == "RHEL": self.logdispatch.log( LogPriority.DEBUG, "System detected as RHEL. Running RHEL specific fixes...") if not self.fix_rhel7_boot_fips(): success = False return success else: self.logdispatch.log( LogPriority.DEBUG, "System is not RHEL-based. Running generic fixes...") f = open(self.grub_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("^GRUB_CMDLINE_LINUX_DEFAULT=", line, re.I): contentlines = [ c.replace(line, line.strip()[:-1] + ' fips=1"\n') for c in contentlines ] fixed_fips = True if not fixed_fips: contentlines.append( 'GRUB_CMDLINE_LINUX_DEFAULT="splash quiet audit=1 fips=1"\n' ) tf = open(tmpfile, "w") tf.writelines(contentlines) tf.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.grub_file} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(tmpfile, self.grub_file, myid) os.rename(tmpfile, self.grub_file) self.ch.executeCommand(self.grub_updater_cmd) except: raise return success def remove_fips_line(self): """ the fips=1 configuration option at the end of the linuxefi boot line in grub config (for efi-based systems) causes rhel to revert to an emergency dracut mode instead of booting normally, when the system is encrypted with luks this method will ensure that line is removed and the grub configuration is updated. :return: """ success = True self.logdispatch.log( LogPriority.DEBUG, "Attempting to remove fips=1 option from grub boot config...") tmpfile = self.grub_file + ".stonixtmp" f = open(self.grub_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("^GRUB_CMDLINE_LINUX_DEFAULT=", line, re.I): self.logdispatch.log(LogPriority.DEBUG, "fips=1 found in boot config file") line2 = line.replace("fips=1", "") self.logdispatch.log( LogPriority.DEBUG, "removing fips=1 from " + str(self.grub_file)) contentlines = [c.replace(line, line2) for c in contentlines] tf = open(tmpfile, "w") tf.writelines(contentlines) tf.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.grub_file} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(tmpfile, self.grub_file, myid) os.rename(tmpfile, self.grub_file) self.logdispatch.log(LogPriority.DEBUG, "regenerating efi boot config file...") self.ch.executeCommand(self.grub_updater_cmd) retcode = self.ch.getReturnCode() if retcode != 0: self.logdispatch.log(LogPriority.WARNING, "Failed to update efi boot config file!") f = open(self.grub_config_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("fips=1", line, re.I): success = False if not success: self.logdispatch.log( LogPriority.WARNING, "fips=1 option still found in efi boot configuration!") else: self.logdispatch.log( LogPriority.DEBUG, "fips option successfully removed from efi boot configuration") return success def is_luks_encrypted(self): """ check all drive devices to see if any are luks encrypted :return: luks_encrypted :rtype: bool """ luks_encrypted = False command = "/sbin/blkid" devices = [] if not os.path.isfile(command): self.logdispatch.log( LogPriority.WARNING, "Unable to check devices for luks encryption due to missing utility 'blkid'" ) return luks_encrypted self.logdispatch.log(LogPriority.DEBUG, "Checking if any devices are luks encrypted...") try: self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('TYPE="crypto_LUKS"', line, re.I): luks_encrypted = True try: devices.append(str(line.split()[0])) except (IndexError, KeyError): continue except: raise for d in devices: if re.search(":", d): devices = [d.replace(":", "") for d in devices] if luks_encrypted: self.logdispatch.log( LogPriority.DEBUG, "The following devices are luks encrypted:\n" + "\n".join(devices)) return luks_encrypted def configure_luks_compatibility(self): """ configure rhel 7 systems, which are LUKS encrypted, to be compatible with fips https://access.redhat.com/solutions/137833 :return: """ prelink_pkg = "prelink" prelink = "/usr/sbin/prelink" dracut_aes_pkg = "dracut-fips-aesni" dracut_fips_pkg = "dracut-fips" prelink_conf_file = "/etc/sysconfig/prelink" dracut = "/usr/bin/dracut" grep = "/usr/bin/grep" mv = "/usr/bin/mv" prelink_installed = False aes_supported = False try: if self.is_luks_encrypted(): if not self.remove_fips_line(): self.detailedresults += "\nFailed to remove fips=1 from efi boot configuration file. Please run: 'sudo grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg' manually!" # check for cpu aes compatibility self.ch.executeCommand(grep + " -w aes /proc/cpuinfo") outputstring = self.ch.getOutputString() if re.search("aes", outputstring, re.I): aes_supported = True # check if prelink package is installed if self.ph.check(prelink_pkg): prelink_installed = True # install dracut fips package self.ph.install(dracut_fips_pkg) # install dracut aes package if cpu supports it if aes_supported: self.ph.install(dracut_aes_pkg) # disable prelinking if installed if prelink_installed: f = open(prelink_conf_file, "w") f.write("PRELINKING=no") f.close() os.chmod(prelink_conf_file, 0o644) os.chown(prelink_conf_file, 0, 0) self.ch.executeCommand(prelink + " -uav") # backup existing initramfs self.ch.executeCommand( mv + " -v /boot/initramfs-$(uname -r).img{,.bak}") # rebuild initramfs (this command may take some time) self.ch.executeCommand(dracut) except: raise def fix_rhel7_boot_fips(self): """ enable fips compliance on redhat 7 systems https://access.redhat.com/solutions/137833 :return: success :rtype: bool """ success = True self.ph = Pkghelper(self.logdispatch, self.environ) grubby = "/usr/sbin/grubby" findmnt = "/usr/bin/findmnt" try: # configure the system to be compatible with luks and fips self.configure_luks_compatibility() # add fips=1 to kernel boot line (requires sytem restart to take effect) self.ch.executeCommand(grubby + " --update-kernel=$(" + grubby + " --default-kernel) --args=fips=1") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to enable fips compliance in kernel boot line" # update boot partition info uuid = "" self.ch.executeCommand(findmnt + " -no uuid /boot") retcode = self.ch.getReturnCode() if retcode == 0: uuid = self.ch.getOutputString() else: success = False self.detailedresults += "\nFailed to update boot partition info" if uuid: self.ch.executeCommand( "[[ -n $uuid ]] && " + grubby + " --update-kernel=$(" + grubby + " --default-kernel) --args=boot=UUID=${uuid}") except: raise return success def fix(self): """ install system job which will run and disable bluetooth, microphone and wifi at boot """ self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: if self.bootci.getcurrvalue(): if self.type == 'mac': self.logdispatch.log(LogPriority.DEBUG, 'Creating Mac plist') self.setmac() elif self.type == 'systemd': self.logdispatch.log(LogPriority.DEBUG, 'Creating systemd service') self.setsystemd() elif self.type == 'rclocal': self.logdispatch.log(LogPriority.DEBUG, 'Creating rc.local entry') self.setrclocal() else: self.detailedresults = 'ERROR: Fix could not determine where boot job should be scheduled!' self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.rulesuccess = False if self.fips_ci.getcurrvalue(): if not self.fix_boot_fips(): self.rulesuccess = False else: self.logdispatch.log(LogPriority.DEBUG, "Rule not enabled. Nothing was done.") except (KeyboardInterrupt, SystemExit): raise except: self.rulesuccess = False self.detailedresults += 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 Zypper(object): ''' The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. @author: Derek T Walker @change: 2012/08/08 Derek Walker - Original Implementation @change: 2014/09/10 dkennel - Added -n option to search command string @change: 2014/12/24 Breen Malmberg - fixed a typo in the old search string; fixed multiple pep8 violations; changed search strings to be match exact and search for installed or available separately @change: 2015/08/20 eball - Added getPackageFromFile and self.rpm var @change: 2016/08/02 eball - Moved checkInstall return out of else block @change: 2017/04/19 Breen Malmberg - refactored multiple methods; cleaned up doc strings; added logging; added two methods: Update and checkUpdate; removed detailedresults reset in __init__ (this should always be handled in the calling rule); replaced detailedresults instances with logging; added the flag "--quiet" to the install variable ''' def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.zyploc = "/usr/bin/zypper" self.install = self.zyploc + " --non-interactive --quiet install " self.remove = self.zyploc + " --non-interactive remove " self.searchi = self.zyploc + " --non-interactive search --match-exact -i " self.searchu = self.zyploc + " --non-interactive search --match-exact -u " self.updates = self.zyploc + " lu " self.upzypp = self.zyploc + " up " self.rpm = "/usr/bin/rpm -q " self.pkgtype = "zypper" def installpackage(self, package): ''' Install a package. Return a bool indicating success or failure. @param package: string; Name of the package to be installed, must be recognizable to the underlying package manager. @return: installed @rtype: bool @author: Derek Walker @change: Breen Malmberg - 12/24/2014 - fixed method doc string formatting @change: Breen Malmberg - 10/1/2018 - added check for package manager lock and retry loop ''' installed = True maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to install package due to zypper package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.install + package) retcode = self.ch.getReturnCode() if retcode != 0: raise repoError('zypper', retcode) except repoError as repoerr: if not repoerr.success: installed = False if installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " installed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to install package " + str(package)) except Exception: raise return installed def removepackage(self, package): ''' Remove a package. Return a bool indicating success or failure. @param package: string; Name of the package to be removed, must be recognizable to the underlying package manager. @return: removed @rtype: bool @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - fixed method doc string formatting; fixed an issue with var 'removed' not being initialized before it was called ''' removed = True maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to remove package due to zypper package manager being in-use by another process.") removed = False return removed else: self.logger.log(LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.remove + package) retcode = self.ch.getReturnCode() if retcode != 0: raise repoError('zypper', retcode) except repoError as repoerr: if not repoerr.success: removed = False if removed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was removed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to remove package " + str(package)) except Exception: raise return removed def checkInstall(self, package): ''' Check the installation status of a package. Return a bool; True if the package is installed. @param string package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return: bool @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - fixed method doc string formatting @change: 12/24/2014 - Breen Malmberg - changed var name 'found' to 'installed' @change: 12/24/2014 - Breen Malmberg - now uses correct search syntax @change: 12/24/2014 - Breen Malmberg - removed detailedresults update on 'found but not installed' as this no longer applies to this method ''' installed = True errstr = "" maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check status of package due to zypper package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.searchi + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('zypper', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: installed = False if installed: self.logger.log(LogPriority.DEBUG, " Package " + str(package) + " is installed") else: self.logger.log(LogPriority.DEBUG, " Package " + str(package) + " is NOT installed") except Exception: raise return installed def checkAvailable(self, package): ''' check if given package is available to install on the current system @param: package string name of package to search for @return: bool @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - added method documentation @change: 12/24/2014 - Breen Malmberg - changed var name 'found' to 'available' @change: 12/24/2014 - Breen Malmberg - fixed search syntax and updated search variable name @change: Breen Malmberg - 5/1/2017 - replaced detailedresults with logging; added parameter validation ''' available = True maxtries = 12 trynum = 0 while psRunning("zypper"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to zypper package manager being in-use by another process.") available = False return available else: self.logger.log(LogPriority.DEBUG, "zypper package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.searchu + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() output = self.ch.getOutput() if retcode != 0: raise repoError('zypper', retcode, str(errstr)) else: for line in output: if re.search(package, line): available = True except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(repoerr)) return False if available: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is NOT available to install") except Exception: raise return available def checkUpdate(self, package=""): ''' check for available updates for specified package. if no package is specified, then check for updates to the entire system. @param package: string; name of package to check @return: updatesavail @rtype: bool @author: Breen Malmberg ''' # zypper does not have a package-specific list updates mechanism # you have to list all updates or nothing updatesavail = True try: try: if package: self.ch.executeCommand(self.updates + " | grep " + package) else: self.ch.executeCommand(self.updates) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('zypper', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) updatesavail = False if package: if not updatesavail: self.logger.log(LogPriority.DEBUG, "No updates are available for package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "Updates are available for package " + str(package)) else: if not updatesavail: self.logger.log(LogPriority.DEBUG, "No updates are available") else: self.logger.log(LogPriority.DEBUG, "Updates are available") except Exception: raise return updatesavail def Update(self, package=""): ''' update a specified package if no package name is specified, then update all packages on the system @param package: string; name of package to update @return: updated @rtype: bool @author: Breen Malmberg ''' updated = True try: try: self.ch.executeCommand(self.upzypp + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('zypper', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) updated = False except Exception: raise return updated def getPackageFromFile(self, filename): '''Returns the name of the package that provides the given filename/path. @param: string filename : The name or path of the file to resolve @return: string name of package if found, None otherwise @author: Eric Ball ''' packagename = "" try: try: self.ch.executeCommand(self.rpm + "-f " + filename) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() outputstr = self.ch.getOutputString() if retcode != 0: raise repoError('zypper', retcode, str(errstr)) # return "" else: packagename = outputstr except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) else: packagename = outputstr except Exception: raise return packagename def getInstall(self): ''' return the install command string for the zypper pkg manager @return: string @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - added method documentation ''' return self.install def getRemove(self): ''' return the uninstall/remove command string for the zypper pkg manager @return: string @author: Derek Walker @change: 12/24/2014 - Breen Malmberg - added method documentation ''' return self.remove
class ConfigureNetworkTime(Rule): """The ConfigureNetworkTime class specifies network time servers and enables the appropriate service""" def __init__(self, config, environ, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logger, statechglogger) self.rulenumber = 96 self.rulename = 'ConfigureNetworkTime' self.formatDetailedResults("initialize") self.logger = logger self.sethelptext() self.mandatory = True self.rootrequired = True self.guidance = [ 'CIS', 'NSA(3.10.2)', 'CCE-4134-3', 'CCE-4385-1', 'CCE-4032-9', 'CCE-4424-8', 'CCE-3487-6' ] # init CI self.ci = self.initCi( "bool", "CONFIGURENETWORKTIME", "To prevent STONIX from configuring network time servers for this system, set the value of CONFIGURENETWORKTIME to False.", True) self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.libc = getLibc() def _set_paths(self): """ """ self.time_package = "chrony" self.time_conf_file = "" time_conf_files = ["/etc/chrony.conf", "/etc/chrony/chrony.conf"] for cf in time_conf_files: if os.path.isfile(cf): self.time_conf_file = cf break def _test_connection(self, hostname): """ :param str hostname: host to test connection to :return: reachable :rtype: bool """ reachable = False if isinstance(hostname, list): for h in hostname: response = os.system("ping -c 1 " + h) if response == 0: reachable = True break else: response = os.system("ping -c 1 " + hostname) if response == 0: reachable = True return reachable def _report_install(self): """ :return: installed :rtype: bool """ installed = True if not self.ph.check('chrony'): installed = False return installed def _report_conf(self): """ :return: configured :rtype: bool """ configured = True self.time_conf_dict = { "driftfile": "/var/lib/chrony/drift", "makestep": "1.0 3", "rtcsync": "", "logdir": "/var/log/chrony", "cmddeny": "all", "server": [] } for ts in self.time_servers: self.time_conf_dict["server"].append(ts) tmpfile = self.time_conf_file + ".stonixtmp" self.time_conf_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.time_conf_file, tmpfile, self.time_conf_dict, "present", "space") if not self.time_conf_editor.report(): configured = False self.detailedresults += "\nThe following configuration options are incorrect in " + str( self.time_conf_file) + ":\n" + "\n".join( self.time_conf_editor.fixables) return configured def _report_linux(self): """ :return: compliant :rtype: bool """ self._set_paths() compliant = True if not self._test_connection(self.time_servers): compliant = False self.detailedresults += "\nCould not reach network time servers" if not self._report_install(): compliant = False self.detailedresults += "\nCould not install network time package" elif not self._report_conf(): compliant = False self.detailedresults += "\nFailed to properly configure network time configuration file" return compliant def report(self): """determine whether the fix() method of this rule has run successfully yet or not :return: self.compliant :rtype: bool """ self.detailedresults = "" # UPDATE THIS SECTION IF THE CONSTANTS BEING USED IN THIS CLASS CHANGE self.constlist = [NTPSERVERSEXTERNAL, NTPSERVERSINTERNAL] if not any(self.constlist): self.compliant = False self.detailedresults += "\nThis rule requires that at least one of the following constants, in localize.py, be defined and not None: NTPSERVERSEXTERNAL, NTPSERVERSINTERNAL" self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) if self._test_connection(NTPSERVERSINTERNAL): self.time_servers = NTPSERVERSINTERNAL else: self.time_servers = NTPSERVERSEXTERNAL self.compliant = True try: if self.environ.getosfamily() == "darwin": self.ss = "/usr/sbin/systemsetup" if not self._report_darwin(): self.compliant = False else: if not self._report_linux(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except: 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 _report_darwin(self): """determine rule compliance status for darwin based systems :return: configured :rtype: """ # defaults configured = True usingnetworktime = False timeserverfound = False try: cmd = [self.ss, "-getnetworktimeserver"] self.ch.executeCommand(cmd) self.output = self.ch.getOutput() for line in self.output: for item in self.time_servers: if re.search(item, line): timeserverfound = True cmd2 = [self.ss, "-getusingnetworktime"] self.ch.executeCommand(cmd2) self.output2 = self.ch.getOutput() for line in self.output2: if re.search('On', line): usingnetworktime = True if not usingnetworktime: self.detailedresults += '\nusingnetworktime not set to on' configured = False if not timeserverfound: self.detailedresults += '\ncorrect time server not configured' configured = False except Exception: raise return configured def fix(self): """Decide which fix sub method to run, and run it to configure network time :return: self.rulesuccess :rtype: bool """ # UPDATE THIS SECTION IF THE CONSTANTS BEING USED IN THIS CLASS CHANGE if not self.checkConsts(self.constlist): self.rulesuccess = False self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return self.rulesuccess self.detailedresults = "" self.iditerator = 0 self.rulesuccess = True try: if self.ci.getcurrvalue(): if self.environ.getosfamily() == "darwin": if not self._fix_darwin(): self.rulesuccess = False else: if not self._fix_linux(): self.rulesuccess = False else: self.detailedresults += "\nRule was not enabled. No action was taken." 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 _fix_linux(self): """ :return: success :rtype: bool """ success = True if not self._fix_install(): success = False else: self._set_paths() self._report_conf() if not self._fix_conf(): success = False return success def _fix_install(self): """ :return: success :rtype: bool """ success = True if not self.ph.install(self.time_package): success = False self.logger.log(LogPriority.DEBUG, "Failed to install network time package") return success def _fix_conf(self): """ :return: success :rtype: bool """ success = True if not self.time_conf_editor.fix(): success = False self.logger.log(LogPriority.DEBUG, "KVEditor failed to fix") elif not self.time_conf_editor.commit(): success = False self.logger.log(LogPriority.DEBUG, "KVEditor failed to commit") return success def _fix_darwin(self): """ private method to perform fix operations for mac os x machines :return: fixresult :rtype: bool """ parseoutput1 = [] parseoutput2 = [] fixresult = True try: # set network time on cmd1 = [self.ss, "-setusingnetworktime", "on"] try: self.ch.executeCommand(cmd1) self.libc.sync() except Exception as errmsg: self.logger.log(LogPriority.DEBUG, str(errmsg)) try: # set undo cmd to restore original network time state for line in self.output2: if re.search('Network Time', line): parseoutput1 = line.split(':') originaltimestate = parseoutput1[1].strip() except KeyError: originaltimestate = "off" undocmd1 = self.ss + " -setusingnetworktime " + originaltimestate event = {"eventtype": "commandstring", "command": undocmd1} self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) # set network time server cmd2 = [ self.ss, "-setnetworktimeserver", str(self.time_servers[0]) ] try: self.ch.executeCommand(cmd2) except Exception as errmsg: self.logger.log(LogPriority.DEBUG, str(errmsg)) try: # set undo cmd to reinstate original time server for line in self.output: if re.search('Network Time Server', line): parseoutput2 = line.split(':') originalnetworktimeserver = parseoutput2[1].strip() except (IndexError, KeyError): originalnetworktimeserver = NTPSERVERSINTERNAL[0] undocmd2 = self.ss + " -setusingnetworktime " + \ originalnetworktimeserver event = {"eventtype": "commandstring", "command": undocmd2} self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) except: raise return fixresult
class AptGet(object): '''Linux specific package manager for distributions that use the apt-get command to install packages. @author: Derek T Walker @change: 2012/08/06 dwalker - Original Implementation @change: 2015/08/20 eball - Added getPackageFromFile ''' def __init__(self, logger): self.logger = logger self.detailedresults = "" self.ch = CommandHelper(self.logger) self.install = "sudo DEBIAN_FRONTEND=noninteractive /usr/bin/apt-get -y --force-yes install " self.remove = "/usr/bin/apt-get -y remove " ############################################################################### def installpackage(self, package): '''Install a package. Return a bool indicating success or failure. @param string package : Name of the package to be installed, must be recognizable to the underlying package manager. @return bool : @author dwalker''' try: self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg installed successfully" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: # try to install for a second time self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + \ " pkg installed successfully" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: self.detailedresults = package + " pkg not able to install" self.logger.log(LogPriority.DEBUG, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def removepackage(self, package): '''Remove a package. Return a bool indicating success or failure. @param string package : Name of the package to be removed, must be recognizable to the underlying package manager. @return bool : @author''' try: self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: self.detailedresults = package + " pkg removed successfully" self.logger.log(LogPriority.INFO, self.detailedresults) return True else: self.detailedresults = package + " pkg not able to be removed" self.logger.log(LogPriority.INFO, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. @param: string package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return: bool : @author: dwalker''' try: stringToMatch = "(.*)" + package + "(.*)" self.ch.executeCommand(["/usr/bin/dpkg", "-l", package]) info = self.ch.getOutput() match = False for line in info: if search(stringToMatch, line): parts = line.split() if parts[0] == "ii": match = True break else: continue if match: self.detailedresults = package + " pkg found and installed\n" self.logger.log(LogPriority.INFO, self.detailedresults) return True else: self.detailedresults = package + " pkg not installed\n" self.logger.log(LogPriority.INFO, self.detailedresults) return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def checkAvailable(self, package): try: found = False retval = call(["/usr/bin/apt-cache", "search", package], stdout=PIPE, stderr=PIPE, shell=False) if retval == 0: message = Popen(["/usr/bin/apt-cache", "search", package], stdout=PIPE, stderr=PIPE, shell=False) info = message.stdout.readlines() while message.poll() is None: continue message.stdout.close() for line in info: if search(package, line): found = True if found: self.detailedresults = package + " pkg is available" else: self.detailedresults = package + " pkg is not available" else: self.detailedresults = package + " pkg not found or may be \ misspelled" self.logger.log(LogPriority.DEBUG, self.detailedresults) return found except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise ############################################################################### def getPackageFromFile(self, filename): '''Returns the name of the package that provides the given filename/path. @param: string filename : The name or path of the file to resolve @return: string name of package if found, None otherwise @author: Eric Ball ''' try: self.ch.executeCommand("dpkg -S " + filename) if self.ch.getReturnCode() == 0: output = self.ch.getOutputString() pkgname = output.split(":")[0] return pkgname else: return None except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) raise (self.detailedresults) ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove
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 BlockSystemAccounts(Rule): """this module ensures that no system accounts have a login shell""" def __init__(self, config, enviro, logger, statechglogger): """ private method to initialize this module :param config: configuration object instance :param enviro: environment object instance :param logger: logdispatcher object instance :param statechglogger: statechglogger object instance """ Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.environ = enviro self.rulenumber = 40 self.rulename = 'BlockSystemAccounts' self.formatDetailedResults("initialize") self.compliant = False self.mandatory = True self.sethelptext() self.rootrequired = True datatype = 'bool' key = 'BLOCKSYSACCOUNTS' instructions = """If you have system accounts that need to have valid \ shells set the value of this to False, or No.""" default = True self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ['CIS', 'NSA(2.3.1.4)', 'cce-3987-5', '4525-2', '4657-3', '4661-5', '4807-4', '4701-9', '4669-8', '4436-2', '4815-7', '4696-1', '4216-8', '4758-9', '4621-9', '4515-3', '4282-0', '4802-5', '4806-6', '4471-9', '4617-7', '4418-0', '4810-8', '3955-2', '3834-9', '4408-1', '4536-9', '4809-0', '3841-4'] self.iditerator = 0 def report(self): """report on the status of the system's compliance with disallowing system accounts to log in :return: self.compliant - boolean; True if compliant, False if not """ self.detailedresults = "" self.compliant = True acceptable_nologin_shells = ["/sbin/nologin", "/dev/null", "", "/usr/bin/false"] self.ch = CommandHelper(self.logger) self.corrections_needed = [] try: system_login_shells = self.getsysloginshells() for acc in system_login_shells: if system_login_shells[acc] not in acceptable_nologin_shells: self.compliant = False self.corrections_needed.append(acc) if self.corrections_needed: self.detailedresults += "\nThe following system accounts can log in:\n" + "\n".join(self.corrections_needed) 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 getUIDMIN(self): """return this system's minimum user ID start value, if configured :return: uid_min - string; system's user id starting value """ uid_min = "" logindefs = "/etc/login.defs" try: # get normal user uid start value logindefscontents = readFile(logindefs, self.logger) if logindefscontents: for line in logindefscontents: if re.search("^UID_MIN", line, re.IGNORECASE): sline = line.split() uid_min = sline[1] if not uid_min: self.logger.log(LogPriority.DEBUG, "Unable to determine UID_MIN") except IndexError: pass except IOError: self.logger.log(LogPriority.DEBUG, "Failed to read uid_min from file") return uid_min return uid_min def getsystemaccounts(self): """ return a list of system accounts :return: system_accounts_list - list of system accounts """ system_accounts_list = [] if self.environ.getosfamily() == "darwin": try: system_accounts_list = ["root", "nobody"] get_sys_accounts_cmd = "/usr/bin/dscl . list /Users | grep -i _" self.ch.executeCommand(get_sys_accounts_cmd) system_accounts_list += self.ch.getOutput() except OSError: self.logger.log(LogPriority.DEBUG, "Failed to retrieve list of system accounts") return system_accounts_list else: exclude_accounts = ["halt", "shutdown", "sync", "root"] system_accounts_list = [] uid_min = self.getUIDMIN() if not uid_min: uid_min = "500" f = open("/etc/passwd", "r") contentlines = f.readlines() f.close() try: for line in contentlines: sline = line.split(":") if int(sline[2]) < int(uid_min): if sline[0] not in exclude_accounts: system_accounts_list.append(sline[0]) except IndexError: pass return system_accounts_list def getloginshell(self, account): """ retrieve the login shell, of the passed account, from passwd :param account: string; name of user account to get info for :return: loginshell - string; default login shell path for account """ loginshell = "" try: f = open("/etc/passwd", "r") contentlines = f.readlines() f.close() except IOError: self.logger.log(LogPriority.DEBUG, "Could not read from passwd file") return loginshell try: for line in contentlines: if re.search("^"+account, line, re.IGNORECASE): sline = line.split(":") loginshell = sline[6] except IndexError: pass return loginshell def getsysloginshells(self): """ return a dictionary of system accounts and their login shells :return: system_login_shells - dictionary of system accounts and their login shells """ system_login_shells = {} system_accounts = self.getsystemaccounts() for acc in system_accounts: system_login_shells[acc] = self.getloginshell(acc).strip() return system_login_shells def setdefaultloginshell(self, account, shell): """ set default shell for given user account :param account: string; name of user account to set default shell for :param shell: the type of shell to set for the given user account """ change_shell_cmd = "/usr/bin/chsh -s " + shell + " " + account self.ch.executeCommand(change_shell_cmd) 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. :return: self.rulesuccess - boolean; True if fix succeeds, False if not """ self.detailedresults = "" self.rulesuccess = True path = "/etc/passwd" tmppath = path + ".stonixtmp" self.iditerator = 0 newcontentlines = [] try: if not self.ci.getcurrvalue(): return self.rulesuccess f = open(path, "r") contentlines = f.readlines() f.close() for line in contentlines: sline = line.split(":") if sline[0] in self.corrections_needed: sline[6] = "/sbin/nologin\n" line = ":".join(sline) newcontentlines.append(line) tf = open(tmppath, "w") tf.writelines(newcontentlines) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': path} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(path, tmppath, myid) os.rename(tmppath, path) os.chown(path, 0, 0) os.chmod(path, 420) resetsecon(path) except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults = 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
class SecureHomeDir(Rule): '''classdocs''' 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 = 45 self.rulename = "SecureHomeDir" self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = False datatype = 'bool' key = 'SECUREHOME' instructions = '''To disable this rule set the value of SECUREHOME to False.''' default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ['NSA 2.3.4.2'] self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.sethelptext() def report(self): '''report the compliance status of the permissions on all local user home directories :returns: self.compliant :rtype: bool @author: Derek Walker ''' self.detailedresults = "" self.compliant = True self.cmdhelper = CommandHelper(self.logger) self.WRHomeDirs = [] self.GWHomeDirs = [] try: if self.environ.getostype() == "Mac OS X": self.compliant = self.reportMac() else: self.compliant = self.reportLinux() if not self.WRHomeDirs: self.detailedresults += "\nNo world readable home directories found." if not self.GWHomeDirs: self.detailedresults += "\nNo group writeable home directories found." 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 reportMac(self): '''check all user local home directories, on Mac OS X, for correct permissions :returns: compliant :rtype: bool @author: Derek Walker @change: Breen Malmberg - 10/13/2015 - moved grpvals variable up to top where it should be; fixed logging; will no longer report on /var/empty or /dev/null permissions ''' compliant = True try: if self.environ.geteuid() == 0: # running as root/admin homedirs = self.getMacHomeDirs() if homedirs: self.logger.log(LogPriority.DEBUG, "Scanning home directories...") for hd in homedirs: if not os.path.exists(hd): self.logger.log(LogPriority.DEBUG, "Skipping directory " + hd + " because it does not exist...") continue self.logger.log(LogPriority.DEBUG, "Checking " + hd) if self.isGW(hd): compliant = False self.detailedresults += "\nThe home directory: " + str(hd) + " is group-writeable" self.GWHomeDirs.append(hd) if self.isWR(hd): compliant = False self.detailedresults += "\nThe home directory: " + str(hd) + " is world-readable" self.WRHomeDirs.append(hd) else: self.logger.log(LogPriority.DEBUG, "No home directories found!") else: # running as a normal user homedir = self.getMyHomeDir() if os.path.exists(homedir): if self.isGW(homedir): compliant = False self.detailedresults += "\nThe home directory: " + str(homedir) + " is group-writeable" self.GWHomeDirs.append(homedir) if self.isWR(homedir): compliant = False self.detailedresults += "\nThe home directory: " + str(homedir) + " is world-readable" self.WRHomeDirs.append(homedir) else: self.logger.log(LogPriority.DEBUG, "Skipping directory " + homedir + " because it does not exist...") except Exception: raise return compliant def isGW(self, path): '''determine if a given path is group writeable :param path: string; absolute file path to scan :returns: groupwriteable :rtype: bool @author: Breen Malmberg ''' groupwriteable = False try: mode = os.stat(path).st_mode groupwriteable = bool(mode & stat.S_IWGRP) except Exception: raise return groupwriteable def isWR(self, path): '''determine if a given path is world readable :param path: string; absolute file path to scan :returns: worldreadable :rtype: bool @author: Breen Malmberg ''' worldreadable = False try: mode = os.stat(path).st_mode worldreadable = bool(mode & stat.S_IROTH) except Exception: raise return worldreadable def getMacHomeDirs(self): '''get a list of user home directories on the Mac :returns: homedirs :rtype: list @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "Building list of Mac local user home directories...") HomeDirs = [] getAccountsList = ["/usr/bin/dscl", ".", "list", "/Users"] UsersList = [] try: self.cmdhelper.executeCommand(getAccountsList) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) return HomeDirs AccountsList = self.cmdhelper.getOutput() # filter out any system accounts # (we want only user accounts) if AccountsList: for acc in AccountsList: if not re.search("^_", acc, re.IGNORECASE) and not re.search("^root", acc, re.IGNORECASE): UsersList.append(acc) if UsersList: for u in UsersList: try: currpwd = pwd.getpwnam(u) except KeyError: UsersList.remove(u) continue HomeDirs.append(currpwd[5]) if not HomeDirs: self.logger.log(LogPriority.DEBUG, "No home directories found") else: HomeDirs = self.validateHomedirs(HomeDirs) except Exception: raise return HomeDirs def getLinuxHomeDirs(self): '''get a list of user home directories on Linux platforms :returns: homedirs :rtype: list @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "Building list of Linux user home directories...") HomeDirs = [] awk = "/usr/bin/awk" passwd = "/etc/passwd" invalidshells = ["/sbin/nologin", "/sbin/halt", "/sbin/shutdown", "/dev/null", "/bin/sync"] getacctscmd = awk + " -F: '{ print $1 }' " + passwd acctnames = [] usernames = [] try: # establish the minimum user id on this system uid_min = self.getUIDMIN() if not uid_min: uid_min = "500" if os.path.exists(awk): # build a list of user (non-system) account names self.cmdhelper.executeCommand(getacctscmd) acctnames = self.cmdhelper.getOutput() else: self.logger.log(LogPriority.DEBUG, "awk utility not installed! What kind of Linux are you running??") # alternate method of getting account names in case system # does not have the awk utility installed.. f = open(passwd, 'r') contents = f.readlines() f.close() for line in contents: sline = line.split(':') if len(sline) > 1: acctnames.append(sline[0]) if acctnames: for an in acctnames: if int(pwd.getpwnam(an).pw_uid) >= int(uid_min): usernames.append(an) else: self.logger.log(LogPriority.DEBUG, "Could not find any accounts on this system!") return HomeDirs # further check to see if this might still be a system account # which just got added in the user id range somehow (by checking # the shell) for un in usernames: if pwd.getpwnam(un).pw_shell in invalidshells: usernames.remove(un) for un in usernames: HomeDirs.append(pwd.getpwnam(un).pw_dir) # now we should be reasonably certain that the list we have are all # valid users (and not system accounts) but let's do one more check # to make sure they weren't assigned a home directory some where that # we don't want to modify (ex. etc or /root) HomeDirs = self.validateHomedirs(HomeDirs) if not HomeDirs: self.logger.log(LogPriority.DEBUG, "No home directories found") else: HomeDirs = self.validateHomedirs(HomeDirs) except Exception: raise return HomeDirs def validateHomedirs(self, dirs): '''strip out common system (and non-existent) directories from the given list of dirs and return the resultant list :param dirs: list; list of strings containing directories to search and modify :returns: validateddirs :rtype: list @author: Breen Malmberg ''' validateddirs = [] systemdirs = ['/tmp', '/var', '/temp', '/', '/bin', '/sbin', '/etc', '/dev', '/root'] self.logger.log(LogPriority.DEBUG, "Validating list of user home directories...") # if the base directory of a given path matches any of the above system directories, then we discard it for d in dirs: if os.path.exists(d): basepath = self.getBasePath(d) if basepath not in systemdirs: validateddirs.append(d) else: self.logger.log(LogPriority.DEBUG, "An account with a uid in the non-system range had a strange home directory: " + d) self.logger.log(LogPriority.DEBUG, "Excluding this home directory from the list...") else: self.logger.log(LogPriority.DEBUG, "Home directory: " + d + " does not exist. Excluding it...") return validateddirs def getBasePath(self, path): '''get only the first (base) part of a given path :param path: string; full path to get base of :returns: basepath :rtype: string @author: Breen Malmberg ''' basepath = "/" # break path into list of characters pathchars = list(path) # remove the first '/' if it is there, to make # the list iteration easier if pathchars[0] == "/": del pathchars[0] # iterate over list of characters, adding all characters # before the next '/' path divider, to the basepath for c in pathchars: if c == "/": break else: basepath += c return basepath def getUIDMIN(self): '''return this system's minimum user ID start value, if configured :returns: uid_min :rtype: string @author: Breen Malmberg ''' uid_min = "" logindefs = "/etc/login.defs" try: # get normal user uid start value logindefscontents = readFile(logindefs, self.logger) if logindefscontents: for line in logindefscontents: if re.search("^UID_MIN", line, re.IGNORECASE): sline = line.split() uid_min = sline[1] if not uid_min: self.logger.log(LogPriority.DEBUG, "Unable to determine UID_MIN") except Exception: raise return uid_min def reportLinux(self): '''check all user local home directories, on Linux platforms, for correct permissions :returns: compliant :rtype: bool @author: Derek Walker @change: Breen Malmberg - 06/28/2018 - re-wrote method ''' compliant = True passwd = "/etc/passwd" try: if not os.path.exists(passwd): self.logger.log(LogPriority.DEBUG, "You have no passwd file! Cannot get lits of user home directories! Aborting.") compliant = False return compliant if self.environ.geteuid() == 0: # running as root/admin homedirs = self.getLinuxHomeDirs() if homedirs: self.logger.log(LogPriority.DEBUG, "Scanning home directory permissions...") for hd in homedirs: if not os.path.exists(hd): self.logger.log(LogPriority.DEBUG, "Skipping directory " + hd + " because it does not exist...") continue self.logger.log(LogPriority.DEBUG, "Checking " + hd) if self.isGW(hd): compliant = False self.detailedresults += "\nThe home directory: " + str(hd) + " is group-writeable" self.GWHomeDirs.append(hd) if self.isWR(hd): compliant = False self.detailedresults += "\nThe home directory: " + str(hd) + " is world-readable" self.WRHomeDirs.append(hd) else: self.logger.log(LogPriority.DEBUG, "No home directories found!") else: # running as a normal user homedir = self.getMyHomeDir() if os.path.exists(homedir): self.logger.log(LogPriority.DEBUG, "Checking " + homedir) if self.isGW(homedir): compliant = False self.detailedresults += "\nThe home directory: " + str(homedir) + " is group-writeable" self.GWHomeDirs.append(homedir) if self.isWR(homedir): compliant = False self.detailedresults += "\nThe home directory: " + str(homedir) + " is world-readable" self.WRHomeDirs.append(homedir) else: self.logger.log(LogPriority.DEBUG, "Skipping directory " + homedir + " because it does not exist...") except Exception: raise return compliant def getMyHomeDir(self): '''return the home directory for the currently logged-in user :returns: HomeDir :rtype: string @author: Breen Malmberg ''' HomeDir = "" findHomeDir = "echo $HOME" uuid = self.environ.geteuid() try: # precautionary check if uuid <= 100: self.logger.log(LogPriority.DEBUG, "This method should only be run by non-system, user accounts!") return HomeDir self.cmdhelper.executeCommand(findHomeDir) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) return HomeDir HomeDir = self.cmdhelper.getOutputString() # backup method (if the $HOME env is not set for the current user) if not HomeDir: HomeDir = pwd.getpwuid(uuid).pw_dir except Exception: raise return HomeDir def fix(self): '''remove group-write and other-read permissions on all local user home directories :returns: self.rulesuccess :rtype: bool @author: Derek Walker @change: Breen Malmberg - 10/13/2015 - will now fix /dev/null permissions when run; will no longer modify /var/empty or /dev/null ''' self.iditerator = 0 self.rulesuccess = True self.detailedresults = "" try: if not self.ci.getcurrvalue(): self.detailedresults += "\nRule was not enabled. Nothing was done." return self.rulesuccess if self.environ.geteuid() == 0: eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.GWHomeDirs: for hd in self.GWHomeDirs: self.logger.log(LogPriority.DEBUG, "Removing group-write permission on directory: " + hd) self.cmdhelper.executeCommand("/bin/chmod g-w " + hd) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) self.rulesuccess = False self.detailedresults += "\nUnable to remove group write permission on directory: " + hd if self.WRHomeDirs: for hd in self.WRHomeDirs: self.logger.log(LogPriority.DEBUG, "Removing world read permission on directory: " + hd) self.cmdhelper.executeCommand("/bin/chmod o-r " + hd) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) self.rulesuccess = False self.detailedresults += "\nUnable to remove world read permission on directory: " + hd self.logger.log(LogPriority.DEBUG, "Also ensuring no world write permission on directory: " + hd) self.cmdhelper.executeCommand("/bin/chmod o-w " + hd) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) 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
class NoCachedFDEKeys(Rule): def __init__(self, config, environ, logdispatch, statechglogger): '''Constructor''' Rule.__init__(self, config, environ, logdispatch, statechglogger) self.logger = logdispatch self.rulenumber = 271 self.rulename = "NoCachedFDEKeys" self.formatDetailedResults("initialize") self.sethelptext() self.rootrequired = True datatype = "bool" key = "NOCACHEDFDEKEYS" instructions = "To disable this rule set the value of " + \ "NOCACHEDFDEKEYS to False" default = True self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.ci = self.initCi(datatype, key, instructions, default) def report(self): ''' :return: ''' try: self.detailedresults = "" compliant = True self.ch = CommandHelper(self.logger) cmd = "/usr/bin/pmset -g" if self.ch.executeCommand(cmd): output = self.ch.getOutput() error = self.ch.getError() if output: for line in output: if re.search("DestroyFVKeyOnStandby", line): line = line.strip() temp = line.split() if temp[1] != "1": self.detailedresults += "Incorrect value " + \ "for DestroyFVKeyOnStandy key\n" debug = "Incorrect value for " + \ "DestroyFVKeyOnStandby key\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False elif error: debug = "Error in running pmset command\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False self.compliant = compliant except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + 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): try: self.detailedresults = "" success = True if self.ci.getcurrvalue(): self.iditerator = 0 eventlist = self.statechglogger.findrulechanges( self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) success = True if not self.compliant: cmd = "/usr/bin/pmset -a destroyfvkeyonstandby 1" if self.ch.executeCommand(cmd): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) undocmd = "/usr/bin/pmset -a destroyfvkeyonstandby 0" event = { "eventtype": "commandstring", "command": undocmd } self.statechglogger.recordchgevent(myid, event) else: success = False self.rulesuccess = success except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + 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 ScheduleStonix(Rule): """Schedule Stonix to run randomly throughout the week and once in a user context @author: Breen Malmberg @change: 09/25/2017 - Breen Malmberg - Eliminated redundant code; separated out some code into its own methods; Improved method comment blocks; Improved in-line code comments; Added logic to ensure that no two STONIX jobs can end up being scheduled to run at the same time @change: 04/17/2018 - Breen Malmberg - Fixed the key being specified in the mac os dict for the jobs from "Day" to "Weekday" @change: 05/02/2018 - Breen Malmberg - Fixed a missed logic path for setting a flag to False indicating that the config file(s) needed to be created on mac os (they were never getting created the paths didn't exist to begin with; this appears to have been a recent regression); added debug logging to the fixmac() method; removed an unused import "FindUserLoggedIn" """ def __init__(self, config, environ, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 268 self.rulename = 'ScheduleStonix' self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = True self.sethelptext() self.rulesuccess = True self.guidance = [''] self.applicable = { 'type': 'white', 'family': ['darwin', 'linux', 'solaris', 'freebsd'] } datatype = "int" keyfd = "FIXDAY" keyfh = "FIXHOUR" keyfm = "FIXMINUTE" keyrd = "REPORTDAY" keyrh = "REPORTHOUR" keyrm = "REPORTMINUTE" keyufh = "USERCONTEXTFIXHOUR" keyufm = "USERCONTEXTFIXMINUTE" instruct_fd = "Enter the day of the week you would like the fix job to run (1-7). 1 = Monday. 7 = Sunday. This value cannot be the same as REPORTDAY value." instruct_fh = "Enter the hour of the day you would like the fix job to run (0-23). 0 = Midnight. This value cannot be the same as USERCONTEXTFIXHOUR value." instruct_fm = "Enter the minute of the hour you would like the fix job to run (0-59)." instruct_rd = "Enter the day of the week you would like the report job to run (1-7) 1 = Monday. 7 = Sunday. This value cannot be the same as FIXDAY value." instruct_rh = "Enter the hour of the day you would like the report job to run (0-23) 0 = Midnight. This value cannot be the same as USERCONTEXTFIXHOUR value." instruct_rm = "Enter the minute of the hour you would like the report job to run (0-59)." instruct_ufh = "Enter the hour of the day you would like the user-context fix job to run (0-23). This value cannot be the same as FIXHOUR value or REPORTHOUR value." instruct_ufm = "Enter the minute of the hour you would like the user-context fix job to run (0-59)." default = 0 datatype2 = "bool" key2 = "CONFIGUREJOBTIMESMANUALLY" instruct2 = "Set the value of CONFIGUREJOBTIMESMANUALLY to True, in order to manually specify when each STONIX job should run (as opposed to using randomly generated times)." default2 = False self.manualjobtimesCI = self.initCi(datatype2, key2, instruct2, default2) datatype3 = "bool" key3 = "SCHEDULEFIXJOBS" instruct3 = "To prevent STONIX from being schedule to run in fix mode, set the value of SCHEDULEFIXJOBS to False" default3 = True self.schedulefixjobsCI = self.initCi(datatype3, key3, instruct3, default3) self.fixdayCI = self.initCi(datatype, keyfd, instruct_fd, default) self.fixhourCI = self.initCi(datatype, keyfh, instruct_fh, default) self.fixminuteCI = self.initCi(datatype, keyfm, instruct_fm, default) self.reportdayCI = self.initCi(datatype, keyrd, instruct_rd, default) self.reporthourCI = self.initCi(datatype, keyrh, instruct_rh, default) self.reportminuteCI = self.initCi(datatype, keyrm, instruct_rm, default) self.userfixhourCI = self.initCi(datatype, keyufh, instruct_ufh, default) self.userfixminuteCI = self.initCi(datatype, keyufm, instruct_ufm, default) self.initVars() if self.firstRun(): self.initTimes() def initTimes(self): """ """ self.genJobTimes() self.syncCITimes() if self.checkJobCollisions(): return self.initTimes() def syncCITimes(self): """ """ self.fixdayCI.updatecurrvalue(self.adminfixday) self.fixhourCI.updatecurrvalue(self.adminfixhour) self.fixminuteCI.updatecurrvalue(self.adminfixminute) self.reportdayCI.updatecurrvalue(self.adminreportday) self.reporthourCI.updatecurrvalue(self.adminreporthour) self.reportminuteCI.updatecurrvalue(self.adminreportminute) self.userfixhourCI.updatecurrvalue(self.userfixhour) self.userfixminuteCI.updatecurrvalue(self.userfixminute) def firstRun(self): """ """ firstrun = True total = 0 total = self.fixdayCI.getcurrvalue() + \ self.fixhourCI.getcurrvalue() + \ self.fixminuteCI.getcurrvalue() + \ self.reportdayCI.getcurrvalue() + \ self.reporthourCI.getcurrvalue() + \ self.reportminuteCI.getcurrvalue() + \ self.userfixhourCI.getcurrvalue() + \ self.userfixminuteCI.getcurrvalue() if total > 0: firstrun = False return firstrun def buildFiles(self): """dynamically build the conf and script files based on the generated or entered times @author: Breen Malmberg """ # define the Mac OS X weekly STONIX report launchd job self.stonixplistreport = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>gov.lanl.stonix.report</string> <key>ProgramArguments</key> <array> <string>/Applications/stonix4mac.app/Contents/Resources/stonix.app/Contents/MacOS/stonix</string> <string>-c</string> <string>-r</string> </array> <key>StartCalendarInterval</key> <array> <dict> <key>Weekday</key> <integer>""" + str(self.adminreportday) + """</integer> <key>Hour</key> <integer>""" + str(self.adminreporthour) + """</integer> <key>Minute</key> <integer>""" + str(self.adminreportminute) + """</integer> </dict> </array> </dict> </plist>""" # define the Mac OS X weekly STONIX fix launchd job self.stonixplistfix = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>gov.lanl.stonix.fix</string> <key>ProgramArguments</key> <array> <string>/Applications/stonix4mac.app/Contents/Resources/stonix.app/Contents/MacOS/stonix</string> <string>-c</string> <string>-f</string> </array> <key>StartCalendarInterval</key> <array> <dict> <key>Weekday</key> <integer>""" + str(self.adminfixday) + """</integer> <key>Hour</key> <integer>""" + str(self.adminfixhour) + """</integer> <key>Minute</key> <integer>""" + str(self.adminfixminute) + """</integer> </dict> </array> </dict> </plist>""" # define the once-daily STONIX user-context launch agent job self.stonixplistuser = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>gov.lanl.stonix.user</string> <key>ProgramArguments</key> <array> <string>/Applications/stonix4mac.app/Contents/Resources/stonix.app/Contents/MacOS/stonix</string> <string>-c</string> <string>-""" + self.usermode + """</string> </array> <key>StartCalendarInterval</key> <array> <dict> <key>Hour</key> <integer>""" + str(self.userfixhour) + """</integer> <key>Minute</key> <integer>""" + str(self.userfixminute) + """</integer> </dict> </array> </dict> </plist>""" self.userstonixscript = """#! /usr/bin/env python #Created on Jan 13, 2014 # #This script is used by ScheduleStonix.py #This script will run stonix.py, in user context mode, once daily # #@author: Breen Malmberg import os,time,getpass,pwd,re #defaults username = getpass.getuser() userhome = '' scriptsuccess = True #get current user's home directory for p in pwd.getpwall(): if p[0] in username: if re.search('^/home/',p[5]) or re.search('^/Users/',p[5]): userhome = p[5] todaysdate = time.strftime("%d%m%Y") stonixscriptpath = '/usr/sbin/stonix.py' stonixtempfolder = userhome + '/.stonix/' alreadyran = False #if the script has not already run today if os.path.exists(stonixtempfolder + 'userstonix.log'): f = open(stonixtempfolder + 'userstonix.log','r') contentlines = f.readlines() f.close() for line in contentlines: line = line.split() #script log file entries should follow the format: usernameDDMMYYYY if re.search('^' + username,line[0]) and re.search(todaysdate,line[1]): alreadyran = True #if the script has not yet run today, then run it if not alreadyran: try: #run stonix -f in user context os.system(stonixscriptpath + ' -c""" + self.usermode + """') except IOError: exitcode = IOError.errno print IOError.message scriptsuccess = False except OSError: exitcode = OSError.errno print OSError.message scriptsuccess = False if scriptsuccess: i = 0 for line in contentlines: if re.search('^' + username,line) and not re.search(todaysdate,line): line = username + ' ' + todaysdate contentlines[i] = line i += 1 #create/update log entry f = open(stonixtempfolder + 'userstonix.log','w') f.writelines(contentlines) f.close() else: print "user-stonix.py script failed to run properly" exit(exitcode)""" def initVars(self): """initialize all of the time variables to be used by the class @author: Breen Malmberg """ self.svchelper = ServiceHelper(self.environ, self.logger) # init cronfilelocation to blank self.cronfilelocation = "/etc/crontab" self.userscriptfile = "/etc/profile.d/user-stonix.py" self.crontimedict = {} # get the STONIX executable path self.stonixpath = self.environ.get_script_path() self.userfixhour = 0 self.userfixminute = 0 self.adminfixday = 0 self.adminfixhour = 0 self.adminfixminute = 0 self.adminreportday = 0 self.adminreporthour = 0 self.adminreportminute = 0 def setUserTimes(self): """ """ self.adminfixday = self.fixdayCI.getcurrvalue() self.adminfixhour = self.fixhourCI.getcurrvalue() self.adminfixminute = self.fixminuteCI.getcurrvalue() self.adminreportday = self.reportdayCI.getcurrvalue() self.adminreporthour = self.reporthourCI.getcurrvalue() self.adminreportminute = self.reportminuteCI.getcurrvalue() self.userfixhour = self.userfixhourCI.getcurrvalue() self.userfixminute = self.userfixminuteCI.getcurrvalue() self.crontimedict['report'] = str(self.adminreportminute) + ' ' + str( self.adminreporthour) + ' \* \* ' + str(self.adminreportday) self.crontimedict['fix'] = str(self.adminfixminute) + ' ' + str( self.adminfixhour) + ' \* \* ' + str(self.adminfixday) def checkJobCollisions(self): """check to make sure none of the generated job times overlap, or are invalid return True if invalid or overlap return False if valid and not overlap :returns: collisions :rtype: list @author: Breen Malmberg """ self.logger.log(LogPriority.DEBUG, "Checking for job time collisions...") collisions = [] # job collision validation if self.fixdayCI.getcurrvalue() == self.reportdayCI.getcurrvalue(): collisions.append("afd") if self.userfixhourCI.getcurrvalue() == self.fixhourCI.getcurrvalue(): collisions.append("ufh") if self.userfixhourCI.getcurrvalue() == self.reporthourCI.getcurrvalue( ): collisions.append("arh") if collisions: self.logger.log(LogPriority.DEBUG, "Job time collision detected.") return collisions def validateUserTimes(self): """check the entered user-defined cron job times to see if they are valid :returns: valid :rtype: bool @author: Breen Malmberg """ valid = True formatvalid = True rangevalid = True collisions = [] fixday = self.fixdayCI.getcurrvalue() fixhour = self.fixhourCI.getcurrvalue() fixminute = self.fixminuteCI.getcurrvalue() reportday = self.reportdayCI.getcurrvalue() reporthour = self.reporthourCI.getcurrvalue() reportminute = self.reportminuteCI.getcurrvalue() userfixhour = self.userfixhourCI.getcurrvalue() userfixminute = self.userfixminuteCI.getcurrvalue() self.logger.log(LogPriority.DEBUG, "Validating user-entered job times...") self.logger.log(LogPriority.DEBUG, "Checking job times format (integer length).") # length (digits) input validation il = [] if len(str(fixday).lstrip("0")) > 1: formatvalid = False il.append("fixday should be 1 integer in length") if len(str(reportday).lstrip("0")) > 1: formatvalid = False il.append("reportday should be 1 integer in length") if len(str(fixhour).lstrip("0")) > 2: formatvalid = False il.append("fixhour should be no more than 2 integers in length") if len(str(fixminute).lstrip("0")) > 2: formatvalid = False il.append("fixminute should be no more than 2 integers in length") if len(str(reporthour).lstrip("0")) > 2: formatvalid = False il.append("reporthour should be no more than 2 integers in length") if len(str(reportminute).lstrip("0")) > 2: formatvalid = False il.append( "reportminute should be no more than 2 integers in length") if len(str(userfixhour).lstrip("0")) > 2: formatvalid = False il.append( "userfixhour should be no more than 2 integers in length") if len(str(userfixminute).lstrip("0")) > 2: formatvalid = False il.append( "userfixminute should be no more than 2 integers in length") if not formatvalid: self.detailedresults += "\nIncorrect job time format detected. Incorrect integer length:\n" + "\n".join( il) # range input validation oor = [] if fixday not in list(range(1, 8)): rangevalid = False oor.append("fixday: " + str(fixday)) if reportday not in list(range(1, 8)): rangevalid = False oor.append("reportday: " + str(reportday)) if fixhour not in list(range(0, 24)): rangevalid = False oor.append("fixhour: " + str(fixhour)) if reporthour not in list(range(0, 24)): rangevalid = False oor.append("reporthour: " + str(reporthour)) if fixminute not in list(range(0, 60)): rangevalid = False oor.append("fixminute: " + str(fixminute)) if reportminute not in list(range(0, 60)): rangevalid = False oor.append("reportminute: " + str(reportminute)) if userfixhour not in list(range(0, 24)): rangevalid = False oor.append("userfixhour: " + str(userfixhour)) if userfixminute not in list(range(0, 60)): oor.append("userfixminute: " + str(userfixminute)) if not rangevalid: self.detailedresults += "\nIncorrect job time format detected. Job time(s) outside valid range:\n" + "\n".join( oor) # fix any job time collisions collisions = self.checkJobCollisions() if collisions: if "afd" in collisions: self.detailedresults += "\nThere is a time collision between admin fix day and admin report day" if "ufh" in collisions: self.detailedresults += "\nThere is a time collision between user fix hour and admin fix hour" if "urh" in collisions: self.detailedresults += "\nThere is a time collision between user fix hour and admin report hour" if not rangevalid: valid = False if not formatvalid: valid = False if collisions: valid = False if valid: self.detailedresults += "\nAll user-entered job times are valid. Proceeding..." else: self.detailedresults += "\nUser-entered job time(s) not valid. Please correct the issue(s) and run fix again, to set your custom job time(s)." return valid def randomExcept(self, lowest, highest, exclude=None): """generate a random number between lowest and highest (including lowest) but excluding the given number exclude (result will never be = highest) :param lowest: int; lower bound :param highest: int; upper bound :param exclude: int|list; integer or range of integers to exclude from result default value for exclude is None :returns: x :rtype: int @author: Breen Malmberg """ x = 0 if lowest == highest: self.logger.log( LogPriority.WARNING, "lowest argument must be less than highest argument") if lowest != exclude: x = lowest return x if lowest > highest: self.logger.log( LogPriority.WARNING, "lowest argument must be less than highest argument") return x if type(exclude).__name__ == "str": exclude = int(exclude) elif type(exclude).__name__ == "NoneType": pass else: self.logger.log( LogPriority.WARNING, "Incorrect type for argument: exclude. Needs to be either list or int. Got: " + type(exclude).__name__) return x x = random.randrange(lowest, highest) if exclude == None: return x if type(exclude).__name__ == "list": if x in exclude: x = self.randomExcept(lowest, highest, exclude) elif type(exclude).__name__ == "int": if x == exclude: x = self.randomExcept(lowest, highest, exclude) return x def genJobTimes(self, *args): """Generate random times to run the STONIX jobs Build the crontimedict used by Linux @author: Breen Malmberg :param *args: """ genall = False self.logger.log(LogPriority.DEBUG, "Generating new, random job times...") if "all" in args: genall = True elif not args: genall = True else: if "afd" in args: self.adminfixday = random.randrange(1, 8) if "afh" in args: self.adminfixhour = random.randrange(17, 24) if "afm" in args: self.adminfixminute = random.randrange(0, 60) if "ard" in args: self.adminreportday = random.randrange(1, 8) if "arh" in args: self.adminreporthour = random.randrange(17, 24) if "arm" in args: self.adminreportminute = random.randrange(0, 60) if "ufh" in args: self.userfixhour = random.randrange(17, 24) if "ufm" in args: self.userfixminute = random.randrange(0, 60) if genall: self.adminfixday = random.randrange(1, 8) self.adminfixhour = random.randrange(17, 24) self.adminfixminute = random.randrange(0, 60) self.adminreportday = random.randrange(1, 8) self.adminreporthour = random.randrange(17, 24) self.adminreportminute = random.randrange(0, 60) self.userfixhour = random.randrange(17, 24) self.userfixminute = random.randrange(0, 60) self.crontimedict['report'] = str(self.adminreportminute) + ' ' + str( self.adminreporthour) + ' * * ' + str(self.adminreportday) self.crontimedict['fix'] = str(self.adminfixminute) + ' ' + str( self.adminfixhour) + ' * * ' + str(self.adminfixday) def getFileContents(self, filepath): """Read file contents from given filepath, into a list Return the list (of strings) Return empty list if given filepath does not exist :param filepath: :returns: contents :rtype: list @author: Breen Malmberg """ contents = [] if os.path.exists(filepath): f = open(filepath, 'r') contents = f.readlines() f.close() return contents def report(self): """Linux: Check that Cron file(s) exist and that they contain the correct job entries Check that the STONIX user script exists and contains the correct contents Mac: Check that the launchd files exist Check that each launchd file contains the correct contents :returns: self.compliant :rtype: bool @author: Breen Malmberg """ self.compliant = True self.detailedresults = "" self.ch = CommandHelper(self.logger) if self.schedulefixjobsCI.getcurrvalue(): self.usermode = 'f' else: self.usermode = 'r' if self.manualjobtimesCI.getcurrvalue(): self.logger.log(LogPriority.DEBUG, "Using user-configured job times...") if self.validateUserTimes(): self.setUserTimes() else: self.compliant = False self.detailedresults += "\nThe user-specified cron job time was invalid." self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant collisions = self.checkJobCollisions() for item in collisions: self.genJobTimes(item) if collisions: self.detailedresults += "\nOne of the times entered was invalid because it would cause more than one of the jobs to run at the same time. The offending time entry has been changed to a new, randomly generated one." self.syncCITimes() else: self.logger.log(LogPriority.DEBUG, "Using randomly generated job times...") if self.checkJobCollisions(): self.genJobTimes() self.syncCITimes() self.logger.log( LogPriority.DEBUG, "Building configuration files text, using job times...") self.buildFiles() self.logger.log(LogPriority.DEBUG, "STONIX path set to: " + str(self.stonixpath)) try: # if Mac OS, check Mac launch daemon and agent jobs if self.environ.getostype() == "Mac OS X": if not self.reportMac(): self.compliant = False else: # else check Linux cron jobs if not self.reportLinux(): 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.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def reportLinux(self): """Check for STONIX Cron job entries in the Linux crontab :returns: retval :rtype: bool @author: Breen Malmberg """ retval = True self.cronfileexists = True self.reportjob = False self.fixjob = False self.userjob = True stonixreportjob = ' root nice -n 19 ' + str( self.stonixpath) + '/stonix.py -cr' stonixfixjob = ' root nice -n 19 ' + str( self.stonixpath) + '/stonix.py -cdf' # check for existence of system crontab file if not os.path.exists(self.cronfilelocation): self.cronfileexists = False self.detailedresults += '\nSystem crontab file does not exist' else: # check for STONIX cron job entries contents = self.getFileContents(self.cronfilelocation) if not contents: self.detailedresults += '\nSystem crontab file was empty' else: # report entry for line in contents: self.logger.log( LogPriority.DEBUG, "Comparing line:\n" + str(line) + "to stonixreportjob:\n" + str(stonixreportjob)) if re.search(stonixreportjob, line, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "REPORT JOB FOUND") self.reportjob = True else: self.logger.log(LogPriority.DEBUG, "Lines do NOT match") if not self.reportjob: self.detailedresults += '\nSTONIX report job not found\n' else: self.detailedresults += '\nSTONIX report job found\n' self.logger.log( LogPriority.DEBUG, "After report entry check: reportjob is " + str(self.reportjob)) # fix entry for line in contents: self.logger.log( LogPriority.DEBUG, "Comparing line:\n" + str(line) + "to stonixfixjob:\n" + str(stonixfixjob)) if re.search(stonixfixjob, line, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "FIX JOB FOUND") self.fixjob = True else: self.logger.log(LogPriority.DEBUG, "Lines do NOT match") if not self.fixjob: self.detailedresults += '\nSTONIX fix job not found\n' else: self.detailedresults += '\nSTONIX fix job found\n' self.logger.log( LogPriority.DEBUG, "After fix entry check: fixjob is " + str(self.fixjob)) self.logger.log( LogPriority.DEBUG, "Before permissions check: retval is " + str(retval)) # check perms of system crontab crontabperms = getOctalPerms(self.cronfilelocation) if crontabperms != 600: self.logger.log( LogPriority.DEBUG, "crontab perms should be 600. Got: " + str(crontabperms)) self.detailedresults += "\nIncorrect permissions detected on system crontab file" retval = False self.logger.log(LogPriority.DEBUG, "Before user script checks: retval is " + str(retval)) # check user script if not os.path.exists(self.userscriptfile): self.userjob = False self.detailedresults += '\nSTONIX user script not found' else: self.detailedresults += '\nSTONIX user script found' contents = self.getFileContents(self.userscriptfile) userjobline = "os.system(stonixscriptpath + ' -c" + self.usermode + "')" stripped_contents = list(map(str.strip, contents)) if userjobline not in stripped_contents: self.userjob = False self.detailedresults += '\nSTONIX user script has incorrect contents' else: self.detailedresults += '\nSTONIX user script has correct contents' # check perms on user script file userscriptperms = getOctalPerms(self.userscriptfile) if userscriptperms != 755: self.detailedresults += "\nIncorrect permissions detected on user script file: " + str( self.userscriptfile) self.logger.log(LogPriority.DEBUG, "Before Final Checks: retval is " + str(retval)) if not self.cronfileexists: retval = False if not self.reportjob: retval = False if not self.fixjob: retval = False if not self.userjob: retval = False return retval def reportMac(self): """Check for the existence of the necessary STONIX launchd jobs Check that each STONIX launchd job contains the correct contents :returns: retval :rtype: bool @author Breen Malmberg """ # defaults retval = True pathsneeded = [ '/Library/LaunchDaemons/gov.lanl.stonix.report.plist', '/Library/LaunchDaemons/gov.lanl.stonix.fix.plist', '/Library/LaunchAgents/gov.lanl.stonix.user.plist' ] plistdict = { '/Library/LaunchDaemons/gov.lanl.stonix.report.plist': self.stonixplistreport, '/Library/LaunchDaemons/gov.lanl.stonix.fix.plist': self.stonixplistfix, '/Library/LaunchAgents/gov.lanl.stonix.user.plist': self.stonixplistuser } systemservicetargets = [ "gov.lanl.stonix.fix", "gov.lanl.stonix.report" ] self.macadminfixjob = True self.macadminreportjob = True self.macuserjob = True try: # if a required path is missing, return False for path in pathsneeded: if not os.path.exists(path): retval = False self.detailedresults += "Path doesn't exist: " + path + "\n" if path == '/Library/LaunchDaemons/gov.lanl.stonix.report.plist': self.macadminreportjob = False elif path == '/Library/LaunchDaemons/gov.lanl.stonix.fix.plist': self.macadminfixjob = False elif path == '/Library/LaunchAgents/gov.lanl.stonix.user.plist': self.macuserjob = False # if path exists, check for required plist content else: contents = readFile(path, self.logger) # get contents of appropriate plist script in a list scrptcontents = plistdict[path].splitlines(True) # compare contents of plist script i = 0 try: for i in range(len(contents)): if not re.search('<integer>', contents[i]): if contents[i].strip( ) != scrptcontents[i].strip(): retval = False self.detailedresults += "line not correct: " + str( contents[i]) + " in file: " + str( path) + "\n" if re.search('report', path, re.IGNORECASE): self.macadminreportjob = False if re.search('fix', path, re.IGNORECASE): self.macadminfixjob = False if re.search('user', path, re.IGNORECASE): self.macuserjob = False else: if not re.search( '<integer>[0-9][0-9]{0,1}<\/integer>', contents[i]): retval = False self.detailedresults += "integer line wrong: " + str( contents[i]) + " in file: " + str( path) + "\n" if re.search('report', path, re.IGNORECASE): self.macadminreportjob = False if re.search('fix', path, re.IGNORECASE): self.macadminfixjob = False if re.search('user', path, re.IGNORECASE): self.macuserjob = False except IndexError: pass # This part needs to be changed to work with re-designed servicehelperTwo self.ch.executeCommand("/bin/launchctl print-disabled system/") disabledserviceslist = self.ch.getOutput() retcode = self.ch.getReturnCode() if retcode != 0: errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) for line in disabledserviceslist: for item in systemservicetargets: if re.search(item + '\"\s+\=\>\s+true', line, re.IGNORECASE): retval = False self.detailedresults += "\nA required STONIX service: gov.lanl.stonix.user.plist is currently disabled" except Exception: raise return retval def fix(self): """Mac OS X: Create or edit STONIX launchd jobs as necessary Linux: Create or edit STONIX Cron job entries as necessary :return: self.rulesuccess :rtype: bool """ self.detailedresults = "" self.rulesuccess = True try: if self.manualjobtimesCI.getcurrvalue(): if not self.validateUserTimes(): self.rulesuccess = False self.detailedresults += "\nThe user-specified cron job time was incorrectly specified." self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess # If Mac OS, do fixes for Mac if self.environ.getostype() == "Mac OS X": if not self.fixMac(): self.rulesuccess = False # else do fixes for Linux else: if not self.fixLinux(): self.rulesuccess = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.rulesuccess = False self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixLinux(self): """Create or edit STONIX Cron jobs for Linux, as necessary :return: retval :rtype: bool """ retval = True # create the report and fix Cron entry strings reportstring = '\n' + str(self.reportminuteCI.getcurrvalue( )) + ' ' + str(self.reporthourCI.getcurrvalue()) + ' * * ' + str( self.reportdayCI.getcurrvalue()) + ' root nice -n 19 ' + str( self.stonixpath) + '/stonix.py' + ' -cr' fixstring = '\n' + str(self.fixminuteCI.getcurrvalue()) + ' ' + str( self.fixhourCI.getcurrvalue()) + ' * * ' + str( self.fixdayCI.getcurrvalue()) + ' root nice -n 19 ' + str( self.stonixpath ) + '/stonix.py' + ' -cdf &> /var/log/stonix-lastfix.log\n' # create Cron file if it doesn't exist if not self.cronfileexists: contents = [] f = open(self.cronfilelocation, 'w') contents.append('## This file created by STONIX\n\n') contents.append(reportstring) if self.schedulefixjobsCI.getcurrvalue(): contents.append(fixstring) f.writelines(contents) f.close() else: contents = self.getFileContents(self.cronfilelocation) # add report line if it doesn't exist if not self.reportjob: # remove any existing erroneous job times for line in contents: if re.search("stonix.*-cr", line, re.IGNORECASE): contents = [c.replace(line, '') for c in contents] contents.append(reportstring) if self.schedulefixjobsCI.getcurrvalue(): # add fix line if it doesn't exist if not self.fixjob: # remove any existing erroneous job times for line in contents: if re.search("stonix.*-cdf", line, re.IGNORECASE): contents = [c.replace(line, '') for c in contents] contents.append(fixstring) f = open(self.cronfilelocation, 'w') f.writelines(contents) f.close() os.chown(self.cronfilelocation, 0, 0) os.chmod(self.cronfilelocation, 0o600) if not self.userjob: self.logger.log(LogPriority.DEBUG, "Fixing the STONIX user script...") # create the user script if not os.path.exists(self.userscriptfile): try: if not os.path.exists('/etc/profile.d'): os.makedirs('/etc/profile.d', 0o755) os.chown('/etc/profile.d', 0, 0) self.logger.log( LogPriority.DEBUG, "Creating user script file and writing correct contents..." ) f = open(self.userscriptfile, 'w') f.write(self.userstonixscript) f.close() os.chown(self.userscriptfile, 0, 0) os.chmod(self.userscriptfile, 0o755) except Exception: retval = False self.logger.log(LogPriority.DEBUG, "Failed to create user script file") # write correct contents else: try: self.logger.log( LogPriority.DEBUG, "Writing correct contents to user script file...") f = open(self.userscriptfile, 'w') f.write(self.userstonixscript) f.close() os.chown(self.userscriptfile, 0, 0) os.chmod(self.userscriptfile, 0o755) except Exception: retval = False self.logger.log( LogPriority.DEBUG, "Failed to write contents to user script file") self.logger.log( LogPriority.DEBUG, "Setting Permissions and Ownership on crontab file...") os.chown(self.cronfilelocation, 0, 0) os.chmod(self.cronfilelocation, 0o600) return retval def fixMac(self): """Create or edit STONIX launchd jobs for Mac OS X Create or edit the STONIX launch agent user job for Mac OS X :return: retval :rtype: bool """ retval = True self.macadminfixpath = "/Library/LaunchDaemons/gov.lanl.stonix.fix.plist" self.macadminreportpath = "/Library/LaunchDaemons/gov.lanl.stonix.report.plist" self.macuserpath = "/Library/LaunchAgents/gov.lanl.stonix.user.plist" # defaults servicedict = { '/Library/LaunchDaemons/gov.lanl.stonix.report.plist': 'gov.lanl.stonix.report', '/Library/LaunchDaemons/gov.lanl.stonix.fix.plist': 'gov.lanl.stonix.fix', '/Library/LaunchAgents/gov.lanl.stonix.user.plist': 'gov.lanl.stonix.user' } self.logger.log(LogPriority.DEBUG, "Running fixes for Mac OS") # create the STONIX launchd jobs try: if not self.macadminreportjob: self.logger.log( LogPriority.DEBUG, "The admin report job is either missing or incorrect. Fixing..." ) # remove existing job file if os.path.exists(self.macadminreportpath): os.remove(self.macadminreportpath) # create weekly report plist file f = open(self.macadminreportpath, 'w') f.write(self.stonixplistreport) f.close() os.chown(self.macadminreportpath, 0, 0) os.chmod(self.macadminreportpath, 0o644) self.logger.log( LogPriority.DEBUG, "Admin report job has been created and configured") if not self.macadminfixjob: self.logger.log( LogPriority.DEBUG, "The admin fix job is either missing or incorrect. Fixing..." ) # remove existing job file if os.path.exists(self.macadminfixpath): os.remove(self.macadminfixpath) if self.schedulefixjobsCI.getcurrvalue(): # create weekly fix plist file f = open(self.macadminfixpath, 'w') f.write(self.stonixplistfix) f.close() os.chown(self.macadminfixpath, 0, 0) os.chmod(self.macadminfixpath, 0o644) self.logger.log( LogPriority.DEBUG, "Admin fix job has been created and configured") if not self.macuserjob: self.logger.log( LogPriority.DEBUG, "The user job is either missing or incorrect. Fixing...") # remove existing job file if os.path.exists(self.macuserpath): os.remove(self.macuserpath) # create once-daily user context plist file f = open(self.macuserpath, 'w') f.write(self.stonixplistuser) f.close() os.chown(self.macuserpath, 0, 0) os.chmod(self.macuserpath, 0o644) self.logger.log(LogPriority.DEBUG, "User job has been created and configured") self.logger.log(LogPriority.DEBUG, "Loading STONIX jobs...") #!FIXME This part needs to be changed to work with servicehelper for item in servicedict: if re.search("user", item, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Loading user job...") self.ch.executeCommand("/bin/launchctl load " + item) else: if re.search("fix", item, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Loading admin fix job...") if re.search("report", item, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Loading admin report job...") self.ch.executeCommand("/bin/launchctl enable system/" + str(servicedict[item])) except Exception: retval = False raise return retval
def reportFree(self): compliant = True self.editor1, self.editor2 = "", "" directives1 = { "ipv6_network_interfaces": "none", "ipv6_activate_all_interfaces": "NO", "ip6addrctl_enable": "NO", "ip6addrctl_policy": "NO" } directives2 = { "net.ipv6.conf.all.disable_ipv6": "1", "net.ipv6.conf.default.disable_ipv6": "1", "net.ipv6.conf.lo.disable_ipv6": "1" } path1 = "/etc/rc.conf" path2 = "/etc/sysctl.conf" tmpfile1 = "/etc/rc.conf.tmp" tmpfile2 = "/etc/sysctl.conf.tmp" # try and create /etc/rc.conf if doesn't exist if not os.path.exists(path1): if not createFile(path1, self.logger): compliant = False self.detailedresults += "Unable to create the file: " + \ path1 + ", so this file will not be configured, " + \ "resulting in failed compliance\n" if os.path.exists(path1): if not checkPerms(path1, [0, 0, 420], self.logger): compliant = False self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", path1, tmpfile1, directives1, "present", "closedeq") if not self.editor1.report(): compliant = False # try and create /etc/sysctl.conf if doesn't exist if not os.path.exists(path2): if not createFile(path2, self.logger): compliant = False self.detailedresults += "Unable to create the file: " + \ path2 + " so this file will not be configured " + \ "resulting in failed compliance\n" if os.path.exists(path2): if not checkPerms(path2, [0, 0, 384], self.logger): compliant = False self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", path2, tmpfile2, directives2, "present", "closedeq") if not self.editor2.report(): compliant = False else: compliant = False cmdhelper = CommandHelper(self.logger) cmd = ["/sbin/ifconfig", "-a"] if not cmdhelper.executeCommand(cmd): return False output = cmdhelper.getOutput() for line in output: line = line.strip() if re.search("^nd6", line): if not re.search("(.)*IFDISABLED(.)*", line): compliant = False return compliant
class SecureMDNS(Rule): '''The Avahi daemon implements the DNS Service Discovery and Multicast DNS protocols, which provide service and host discovery on a network. It allows a system to automatically identify resources on the network, such as printers or web servers. This capability is also known as mDNSresponder and is a major part of Zeroconf networking. By default, it is enabled. This rule makes a number of configuration changes to the avahi service in order to secure it. @change: 04/16/2014 ekkehard ci and self.setkvdefaultscurrenthost updates ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 135 self.rulename = 'SecureMDNS' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.compliant = False self.rulesuccess = True self.guidance = [ 'NSA(3.7.2)', 'CCE 4136-8', 'CCE 4409-9', 'CCE 4426-3', 'CCE 4193-9', 'CCE 4444-6', 'CCE 4352-1', 'CCE 4433-9', 'CCE 4451-1', 'CCE 4341-4', 'CCE 4358-8', 'CCE-RHEL7-CCE-TBD 2.5.2' ] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # set up command helper object self.ch = CommandHelper(self.logger) # init helper classes self.sh = ServiceHelper(self.environ, self.logger) self.serviceTarget = "" if self.environ.getostype() == "Mac OS X": self.ismac = True self.hasSIP = False self.plb = "/usr/libexec/PlistBuddy" osxversion = str(self.environ.getosver()) versplit = osxversion.split(".") if len(versplit) > 2: minorVersion = int(versplit[1]) releaseVersion = int(versplit[2]) elif len(versplit) == 2: minorVersion = int(versplit[1]) releaseVersion = 0 else: self.logger.log(LogPriority.ERROR, "Unexpected version string length") raise Exception if minorVersion == 10 and releaseVersion < 4: self.service = "/System/Library/LaunchDaemons/com.apple." + \ "discoveryd.plist" self.servicename = "com.apple.networking.discoveryd" self.parameter = "--no-multicast" self.pbr = self.plb + " -c Print " + self.service + \ " | grep 'no-multicast'" self.pbf = self.plb + ' -c "Add :ProgramArguments: string ' + \ self.parameter + '" ' + self.service elif minorVersion > 10: self.hasSIP = True self.service = "/System/Library/LaunchDaemons/" + \ "com.apple.mDNSResponder.plist" self.servicename = "com.apple.mDNSResponder.reloaded" self.parameter = "NoMulticastAdvertisements" self.preferences = "/Library/Preferences/" + \ "com.apple.mDNSResponder.plist" self.pbr = self.plb + " -c Print " + self.preferences + \ " | grep 'NoMulticastAdvertisements'" self.pbf = "defaults write " + self.preferences + " " + \ self.parameter + " -bool YES" else: self.service = "/System/Library/LaunchDaemons/" + \ "com.apple.mDNSResponder.plist" if minorVersion >= 10: self.servicename = "com.apple.mDNSResponder.reloaded" else: self.servicename = "com.apple.mDNSResponder" self.parameter = "-NoMulticastAdvertisements" self.pbr = self.plb + " -c Print " + self.service + \ " | grep 'NoMulticastAdvertisements'" self.pbf = self.plb + ' -c "Add :ProgramArguments: string ' + \ self.parameter + '" ' + self.service else: self.ismac = False # init CIs datatype = 'bool' mdnskey = 'SECUREMDNS' avahikey = 'DISABLEAVAHI' mdnsinstructions = 'To configure the Avahi server daemon ' + \ 'securely set the value of SECUREMDNS to True and the ' + \ 'value of DISABLEAVAHI to False.' avahiinstructions = 'To completely disable the Avahi server ' + \ 'daemon rather than configure it, set the value of ' + \ 'DISABLEAVAHI to True and the value of SECUREMDNS to False.' mdnsdefault = False avahidefault = True self.SecureMDNS = self.initCi(datatype, mdnskey, mdnsinstructions, mdnsdefault) self.DisableAvahi = self.initCi(datatype, avahikey, avahiinstructions, avahidefault) self.configparser = configparser.SafeConfigParser() self.confavahidict = { 'use-ipv6': { 'section': 'server', 'val': 'no' }, 'check-response-ttl': { 'section': 'server', 'val': 'yes' }, 'disallow-other-stacks': { 'section': 'server', 'val': 'yes' }, 'disable-publishing': { 'section': 'publish', 'val': 'yes' }, 'disable-user-service-publishing': { 'section': 'publish', 'val': 'yes' }, 'publish-addresses': { 'section': 'publish', 'val': 'no' }, 'publish-hinfo': { 'section': 'publish', 'val': 'no' }, 'publish-workstation': { 'section': 'publish', 'val': 'no' }, 'publish-domain': { 'section': 'publish', 'val': 'no' } } self.confoptions = { 'server': { 'use-ipv6': 'no', 'check-response-ttl': 'yes', 'disallow-other-stacks': 'yes' }, 'publish': { 'disable-publishing': 'yes', 'disable-user-service-publishing': 'yes', 'publish-addresses': 'no', 'publish-hinfo': 'no', 'publish-workstation': 'no', 'publish-domain': 'no' } } self.iditerator = 0 def report(self): '''The report method examines the current configuration and determines whether or not it is correct. 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. :returns: bool @author: Breen Malmberg @change: dwalker - added conditional call to reportmac() @change: Breen Malmberg - 12/05/2017 - removed unnecessary argument "serviceTarget" in linux-only call to servicehelper; removed assignment of unused local variable serviceTarget to self.servicename since servicename is not assigned in the linux code logic path (which was resulting in variable referenced before assignment error) ''' try: # defaults compliant = True self.detailedresults = '' self.rulesuccess = True # if system is a mac, run reportmac if self.ismac: compliant = self.reportmac() # if not mac os x, then run this portion else: self.editor = None # set up package helper object only if not mac os x self.pkghelper = Pkghelper(self.logger, self.environ) # if the disableavahi CI is set, we want to make sure it is # completely disabled if self.DisableAvahi.getcurrvalue(): self.package = "avahi-daemon" # if avahi-daemon is still running, it is not disabled if self.sh.auditService('avahi-daemon'): compliant = False self.detailedresults += 'DisableAvahi has been ' + \ 'set to True, but avahi-daemon service is ' + \ 'currently running.\n' self.numdependencies = 0 if self.pkghelper.determineMgr() == 'yum' or \ self.pkghelper.determineMgr() == 'dnf': self.package = "avahi" # The following KVEditor for /etc/sysconfig/network is # used to meet the zeroconf requirement in # CCE-RHEL7-CCE-TBD 2.5.2 path = "/etc/sysconfig/network" self.path = path if os.path.exists(path): tmppath = path + ".tmp" data = {"NOZEROCONF": "yes"} self.editor = KVEditorStonix( self.statechglogger, self.logger, "conf", path, tmppath, data, "present", "closedeq") if not self.editor.report(): self.compliant = False self.detailedresults += path + " does not " + \ "have the correct settings.\n" else: self.compliant = False self.detailedresults += path + " does not exist.\n" self.numdependencies = \ self.parseNumDependencies(self.package) if self.numdependencies <= 3: if self.pkghelper.check(self.package): compliant = False self.detailedresults += 'DisableAvahi is ' + \ 'set to True, but Avahi is currently ' + \ 'installed.\n' else: self.detailedresults += 'Avahi has too many ' + \ 'dependent packages. Will not attempt to ' + \ 'remove it.\n' elif self.pkghelper.determineMgr() == "zypper": self.package = "avahi" elif self.pkghelper.check(self.package): compliant = False self.detailedresults += 'DisableAvahi is ' + \ 'set to True, but Avahi is currently ' + \ 'installed.\n' # otherwise if the securemdns CI is set, we want to make sure # it is securely configured if self.SecureMDNS.getcurrvalue(): # if the config file is found, proceed if os.path.exists('/etc/avahi/avahi-daemon.conf'): kvtype = "tagconf" intent = "present" filepath = '/etc/avahi/avahi-daemon.conf' tmpfilepath = '/etc/avahi/avahi-daemon.conf.stonixtmp' conftype = "closedeq" self.avahiconfeditor = KVEditorStonix( self.statechglogger, self.logger, kvtype, filepath, tmpfilepath, self.confoptions, intent, conftype) self.avahiconfeditor.report() if self.avahiconfeditor.fixables: compliant = False self.detailedresults += "\nThe following configuration options are missing or incorrect in " + str( filepath) + ":\n" + "\n".join( self.avahiconfeditor.fixables) # if config file not found, check if avahi is installed else: # if not installed, we can't configure anything if not self.pkghelper.check('avahi'): self.detailedresults += 'Avahi Daemon not ' + \ 'installed. Cannot configure it.\n' compliant = True self.logger.log(LogPriority.DEBUG, self.detailedresults) # if it is installed, then the config file is missing else: compliant = False self.detailedresults += 'Avahi is installed ' + \ 'but could not find config file in ' + \ 'expected location.\n' self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = compliant except (IOError): self.detailedresults += '\n' + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) 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 reportmac(self): '''check for configuration items needed for mac os x :returns: bool @author: Breen Malmberg @change: dwalker - implemented kveditor defaults ''' try: self.detailedresults = "" # See if parameter is set self.ch.executeCommand(self.pbr) resultOutput = self.ch.getOutput() if len(resultOutput) >= 1: if (resultOutput[0] == ""): commandsuccess = False self.detailedresults += "Parameter: " + str(self.parameter) + \ " for service " + self.servicename + " is not set.\n" else: commandsuccess = True debug = "Parameter: " + str(self.parameter) + \ " for service " + self.servicename + \ " is set correctly." self.logger.log(LogPriority.DEBUG, debug) else: commandsuccess = False self.detailedresults += "Parameter: " + str(self.parameter) + \ " for service " + self.servicename + " is not set.\n" # see if service is running if not re.match("^10.11", self.environ.getosver()): servicesuccess = self.sh.auditService( self.service, serviceTarget=self.servicename) else: servicesuccess = self.sh.auditService( self.service, serviceTarget=self.servicename) if servicesuccess: debug = "Service: " + str(self.service) + ", " + \ self.servicename + " audit successful." self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += "Service: " + str(self.service) + \ ", " + self.servicename + " audit failed.\n" if servicesuccess and commandsuccess: return True else: return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False raise 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. @author: Breen Malmberg @change: dwalker - added statechglogger findrulechanges and deleteentry @changed: Breen Malmberg - 12/05/2017 - removed unnecessary servicetarget ''' try: self.rulesuccess = True self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) self.detailedresults = "" # if this system is a mac, run fixmac() if self.ismac: self.rulesuccess = self.fixmac() # if not mac os x, run this portion else: # if DisableAvahi CI is enabled, disable the avahi service # and remove the package if self.DisableAvahi.getcurrvalue(): avahi = self.package avahid = 'avahi-daemon' if self.sh.auditService(avahid): debug = "Disabling " + avahid + " service" self.logger.log(LogPriority.DEBUG, debug) self.sh.disableService(avahid) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "servicehelper", "servicename": avahid, "startstate": "enabled", "endstate": "disabled" } self.statechglogger.recordchgevent(myid, event) if self.environ.getosfamily() == 'linux' and \ self.pkghelper.check(avahi): if self.numdependencies <= 3: self.pkghelper.remove(avahi) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "pkghelper", "pkgname": avahi, "startstate": "installed", "endstate": "removed" } self.statechglogger.recordchgevent(myid, event) else: debug += 'Avahi package has too many dependent ' \ + 'packages. Will not attempt to remove.\n' self.logger.log(LogPriority.DEBUG, debug) if self.pkghelper.determineMgr() == 'yum' or \ self.pkghelper.determineMgr() == 'dnf': path = self.path if not os.path.exists(path): if createFile(path, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": path } self.statechglogger.recordchgevent(myid, event) else: self.rulesuccess = False self.detailedresults += "Failed to create " + \ "file: " + path + ".\n" if self.editor is None: tmppath = path + ".tmp" data = {"NOZEROCONF": "yes"} self.editor = KVEditorStonix( self.statechglogger, self.logger, "conf", path, tmppath, data, "present", "closedeq") if not self.editor.report(): if self.editor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.commit(): self.rulesuccess = False self.detailedresults += "Could not " + \ "commit changes to " + path + ".\n" else: self.rulesuccess = False self.detailedresults += "Could not fix " + \ "file " + path + ".\n" # if SecureMDNS CI is enabled, configure avahi-daemon.conf if self.SecureMDNS.getcurrvalue(): # if config file is present, proceed avahiconf = '/etc/avahi/avahi-daemon.conf' if os.path.exists(avahiconf): if self.avahiconfeditor.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.avahiconfeditor.setEventID(myid) if not self.avahiconfeditor.fix(): self.rulesuccess = False debug = "KVEditor fix for " + avahiconf + \ "failed" self.logger.log(LogPriority.DEBUG, debug) elif not self.avahiconfeditor.commit(): self.rulesuccess = False debug = "KVEditor commit for " + avahiconf + \ "failed" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) setPerms(avahiconf, [0, 0, 0o644], self.logger, self.statechglogger, myid) resetsecon(avahiconf) # if config file is not present and avahi not installed, # then we can't configure it else: if not self.pkghelper.check(avahi): debug = 'Avahi Daemon not installed. ' + \ 'Cannot configure it.' self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += 'Avahi daemon ' + \ 'installed, but could not locate the ' + \ 'configuration file for it.\n' self.rulesuccess = False except IOError: self.detailedresults += '\n' + traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) 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 fixmac(self): '''apply fixes needed for mac os x @author: Breen Malmberg @change: dwalker - implemented kveditor instead of direct editing ''' try: self.detailedresults = "" success = True # See if parameter is set self.ch.executeCommand(self.pbr) resultOutput = self.ch.getOutput() if len(resultOutput) >= 1: if (resultOutput[0] == ""): fixit = True else: fixit = False else: fixit = True # Add parameter if fixit: # Due to weaknesses in using PlistBuddy and defaults to delete # from plists, as well as shortcomings in STONIX's state change # logging, we will record this change as a file deletion. # If the rule's undo is run on OS X, it will restore the # previous version of this file. self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if self.hasSIP: self.statechglogger.recordfiledelete( self.preferences, myid) else: self.statechglogger.recordfiledelete(self.service, myid) self.ch.executeCommand(self.pbf) resultOutput = self.ch.getOutput() errorcode = self.ch.getReturnCode() if errorcode == 0: debug = self.parameter + " was set successfully!" self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += self.parameter + \ " was not set successfully!\n" self.statechglogger.deleteentry(myid) success = False else: debug = self.parameter + " was already set!" self.logger.log(LogPriority.DEBUG, debug) # Reload Service if success: success = self.sh.reloadService(self.service, serviceTarget=self.servicename) if success: debug = "Service: " + str(self.service) + ", " + \ self.servicename + " was reloaded successfully." self.logger.log(LogPriority.DEBUG, debug) else: debug = "Service: " + str(self.service) + ", " + \ self.servicename + " reload failed!" self.logger.log(LogPriority.DEBUG, debug) except (KeyboardInterrupt, SystemExit): raise except Exception: success = False raise return success def parseNumDependencies(self, pkgname): '''parse output of yum command to determine number of dependent packages to the given pkgname :param pkgname: :returns: int @author: Breen Malmberg ''' numdeps = 0 flag = 0 try: if self.pkghelper.determineMgr() == 'zypper': command = ['zypper', 'info', '--requires', pkgname] self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if flag: numdeps += 1 if re.search('Requires:', line): flag = 1 elif self.pkghelper.determineMgr() == 'yum': command = ['yum', '--assumeno', 'remove', pkgname] self.ch.wait = False self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('Dependent packages\)', line): sline = line.split('(+') if len(sline) < 2: return numdeps cline = [ int(s) for s in sline[1].split() if s.isdigit() ] numdeps = int(cline[0]) elif self.pkghelper.determineMgr() == 'dnf': command = ['dnf', '--assumeno', 'remove', pkgname] self.ch.wait = False self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('Dependent packages\)', line): sline = line.split('(+') if len(sline) < 2: return numdeps cline = [ int(s) for s in sline[1].split() if s.isdigit() ] numdeps = int(cline[0]) elif self.pkghelper.determineMgr() == 'apt-get': command = ['apt-cache', 'depends', pkgname] self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('Depends:', line): numdeps += 1 else: self.detailedresults += 'Unable to detect package manager\n' return numdeps except (IOError, OSError): self.detailedresults += 'Specified package: ' + str(pkgname) + \ ' not found.\n' return numdeps except Exception: raise return numdeps
class DisableIPV6(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 123 self.rulename = "DisableIPV6" self.formatDetailedResults("initialize") self.guidance = ["NSA 2.5.3.1"] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # configuration item instantiation datatype = 'bool' key = 'DISABLEIPV6' instructions = "To disable this rule set the value of DISABLEIPV6 " + \ "to False." default = False self.ci = self.initCi(datatype, key, instructions, default) self.iditerator = 0 self.created = False self.created2 = False # self.editor1: sysctl file editor # self.editor2: network file editor # self.editor3: sshd file editor self.editor1, self.editor2, self.editor3 = "", "", "" self.sh = ServiceHelper(self.environ, self.logger) self.sethelptext() def report(self): try: self.detailedresults = "" if self.environ.getosfamily() == "linux": self.ph = Pkghelper(self.logger, self.environ) self.compliant = self.reportLinux() elif self.environ.getosfamily() == "freebsd": self.compliant = self.reportFree() elif self.environ.getostype() == "Mac OS X": self.compliant = self.reportMac() 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): 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() elif self.environ.getosfamily() == "freebsd": self.rulesuccess = self.fixFree() elif self.environ.getosfamily() == "darwin": self.rulesuccess = self.fixMac() 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 reportMac(self): self.ifaces = [] compliant = True self.cmdhelper = CommandHelper(self.logger) cmd = ["/usr/sbin/networksetup", "-listallnetworkservices"] if not self.cmdhelper.executeCommand(cmd): self.detailedresults += "Unable to run " + \ "networksetup -listallnetworkservices command\n" return False output = self.cmdhelper.getOutput() for item in output: item = item.strip() cmd = ["/usr/sbin/networksetup", "-getinfo", item] if not self.cmdhelper.executeCommand(cmd): self.detailedresults += "Unable to run " + \ "networksetup -getinfo command\n" return False output2 = self.cmdhelper.getOutput() for item2 in output2: if re.search("^IPv6:", item2): check = item2.split(":") if check[1].strip() != "Off": self.detailedresults += "IPV6 is not turned off " + \ "for " + item + " interface\n" self.ifaces.append(item) compliant = False return compliant def fixMac(self): if self.ifaces: for item in self.ifaces: cmd = ["/usr/sbin/networksetup", "setv6off", item] if not self.cmdhelper.executeCommand(cmd): self.rulesuccess = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { 'eventtype': 'comm', 'command': ['/usr/sbin/networksetup', 'setv6automatic', item] } self.statechglogger.recordchgevent(myid, event) return self.rulesuccess def reportFree(self): compliant = True self.editor1, self.editor2 = "", "" directives1 = { "ipv6_network_interfaces": "none", "ipv6_activate_all_interfaces": "NO", "ip6addrctl_enable": "NO", "ip6addrctl_policy": "NO" } directives2 = { "net.ipv6.conf.all.disable_ipv6": "1", "net.ipv6.conf.default.disable_ipv6": "1", "net.ipv6.conf.lo.disable_ipv6": "1" } path1 = "/etc/rc.conf" path2 = "/etc/sysctl.conf" tmpfile1 = "/etc/rc.conf.tmp" tmpfile2 = "/etc/sysctl.conf.tmp" # try and create /etc/rc.conf if doesn't exist if not os.path.exists(path1): if not createFile(path1, self.logger): compliant = False self.detailedresults += "Unable to create the file: " + \ path1 + ", so this file will not be configured, " + \ "resulting in failed compliance\n" if os.path.exists(path1): if not checkPerms(path1, [0, 0, 420], self.logger): compliant = False self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", path1, tmpfile1, directives1, "present", "closedeq") if not self.editor1.report(): compliant = False # try and create /etc/sysctl.conf if doesn't exist if not os.path.exists(path2): if not createFile(path2, self.logger): compliant = False self.detailedresults += "Unable to create the file: " + \ path2 + " so this file will not be configured " + \ "resulting in failed compliance\n" if os.path.exists(path2): if not checkPerms(path2, [0, 0, 384], self.logger): compliant = False self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", path2, tmpfile2, directives2, "present", "closedeq") if not self.editor2.report(): compliant = False else: compliant = False cmdhelper = CommandHelper(self.logger) cmd = ["/sbin/ifconfig", "-a"] if not cmdhelper.executeCommand(cmd): return False output = cmdhelper.getOutput() for line in output: line = line.strip() if re.search("^nd6", line): if not re.search("(.)*IFDISABLED(.)*", line): compliant = False return compliant def fixFree(self): # debug messages are used for developers, self.detailedresults # are used for the users information path1 = "/etc/rc.conf" path2 = "/etc/sysctl.conf" success = True debug = "" if os.path.exists(path1): if not checkPerms(path1, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 420], self.logger, self.statechglogger, myid): success = False if self.editor1: if self.editor1.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): debug += "Kveditor unable to correct file: " + \ path1 + "\n" self.detailedresults += "Unable to correct " + path1 + \ "\n" success = False elif not self.editor1.commit(): self.detailedresults += "Unable to correct " + path1 + \ "\n" debug += "commit for kveditor1 was not successful\n" success = False else: debug += "Editor2 was never created so path didn't exist \ and/or wasn't able to be created\n" success = False else: self.detailedresults += path1 + " doesn't exist!\n" debug += path1 + " doesn't exist, unble to fix file\n" success = False if os.path.exists(path2): # check permissions on /etc/sysctl.conf if not checkPerms(path2, [0, 0, 384], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) # set permissions if wrong if not setPerms(self.path, [0, 0, 384, self.logger], self.statechglogger, myid): success = False # check if editor is present if self.editor2: if self.editor2.fixables(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): debug += "Kveditor unable to correct file: " + \ path2 + "\n" self.detailedresults += "Unable to correct " + path2 + \ "\n" success = False elif not self.editor2.commit(): self.detailedresults += "Unable to correct " + path2 + \ "\n" debug += "commit for kveditor2 was not successful\n" success = False else: debug += "Editor2 was never created so path didn't exist \ and/or wasn't able to be created\n" success = False else: self.detailedresults += path2 + " doesn't exist!\n" debug += path2 + " doesn't exist, unble to fix file\n" success = False # restart the network ch = CommandHelper(self.logger) cmd = ["/etc/rc.d/netif", "restart"] if not ch.executeCommand(cmd): self.detaileresults += "Unable to restart network\n" success = False return success def reportLinux(self): self.ch = CommandHelper(self.logger) compliant = True netwrkfile = "" ifacefile = "" self.modprobefiles = [] #self.modprobeOK = False self.modprobeOK = True sysctl = "/etc/sysctl.conf" self.interface = {"IPV6INIT": "no", "NETWORKING_IPV6": "no"} self.sysctls = { "net.ipv6.conf.all.disable_ipv6": "1", "net.ipv6.conf.default.disable_ipv6": "1" } self.modprobes = { "options": ["ipv6 disable=1"], "install": ["ipv6 /bin/true"] } # stig portion, check netconfig file for correct contents if self.ph.manager == "apt-get": nfspkg = "nfs-common" else: nfspkg = "nfs-utils.x86_64" if self.ph.check(nfspkg): if os.path.exists("/etc/netconfig"): item1 = "udp6 tpi_clts v inet6 udp - -" item2 = "tcp6 tpi_cots_ord v inet6 tcp - -" contents = readFile("/etc/netconfig", self.logger) for line in contents: line = re.sub("\s+", " ", line.strip()) if re.search(item1, line) or re.search(item2, line): self.detailedresults += "/etc/netconfig file contains " + \ "lines we don't want present\n" compliant = False # "ifconfig" has been deprecated on Debian9 and some otherd distros # so use "ip addr" instead # Here we check if the system is giving out ipv6 ip addresses if os.path.exists("/sbin/ifconfig"): cmd = ["/sbin/ifconfig"] else: cmd = ["/sbin/ip", "addr"] if not self.ch.executeCommand(cmd): compliant = False else: output = self.ch.getOutput() for line in output: if re.search("^inet6", line.strip()): self.detailedresults += "inet6 exists in the " + \ "ifconfig output\n" compliant = False break # check for ipv6 address in hostname file if os.path.exists("/etc/hosts"): contents = readFile("/etc/hosts", self.logger) for line in contents: if re.search("^#", line) or re.match("^\s*$", line): continue if re.search(":", line): compliant = False # check compliancy of /etc/sysctl.conf file if not os.path.exists(sysctl): self.detailedresults += "File " + sysctl + " does not exist\n" compliant = False else: tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): self.detailedresults += "/etc/sysctl file doesn't contain \ the correct contents\n" compliant = False if not checkPerms(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions for " + sysctl + \ "are incorrect\n" compliant = False # in addition to checking /etc/sysctl.conf contents we need to # also check sysctl compliancy using the sysctl command for key in self.sysctls: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() output = self.ch.getOutputString() errmsg = output + self.ch.getErrorString() if retcode != 0: if re.search("unknown key", errmsg): continue else: self.detailedresults += "Failed to get value " + key + " using sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: actualoutput = output.strip() expectedoutput = key + " = " + self.sysctls[key] if actualoutput != expectedoutput: compliant = False self.detailedresults += "\nsysctl output has " + \ "incorrect value: expected " + \ expectedoutput + ", found " + \ actualoutput + "\n" # check files inside modprobe.d directory for correct contents if os.path.exists("/etc/modprobe.d/"): modprobefiles = glob.glob("/etc/modprobe.d/*") for modfile in modprobefiles: tmpfile = "" modprobekveditor = KVEditorStonix(self.statechglogger, self.logger, "conf", modfile, tmpfile, self.modprobes, "present", "space") if modprobekveditor.report(): self.modprobeOK = True break if not self.modprobeOK: self.detailedresults += "Didn't find desired contents inside files " + \ "within /etc/modprobe.d/" compliant = False else: # system isn't using loadable kernel modules, not an issue self.modprobeOK = True # Check for existence of interface and network files to be configured if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" if not os.path.exists(ifacefile): ifacefile = "" netwrkfile = "/etc/sysconfig/network" if not os.path.exists(netwrkfile): netwrkfile = "" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" if not os.path.exists(ifacefile): ifacefile = "" # go through interface directory and check each interface file # for correct contents if ifacefile: dirs = glob.glob(ifacefile + '*') for loc in dirs: contents = [] if re.search('^' + ifacefile + 'ifcfg', loc): if not checkPerms(loc, [0, 0, 0o644], self.logger): compliant = False contents = readFile(loc, self.logger) if contents: for key in self.interface: found = False for line in contents: if re.search("^#", line) or re.match( "^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip( ) == self.interface[key]: found = True continue else: found = False break else: compliant = False self.detailedresults += loc + \ " file in bad format\n" if not found: self.detailedresults += "contents of " + \ loc + " file is wrong\n" compliant = False break else: continue else: compliant = False # check network file for correct contents if netwrkfile: if os.path.exists(netwrkfile): if not checkPerms(netwrkfile, [0, 0, 0o644], self.logger): compliant = False tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface, "present", "closedeq") if not self.editor2.report(): self.detailedresults += netwrkfile + " doesn't contain \ the correct contents\n" compliant = False else: self.detailedresults += netwrkfile + " doesn't exist\n" compliant = False # This subpart is only for apt-get based systems # sshd needs the inet directive for ipv6 disablement if self.ph.manager == "apt-get": data = {"AddressFamily": "inet"} kvtype = "conf" path = "/etc/ssh/sshd_config" tmpPath = path + ".tmp" intent = "present" configtype = "space" self.editor3 = KVEditorStonix(self.statechglogger, self.logger, kvtype, path, tmpPath, data, intent, configtype) if not self.editor3.report(): self.detailedresults += "/etc/ssh/ssdh_config doesn't \ contain the correct contents\n" compliant = False return compliant def fixLinux(self): ''' @change: dkennel removed extraneous arg from setperms call on 864 ''' universal = "#The following lines were added by stonix\n" debug = "" success = True ifacefile = "" netwrkfile = "" sysctl = "/etc/sysctl.conf" blacklistfile = "/etc/modprobe.d/stonix-blacklist.conf" # STIG portion, correct netconfig file if self.ph.manager == "apt-get": nfspkg = "nfs-common" else: nfspkg = "nfs-utils.x86_64" # if package not installed, no need to configure it if self.ph.check(nfspkg): if os.path.exists("/etc/netconfig"): filestring = "" # we want to make sure the following two lines don't # appear in the netconfig file item1 = "udp6 tpi_clts v inet6 udp - -" item2 = "tcp6 tpi_cots_ord v inet6 tcp - -" contents = readFile("/etc/netconfig", self.logger) for line in contents: templine = re.sub("\s+", " ", line.strip()) # if we find the lines, skip them thus leaving them out of # of the rewrite if re.search(item1, templine) or re.search( item2, templine): continue else: filestring += line tmpfile = "/etc/netconfig.tmp" if not writeFile(tmpfile, filestring, self.logger): success = False else: # record event, rename file, set perms self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": "/etc/netconfig"} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( "/etc/netconfig", tmpfile, myid) os.rename(tmpfile, "/etc/netconfig") os.chown("/etc/netconfig", 0, 0) os.chmod("/etc/netconfig", 420) resetsecon("/etc/netconfig") # remove any ipv6 addresses from /etc/hosts file if os.path.exists("/etc/hosts"): contents = readFile("/etc/hosts", self.logger) tempstring = "" tmpfile = "/etc/hosts.tmp" for line in contents: if re.search("^#", line) or re.match("^\s*$", line): tempstring += line continue elif re.search(":", line): tempstring += "#" + line else: tempstring += line if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": "/etc/hosts"} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange("/etc/hosts", tmpfile, myid) os.rename(tmpfile, "/etc/hosts") os.chown("/etc/hosts", 0, 0) os.chmod("/etc/hosts", 420) resetsecon("/etc/hosts") else: success = False debug = "Unable to write to file /etc/hosts\n" self.logger.log(LogPriority.DEBUG, debug) # fix sysctl / tuning kernel parameters # manually write key value pairs to /etc/sysctl.conf created = False if not os.path.exists(sysctl): if createFile(sysctl, self.logger): created = True setPerms(sysctl, [0, 0, 0o644], self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sysctl} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to create " + sysctl + "\n" self.logger.log(LogPriority.DEBUG, debug) if os.path.exists(sysctl): if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sysctl, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): if self.editor1.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor1.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not setPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False resetsecon(sysctl) # here we also check the output of the sysctl command for each key # to cover all bases for key in self.sysctls: if self.ch.executeCommand("/sbin/sysctl " + key): output = self.ch.getOutputString().strip() errmsg = output + self.ch.getErrorString() if re.search("unknown key", errmsg): continue if not re.search(self.sysctls[key] + "$", output): undovalue = output[-1] self.ch.executeCommand("/sbin/sysctl -q -e -w " + key + "=" + self.sysctls[key]) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set " + key + " = " + self.sysctls[ key] + "\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -q -e -w " + key + "=" + undovalue event = { "eventtype": "commandstring", "command": command } self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Unable to get value for " + key + "\n" success = False # at the end do a print and ignore any key errors to ensure # the new values are read into the kernel self.ch.executeCommand("/sbin/sysctl -q -e -p") retcode2 = self.ch.getReturnCode() if retcode2 != 0: success = False self.detailedresults += "Failed to load new sysctl configuration from config file\n" errmsg2 = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg2) # We never found the correct contents in any of the modprobe.d files # so we're going to created the stonix-blacklist file # this file is used in other rules if not self.modprobeOK: created = False tmpfile = blacklistfile + ".tmp" modprobekveditor = KVEditorStonix(self.statechglogger, self.logger, "conf", blacklistfile, tmpfile, self.modprobes, "notpresent", "space") if not os.path.exists(blacklistfile): # create the file and record the event as file creation if createFile(blacklistfile, self.logger): created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": blacklistfile } self.statechglogger.recordchgevent(myid, event) if os.path.exists(blacklistfile): if not modprobekveditor.report(): if not modprobekveditor.fix(): success = False self.detailedresults += "Unable to correct contents in " + \ blacklistfile + "\n" else: # if the file was created, then we already recorded an event # for that, so this step would get skipped if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) modprobekveditor.setEventID(myid) if not modprobekveditor.commit(): success = False self.detailedresults += "Unable to correct contents in " + \ blacklistfile + "\n" # fix ifcfg (interface) files if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" netwrkfile = "/etc/sysconfig/network" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" if ifacefile: if os.path.exists(ifacefile): dirs = glob.glob(ifacefile + "*") if dirs: for loc in dirs: interface = {"IPV6INIT": "no", "NETWORKING_IPV6": "no"} interface2 = { "IPV6INIT": "no", "NETWORKING_IPV6": "no" } found = False tempstring = "" if re.search('^' + ifacefile + 'ifcfg', loc): filename = loc tmpfile = filename + ".tmp" contents = readFile(filename, self.logger) if not checkPerms(filename, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(filename, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + \ filename + "\n" self.logger.log(LogPriority.DEBUG, debug) success = False for key in interface: found = False for line in contents: if re.search("^#", line) or \ re.match("^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip( ) == interface[key]: if found: continue found = True else: contents.remove(line) if found: del interface2[key] for line in contents: tempstring += line tempstring += universal for key in interface2: tempstring += key + "=" + interface2[key] + \ "\n" if not writeFile(tmpfile, tempstring, self.logger): success = False debug = "Unable to write to file " + loc + "\n" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': filename} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( filename, tmpfile, myid) os.rename(tmpfile, filename) os.chown(filename, 0, 0) os.chmod(filename, 420) resetsecon(filename) elif not os.path.exists(ifacefile) and ifacefile != "": # will not attempt to create the interface files debug = "interface directory which holds interface \ files, doesn't exist, stonix will not attempt to make this \ directory or the files contained therein" success = False self.logger.log(LogPriority.DEBUG, debug) # fix network file if it exists if netwrkfile: if not os.path.exists(netwrkfile): if not createFile(netwrkfile, self.logger): debug = "Unable to create " + netwrkfile + "file\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: if not checkPerms(netwrkfile, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(netwrkfile, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + \ netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) success = False tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface, "present", "closedeq") if not self.editor2.report(): self.detailedresults += netwrkfile + " doesn't contain \ the correct contents\n" if self.editor2: if self.editor2.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for " + netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor2.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for " + netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(netwrkfile, 0, 0) os.chmod(netwrkfile, 420) resetsecon(netwrkfile) # fix sshd_config file for apt-get systems if ssh is installed if self.ph.manager == "apt-get": if not os.path.exists("/etc/ssh/sshd_config"): msg = "/etc/ssh/ssd_config doesn\'t exist. This could mean ssh \ is not installed or the file has been inadvertantly deleted. Due to the \ complexity of this file stonix will not attempt to create this file" self.logger.log(LogPriority.DEBUG, msg) success = False else: if not checkPerms("/etc/ssh/sshd_config", [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms("/etc/ssh/sshd_config", [0, 0, 420], self.logger, self.statechglogger, myid): success = False debug = "Unable to set permissions on " + \ "/etc/ssh/sshd_config\n" self.logger.log(LogPriority.DEBUG, debug) if self.editor3: if self.editor3.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor3.setEventID(myid) if not self.editor3.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/ssh/sshd_config file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor3.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/ssh/sshd_config file\n" self.logger.log(LogPriority.DEBUG, debug) os.chown("/etc/ssh/sshd_config", 0, 0) os.chmod("/etc/ssh/sshd_config", 420) resetsecon("/etc/ssh/sshd_config") return success
class MinimizeServices(Rule): '''The MinimizeServices class sets the system so that only approved services are running at any given time. ''' def __init__(self, config, environ, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.statechglogger = statechglogger self.rulenumber = 12 self.rulename = 'MinimizeServices' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.applicable = {'type': 'black', 'family': ['darwin']} self.servicehelper = ServiceHelper(self.environ, self.logger) self.guidance = [ 'NSA 2.1.2.2', 'NSA 2.2.2.3', 'NSA 2.4.3', 'NSA 3.1', 'CCE-3416-5', 'CCE-4218-4', 'CCE-4072-5', 'CCE-4254-9', 'CCE-3668-1', 'CCE-4129-3', 'CCE-3679-8', 'CCE-4292-9', 'CCE-4234-1', 'CCE-4252-3', 'CCE-3390-2', 'CCE-3974-3', 'CCE-4141-8', 'CCE-3537-8', 'CCE-3705-1', 'CCE-4273-9', 'CCE-3412-4', 'CCE-4229-1', 'CCE-4123-6', 'CCE-4286-1', 'CCE-3425-6', 'CCE-4211-9', 'CCE-3854-7', 'CCE-4356-2', 'CCE-4369-5', 'CCE-4100-4', 'CCE-3455-3', 'CCE-4421-4', 'CCE-4302-6', 'CCE-3822-4', 'CCE-4364-6', 'CCE-4355-4', 'CCE-4377-8', 'CCE-4289-5', 'CCE-4298-6', 'CCE-4051-9', 'CCE-4324-0', 'CCE-4406-5', 'CCE-4268-9', 'CCE-4303-4', 'CCE-4365-3', 'CCE-3755-6', 'CCE-4425-5', 'CCE-4191-3', 'CCE-4336-4', 'CCE-4376-0', 'CCE-4416-4', 'CCE-3501-4', 'CCE-4396-8', 'CCE-3535-2', 'CCE-3568-3', 'CCE-4533-6', 'CCE-4550-0', 'CCE-4473-5', 'CCE-4491-7', 'CCE-3578-2', 'CCE-3919-8', 'CCE-4338-0', 'CCE-3847-1', 'CCE-4551-8', 'CCE-4556-7', 'CCE-3765-5', 'CCE-4508-8', 'CCE-4327-3', 'CCE-4468-5', 'CCE-4512-0', 'CCE-4375-2', 'CCE-4393-5', 'CCE-3662-4', 'CCE-4442-0', 'CCE-4596-3', 'CCE-4486-7', 'CCE-4362-0', 'CCE-3622-8', 'CCE-4299-4', 'CCE-4592-2', 'CCE-4614-4', 'CCE-4279-6', 'CCE-4557-5', 'CCE-4588-0', 'CCE-4354-7', 'CCE-4240-8', 'CCE-4517-9', 'CCE-4284-6', 'CCE-4429-7', 'CCE-4306-7', 'CCE-4499-0', 'CCE-4266-3', 'CCE-4411-5', 'CCE-4305-9', 'CCE-4477-6', 'CCE-3650-9', 'CCE-4571-6', 'CCE-3950-3', 'CCE-4470-1', 'CCE-4598-9', 'CCE-4620-1', 'CCE-4333-1', 'CCE-3857-0', 'CCE-4359-6', 'CCE-4615-1', 'CCE-4007-1', 'CCE-3901-6', 'CCE-4553-4', 'CCE-4584-9', 'CCE-4611-0', 'CCE-3655-8', 'CCE-4541-9', 'CCE-4483-4', 'CCE-3663-2', 'CCE-4037-8' ] self.bsddefault = [ 'background_fsck', 'cleanvar', 'cron', 'devd', 'dmesg', 'gptboot', 'hostid', 'ip6addrctl', 'mixer', 'newsyslog', 'ntpd', 'sendmail_submit', 'sendmail_submit', 'sendmail_msp_queue', 'sshd', 'syslogd', 'virecover' ] self.linuxdefault = [ 'acpi-support', 'acpid', 'alsa-restore', 'alsa-store', 'alsasound', 'anacron', 'apmd', 'atd', 'audit', 'auditd', 'autofs', 'blk-availability', 'bootlogd', 'cpuspeed', 'cron', 'crond', 'cups', 'dbus', 'earlysyslog', 'exim4', 'fbset', 'firstboot', 'gdm3', 'grub-common', 'haldaemon', 'haveged', 'iptables', 'ip6tables', 'irqbalance', 'kbd', 'libnss-ldap', 'lm_sensors', 'lvm2-monitor', 'mcstrans', 'mdmonitor', 'messagebus', 'minissdpd', 'motd', 'netfs', 'network', 'network-manager', 'NetworkManager', 'network-remotefs', 'nfs-common', 'nfslock', 'nscd', 'nslcd', 'ntpd', 'oddjobd', 'osad', 'ondemand', 'portmap', 'postfix', 'psacct', 'pulseaudio', 'purge-kernels', 'random', 'rasagent', 'rc.local', 'rdma', 'restorecond', 'rhnsd', 'rmnologin', 'rpcgssd', 'rpcidmapd', 'rpcbind', 'rsyslog', 'rsyslogd', 'sendmail', 'setroubleshoot', 'smartd', 'splash', 'splash-early', 'sshd', 'sssd', 'syslog', 'sysstat', 'udev-post', 'xfs' ] self.specials = [ 'apparmor', 'urandom', 'x11-common', 'sendsigs', 'unmountnfs.sh', 'networking', 'umountfs', 'umountroot', 'reboot', 'halt', 'killprocs', 'single', 'unattended-upgrades', 'devfs', 'dmesg', 'bootmisc', 'fsck', 'hostname', 'hwclock', 'keymaps', 'localmount', 'modules', 'mount-ro', 'mtab', 'net.lo', 'procfs', 'root', 'savecache', 'swap', 'sysctl', 'termencoding', 'udev' ] self.soldefault = [ 'lrc:/etc/rc2_d/S10lu', 'lrc:/etc/rc2_d/S20sysetup', 'lrc:/etc/rc2_d/S40llc2', 'lrc:/etc/rc2_d/S42ncakmod', 'lrc:/etc/rc2_d/S47pppd', 'lrc:/etc/rc2_d/S70uucp', 'lrc:/etc/rc2_d/S72autoinstall', 'lrc:/etc/rc2_d/S73cachefs_daemon', 'lrc:/etc/rc2_d/S81dodatadm_udaplt', 'lrc:/etc/rc2_d/S89PRESERVE', 'lrc:/etc/rc2_d/S89bdconfig', 'lrc:/etc/rc2_d/S91ifbinit', 'lrc:/etc/rc2_d/S91jfbinit', 'lrc:/etc/rc2_d/S94ncalogd', 'lrc:/etc/rc2_d/S98deallocate', 'lrc:/etc/rc3_d/S16boot_server', 'lrc:/etc/rc3_d/S50apache', 'lrc:/etc/rc3_d/S52imq', 'lrc:/etc/rc3_d/S80mipagent', 'lrc:/etc/rc3_d/S84appserv', 'svc:/system/svc/restarter:default', 'svc:/system/installupdates:default', 'svc:/network/pfil:default', 'svc:/network/tnctl:default', 'svc:/network/loopback:default', 'svc:/system/filesystem/root:default', 'svc:/system/scheduler:default', 'svc:/network/physical:default', 'svc:/system/identity:node', 'svc:/system/boot-archive:default', 'svc:/system/filesystem/usr:default', 'svc:/system/keymap:default', 'svc:/system/device/local:default', 'svc:/system/filesystem/minimal:default', 'svc:/system/resource-mgmt:default', 'svc:/system/name-service-cache:default', 'svc:/system/rmtmpfiles:default', 'svc:/system/identity:domain', 'svc:/system/coreadm:default', 'svc:/system/cryptosvc:default', 'svc:/system/sysevent:default', 'svc:/system/device/fc-fabric:default', 'svc:/milestone/devices:default', 'svc:/system/sar:default', 'svc:/system/picl:default', 'svc:/network/ipsec/ipsecalgs:default', 'svc:/network/ipsec/policy:default', 'svc:/milestone/network:default', 'svc:/network/initial:default', 'svc:/network/service:default', 'svc:/network/dns/client:default', 'svc:/milestone/name-services:default', 'svc:/system/pkgserv:default', 'svc:/network/iscsi/initiator:default', 'svc:/system/manifest-import:default', 'svc:/system/patchchk:default', 'svc:/milestone/single-user:default', 'svc:/system/filesystem/local:default', 'svc:/network/shares/group:default', 'svc:/system/cron:default', 'svc:/system/sysidtool:net', 'svc:/system/boot-archive-update:default', 'svc:/network/routing-setup:default', 'svc:/network/rpc/bind:default', 'svc:/system/sysidtool:system', 'svc:/milestone/sysconfig:default', 'svc:/system/sac:default', 'svc:/system/filesystem/autofs:default', 'svc:/network/inetd:default', 'svc:/application/font/fc-cache:default', 'svc:/system/utmp:default', 'svc:/system/console-login:default', 'svc:/system/dumpadm:default', 'svc:/system/system-log:default', 'svc:/network/ssh:default', 'svc:/network/sendmail-client:default', 'svc:/network/smtp:sendmail', 'svc:/application/management/wbem:default', 'svc:/system/postrun:default', 'svc:/network/rpc/gss:default', 'svc:/application/font/stfsloader:default', 'svc:/network/rpc/cde-calendar-manager:default', 'svc:/network/rpc/cde-ttdbserver:tcp', 'svc:/network/rpc/smserver:default', 'svc:/network/security/ktkt_warn:default', 'svc:/network/rpc-100235_1/rpc_ticotsord:default', 'svc:/system/filesystem/volfs:default', 'svc:/milestone/multi-user:default', 'svc:/system/boot-config:default', 'svc:/system/fmd:default', 'svc:/milestone/multi-user-server:default', 'svc:/system/zones:default', 'svc:/system/fpsd:default', 'svc:/system/basicreg:default', 'svc:/application/stosreg:default', 'svc:/application/graphical-login/cde-login:default', 'svc:/application/cde-printinfo:default', 'svc:/application/autoreg:default', 'svc:/system/webconsole:console', 'svc:/network/cswpuppetd:default' ] self.systemddefault = [ 'accounts-daemon.service', 'after-local.service', 'alsa-restore.service', 'alsasound', 'alsa-store.service', 'alsa-state.service', 'alsa-state.service', 'anacron.service', '[email protected]', 'arp-ethers.service', 'atd.service', 'auditd.service', 'auth-rpcgss-module.service', 'brandbot.service', 'chronyd.service', 'chronyd', 'chrony.service', 'chrony', 'colord.service', 'cron', 'cron.service', 'crond.service', 'cups', 'cups.service', 'debian-fixup.service', 'dbus.service', 'dbus', 'dbus-daemon.service', 'dbus-broker.service', 'display-manager.service', 'dm-event.service', 'dmraid-activation.service', 'dnf-makecache.service', 'dracut-shutdown.service', 'emergency.service', 'encase.service', 'fedora-autorelabel-mark.service', 'fedora-autorelabel.service', 'fedora-configure.service', 'fedora-import-state.service', 'fedora-loadmodules.service', 'fedora-readonly.service', 'fedora-storage-init-late.service', 'fedora-storage-init.service', 'fedora-wait-storage.service', 'firewalld.service', 'gdm.service', 'gdm3.service', 'gdm3', '*****@*****.**', '*****@*****.**', '*****@*****.**', '*****@*****.**', '[email protected]', 'getty-static.service', 'halt.service', 'localfs.service', 'ip6tables.service', 'iptables.service', 'irqbalance.service', 'iprdump.service', 'iprinit.service', 'iprupdate.service', 'iscsi-shutdown.service', 'kmod', 'kmod-static-nodes.service', 'ksm.service', 'ksmtuned.service', 'ldconfig.service', 'lightdm.service', 'lightdm', 'lvm2-activation.service', 'lvm2-activation-early.service', 'lvm2-monitor.service', 'mcelog', 'mcelog.service', 'mdmonitor-takeover.service', 'mdmonitor.service', 'named-setup-rndc.service', 'netconsole.service', 'network.service', 'networking.service', 'networking', 'network-manager', 'network-manager.service', 'NetworkManager.service', 'NetworkManager', 'network', 'netcf-transaction.service', 'nfs-blkmap.service', 'nfs-config.service', 'nfs-idmapd.service', 'nfs-mountd.service', 'nfs-utils.service', 'nessusagent.service', 'nfs-lock.service', 'nslcd.service', 'ntpd.service', 'ntpdate.service', 'ntp.service', 'ntp', 'ntpd', 'ondemand.service', 'osad.service', 'pcscd', 'pcscd.service', 'postfix', 'polkit.service', 'purge-kernels.service', 'plymouth-quit-wait.service', 'plymouth-quit.service', 'plymouth-read-write.service', 'plymouth-start.service', 'polkit.service', 'postfix.service', '*****@*****.**', 'poweroff.service', 'prefdm.service', 'procps.service', 'procps', 'psacct.service', 'psacct', 'rc-local.service', 'rc.local', 'reboot.service', 'remount-rootfs.service', 'rescue.service', 'resolvconf.service', 'resolvconf', 'rhel-autorelabel', 'rhel-configure.service', 'rhel-import-state.service', 'rhel-loadmodules.service', 'rhel-readonly.service', 'rhnsd.service', 'rhel-autorelabel-mark.service', 'rhel-autorelabel.service', 'rhsmcertd.service', 'rsyslog.service', 'rsyslog', 'rsyslogd', 'rtkit-daemon.service', 'rhnsd', 'rngd.service', 'rpcbind.service', 'rpc-gssd.service', 'rpc-statd-notify.service', 'rpc-statd.service', 'rpc-svcgssd.service', 'sddm.service', 'sddm', 'sendmail.service', 'sm-client.service', 'spice-vdagentd.service', 'ssh', 'ssh.service', 'sshd', 'sshd.service', 'sshd-keygen.service', 'stonix-mute-mic.service', 'stonix-mute-mic', 'SuSEfirewall2.service', 'SuSEfirewall2_init.service', 'syslog.service', 'systemd-firstboot.service', 'systemd-fsck-root.service', 'systemd-hostnamed.service', 'systemd-hwdb-update.service', 'systemd-journal-catalog-update.service', 'systemd-machine-id-commit.service', 'systemd-setup-dgram-qlen.service', 'systemd-sysusers.service', 'systemd-update-done.service', 'systemd-journal-flush.service', 'systemd-random-seed.service', 'systemd-tmpfiles-setup-dev.service', 'systemd-udev-root-symlink.service', 'systemd-udev-settle.service', 'systemd-udev-trigger.service', 'systemd-udev.service', 'systemd-udevd.service', 'systemd-update-utmp.service', 'sssd.service', 'system-setup-keyboard.service', 'smartd.service', 'sysstat.service', 'systemd-fsck-root.service', 'systemd-journal-flush.service', 'systemd-random-seed.service', 'systemd-reboot.service', 'systemd-tmpfiles-setup-dev.service', 'systemd-udev-settle.service', 'systemd-udev-trigger.service', 'systemd-udevd.service', 'systemd-update-utmp.service', 'systemd-ask-password-console.service', 'systemd-ask-password-plymouth.service', 'systemd-ask-password-wall.service', 'systemd-binfmt.service', 'systemd-initctl.service', 'systemd-journald.service', 'systemd-logind.service', 'systemd-modules-load.service', 'systemd-networkd-wait-online.service', 'systemd-networkd.service', 'systemd-random-seed-load.service', 'systemd-random-seed-save.service', 'systemd-readahead-collect.service', 'systemd-readahead-done.service', 'systemd-readahead-replay.service', 'systemd-remount-api-vfs.service', 'systemd-remount-fs.service', 'systemd-shutdownd.service', 'systemd-sysctl.service', 'systemd-tmpfiles-clean.service', 'systemd-tmpfiles-setup.service', 'systemd-update-utmp-runlevel.service', 'systemd-update-utmp-shutdown.service', 'systemd-user-sessions.service', 'systemd-vconsole-setup.service', 'systemd-resolved.service', 'systemd-resolved', 'dbus-org.freedesktop.resolve1.service' 'tcsd.service', 'tuned.service', 'udev', 'udev.service', 'udev-settle.service', 'udev-trigger.service', 'udev.service', 'udev-finish.service', 'udev-finish', 'udisks2.service', 'ufw.service', 'upower.service', 'unbound-anchor.service', 'urandom.service', 'urandom', 'virtlockd.service', 'wicked.service', 'xdm', 'xdm.service', 'YaST2-Second-Stage.service', 'ypbind.service', 'sysstat-collect.service', 'sysstat-summary.service' ] datatype = 'bool' key = 'MINIMIZESVCS' instructions = "To prevent STONIX from disabling all non-white-listed services, set the value of MINIMIZESVCS to False." default = True self.minimizeci = self.initCi(datatype, key, instructions, default) datatype2 = 'list' key2 = 'SERVICEENABLE' instructions2 = """This list contains services that are permitted to \ run on this platform. If you need to run a service not currently in this \ list, add the service to the list and STONIX will not disable it. List \ elements should be space separated.""" self.serviceTarget = "" self.logger.log(LogPriority.DEBUG, "Starting platform detection") self.environ.setsystemtype() systemtype = self.environ.getsystemtype() self.ch = CommandHelper(self.logger) if systemtype == "systemd": self.logger.log(LogPriority.DEBUG, [ 'MinimizeServices.__init__', "systemctl found. Using systemd white list" ]) self.svcslistci = self.initCi(datatype2, key2, instructions2, self.systemddefault) elif self.environ.getosfamily() == 'linux': self.logger.log(LogPriority.DEBUG, [ 'MinimizeServices.__init__', "Linux OS found. Using Linux default white list" ]) self.svcslistci = self.initCi(datatype2, key2, instructions2, self.linuxdefault) elif self.environ.getosfamily() == 'solaris': self.logger.log(LogPriority.DEBUG, [ 'MinimizeServices.__init__', "Solaris OS found. Using Solaris list" ]) self.svcslistci = self.initCi(datatype2, key2, instructions2, self.soldefault) elif self.environ.getosfamily() == 'freebsd': self.logger.log(LogPriority.DEBUG, [ 'MinimizeServices.__init__', "FreeBSD OS found. Using BSD white list" ]) self.svcslistci = self.initCi(datatype2, key2, instructions2, self.bsddefault) else: self.logger.log(LogPriority.DEBUG, [ 'MinimizeServices.__init__', "Detection fell through. Return from ENV:" + self.environ.getosfamily() ]) self.svcslistci = self.initCi(datatype2, key2, instructions2, self.linuxdefault) #what's in stonix.conf x = self.svcslistci.getcurrvalue() #self.systemddefault - x y = [li for li in self.systemddefault if li not in x] if systemtype == "systemd": #the set of what's in stonix.conf added to what's in self.systemddefault that isn't in stonix.conf self.svcslistci.updatecurrvalue(x + y) def whiteListAliases(self): """ add all alias names of white listed services to white list :return: void """ dirs = [ "/usr/share/dbus-1/services/*", "/usr/share/dbus-1/system-services/*", "/etc/systemd/system/*", "/usr/lib/systemd/system/*" ] grep_alias_cmd = "/bin/grep -rih Alias= " aliaslines = [] services = self.svcslistci.getcurrvalue() for d in dirs: self.ch.executeCommand(grep_alias_cmd + d) output = self.ch.getOutput() retcode = self.ch.getReturnCode() if retcode == 0: aliaslines += output for al in aliaslines: try: aliases = al.split("=") aliases = aliases[1].split() for i in range(len(aliases)): if ".target" in aliases[i]: continue else: services.append(aliases[i]) except IndexError: continue services = list(set(services)) self.svcslistci.updatecurrvalue(services) def report(self): '''Report on whether any services not in the baseline or configured set are running. :return: self.compliant :rtype: bool ''' self.compliant = True self.detailedresults = "" unauthorizedservices = [] authorizedservices = [] if self.environ.getsystemtype() == "systemd": self.whiteListAliases() try: servicelist = self.servicehelper.listServices() allowedlist = self.svcslistci.getcurrvalue() corelist = self.svcslistci.getdefvalue() self.logger.log(LogPriority.DEBUG, "AllowedList: " + str(allowedlist)) for service in servicelist: if service in allowedlist or re.search('user@[0-9]*.service', service): if service in allowedlist: authorizedservices.append(str(service)) # [email protected] are user managers started by PAM # on Linux. They are GRAS (generally regarded as safe) if service not in corelist and \ not re.search('user@[0-9]*.service', service): self.logger.log(LogPriority.DEBUG, 'Non-core service running: ' + service) else: if self.servicehelper.auditService( service, serviceTarget=self.serviceTarget): self.compliant = False unauthorizedservices.append(str(service)) self.logger.log( LogPriority.DEBUG, "The following white-listed services were found and will NOT be disabled:\n+ " + "\n+ ".join(authorizedservices) + "\n") if self.compliant: self.detailedresults += 'No unauthorized services were detected' self.targetstate = 'configured' else: self.detailedresults += "Unauthorized running services detected:\n- " + "\n- ".join( unauthorizedservices) + "\n" self.targetstate = 'notconfigured' 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.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): '''Disable all services not in the 'enable services' list. :return: self.rulesuccess :rtype: bool ''' self.detailedresults = "" self.rulesuccess = True changes = [] if self.minimizeci.getcurrvalue(): try: servicelist = self.servicehelper.listServices() allowedlist = self.svcslistci.getcurrvalue() # make sure you're not dealing with any return garbage # in the comparisons below allowedlist = [i.rstrip() for i in allowedlist] servicelist = [i.rstrip() for i in servicelist] for service in servicelist: if service in allowedlist: continue elif re.search('user@[0-9]*.service', service): # these are systemd usermanagers started by PAM # they are OK. continue else: running = self.servicehelper.auditService( service, serviceTarget=self.serviceTarget) if running and service not in self.specials: changes.append(service) self.servicehelper.disableService( service, serviceTarget=self.serviceTarget) mytype = 'command' mystart = [] myend = changes myid = '0013001' event = { 'eventtype': mytype, 'startstate': mystart, 'endstate': myend } self.statechglogger.recordchgevent(myid, event) except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += 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 undo(self): '''Attempt to reset the service state for all the services that we changed back to their original states. @author: Dave Kennel ''' self.targetstate = 'notconfigured' try: event1 = self.statechglogger.getchgevent('0013001') for service in event1['endstate']: self.servicehelper.enableService( service, serviceTarget=self.serviceTarget) except (IndexError, KeyError): self.logger.log( LogPriority.DEBUG, ['MinimizeServices.undo', "EventID 0013001 not found"]) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.detailedresults = 'MinimizeServices: ' self.detailedresults = self.detailedresults + \ traceback.format_exc() self.rulesuccess = False self.logger.log(LogPriority.ERROR, ['MinimizeServices.fix', self.detailedresults]) return False self.report() if self.currstate == self.targetstate: self.detailedresults = 'MinimizeServices: Changes ' + \ 'successfully reverted' return True
class DisableInactiveAccounts(Rule): '''This rule will set the global policy for inactive accounts so that any account not accessed/used within 35 days will be automatically disabled. ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 4 self.rulename = 'DisableInactiveAccounts' self.compliant = True self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = True self.sethelptext() self.guidance = ['CNSSI 1253', 'DISA STIG'] datatype = 'bool' key = 'DISABLEINACTIVEACCOUNTS' instructions = 'To disable this rule, set the value of ' + \ 'DisableInactiveAccounts to False.' default = True self.ci = self.initCi(datatype, key, instructions, default) self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] }, 'fisma': 'high' } self.initobjs() def initobjs(self): '''initialize objects for use by this class :returns: void @author: Breen Malmberg ''' self.cmdhelper = CommandHelper(self.logger) def getEnabledAccounts(self): '''return a list of all currently enabled accounts :returns: enabledaccounts :rtype: list @author: Breen Malmberg ''' allaccounts = [] enabledaccounts = [] getallaccounts = "/usr/bin/dscl . -list /Users" getenabled = "/usr/bin/pwpolicy -u {username} --get-effective-policy" try: self.cmdhelper.executeCommand(getallaccounts) allaccounts = self.cmdhelper.getOutput() if allaccounts: for acc in allaccounts: self.cmdhelper.executeCommand( getenabled.replace("{username}", acc)) outputstr = self.cmdhelper.getOutputString() if re.search("isDisabled=false", outputstr, re.IGNORECASE): enabledaccounts.append(acc) except Exception: raise return enabledaccounts def report(self): '''get a list of users determine each user's password last set time determine if each user is inactive :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [EXCLUDEACCOUNTS] if not self.checkConsts(constlist): self.compliant = False self.detailedresults = "\nPlease ensure that the constant: EXCLUDEACCOUNTS, 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 self.compliant = True self.detailedresults = '' # do not check any accounts with these regex terms found in them # these are accounts which would not have the passwordlastsettime key # in their accountpolicydata, and accounts which we do not want to # disable accexcludere = ['_', 'nobody', 'daemon', 'root'] for account in EXCLUDEACCOUNTS: accexcludere.append(account) self.inactiveaccounts = [] try: userlist = self.getEnabledAccounts() if self.cmdhelper.getReturnCode() != 0: self.rulesuccess = False self.compliant = False self.detailedresults += '\nThere was a problem retrieving ' + \ 'the list of users on this system.' userlistnew = [] for user in userlist: removeuser = False for element in accexcludere: if re.search(element, user): removeuser = True if not removeuser: userlistnew.append(user) for user in userlistnew: inactivedays = self.getinactivedays(user.strip()) if int(inactivedays) > 35: self.compliant = False self.detailedresults += '\nThe user account "' + \ user.strip() + \ '" has been inactive for more than 35 days.' self.inactiveaccounts.append(user.strip()) elif int(inactivedays) > 0 and int(inactivedays) <= 35: daysleft = 35 - int(inactivedays) self.detailedresults += '\nThe user account "' + \ user.strip() + '" has been inactive for ' + \ str(inactivedays) + ' days. You have ' + \ str(daysleft) + \ ' days left before this account will be disabled.' self.logger.log( LogPriority.DEBUG, '\nThe user account "' + user.strip() + '" has been inactive for ' + str(inactivedays) + ' days. You have ' + str(daysleft) + ' days left before this ' + 'account will be disabled.') else: self.detailedresults += '\nThe user account "' + \ user.strip() + '" is not inactive. No problems.' 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 getinactivedays(self, user): '''Get and return the number of days a given user account has been inactive :param user: string the name of the account to check @author: Breen Malmberg :returns: inactivedays :rtype: int ''' inactivedays = 0 date_format = "%a %b %d %H:%M:%S %Y" try: if not user: self.logger.log( LogPriority.DEBUG, "The given value for " + "parameter user was None, or blank!") return inactivedays if not isinstance(user, str): self.logger.log( LogPriority.DEBUG, "The given value for parameter user was not " + "of the correct type (int)!") return inactivedays self.cmdhelper.executeCommand('/usr/bin/dscl . readpl /Users/' + user + ' accountPolicyData ' + 'passwordLastSetTime') epochchangetimestr = self.cmdhelper.getOutputString() retcode = self.cmdhelper.getReturnCode() outstr = self.cmdhelper.getOutputString() if retcode != 0: if retcode == 181: # this is the mac os x error code when a plist path does not exist if re.search("No such plist path: passwordLastSetTime", outstr, re.IGNORECASE): self.detailedresults += "The local account: " + str( user ) + " has never had a password set for it! We will now disable this local account on this machine." self.logger.log( LogPriority.DEBUG, "The local user account: " + str(user) + " had no password for it. STONIX will disable it now." ) inactivedays = 9999 # this will ensure it gets added to the list of accounts to disable return inactivedays else: self.detailedresults += '\nThere was an issue reading ' + \ user + '\'s accountPolicyData passwordLastSetTime' self.compliant = False return inactivedays epochchangetimelist = epochchangetimestr.split(':') epochchangetimestropr = epochchangetimelist[1].strip() epochchangetime = Decimal(epochchangetimestropr) pwchangedate = time.ctime(epochchangetime) a = datetime.strptime(pwchangedate, date_format) now = time.ctime() b = datetime.strptime(now, date_format) diff = b - a if int(diff.days) > 180: inactivedays = int(diff.days) - 180 except Exception: raise return inactivedays def fix(self): '''check if ci is enabled if it is, run fix actions for this rule if not, report that it is disabled :returns: fixsuccess :rtype: bool @author: Breen Malmberg ''' # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [EXCLUDEACCOUNTS] if not self.checkConsts(constlist): success = False self.formatDetailedResults("fix", success, self.detailedresults) return success # defaults fixsuccess = True self.detailedresults = '' self.iditerator = 0 disabledaccounts = [] try: if self.ci.getcurrvalue(): if self.inactiveaccounts: for user in self.inactiveaccounts: self.cmdhelper.executeCommand( '/usr/bin/pwpolicy -disableuser -u ' + user) errout = self.cmdhelper.getErrorString() rc = self.cmdhelper.getReturnCode() if rc != 0: self.detailedresults += '\nThere was an issue trying to disable user account: ' + user self.logger.log(LogPriority.DEBUG, errout) fixsuccess = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { 'eventtype': 'commandstring', 'command': '/usr/bin/pwpolicy -enableuser -u ' + user } self.statechglogger.recordchgevent(myid, event) disabledaccounts.append(user) self.logger.log( LogPriority.DEBUG, "Disabling user account: " + str(user) + " ...") if disabledaccounts: self.detailedresults += "\nDisabled the following accounts: " + "\n- ".join( disabledaccounts) else: self.detailedresults += '\nNo inactive accounts detected. No accounts were disabled.' else: self.detailedresults += '\nThe CI for this 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", fixsuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return fixsuccess
class SHchkconfig(ServiceHelperTemplate): """ SHchkconfig is the Service Helper for systems using the chkconfig command to configure services. (RHEL up to 6, SUSE, Centos up to 6, etc) @author: David Kennel """ def __init__(self, environment, logdispatcher): """ Constructor """ super(SHchkconfig, self).__init__(environment, logdispatcher) self.environ = environment self.logger = logdispatcher self.initobjs() self.localize() def initobjs(self): """ initialize class objects @return: """ self.ch = CommandHelper(self.logger) def localize(self): """ set base command paths (chkconfig and service) based on OS @return: """ self.svc = "" self.chk = "" chk_paths = ["/sbin/chkconfig", "/usr/sbin/chkconfig"] for cp in chk_paths: if os.path.exists(cp): self.chk = cp break service_paths = ["/sbin/service", "/usr/sbin/service"] for sp in service_paths: if os.path.exists(sp): self.svc = sp break if not self.svc: raise IOError("Could not locate the service utility on this system") if not self.chk: raise IOError("Could not locate the chkconfig utility on this system") def startService(self, service, **kwargs): """ start a given service @param service: string; name of service @param kwargs: @return: success @rtype: bool @author: Breen Malmberg """ success = True self.ch.executeCommand(self.svc + " " + service + " start") retcode = self.ch.getReturnCode() if retcode != 0: success = False if not self.isRunning(service): success = False return success def stopService(self, service, **kwargs): """ stop a given service @param service: @param kwargs: @return: success @rtype: bool @author: Breen Malmberg """ success = True self.ch.executeCommand(self.svc + " " + service + " stop") retcode = self.ch.getReturnCode() if retcode != 0: success = False if self.isRunning(service): success = False return success def disableService(self, service, **kwargs): """ Disables the specified service and stops it if it is running @param service: string; Name of the service to be disabled @return: bool @author: David Kennel @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ disabled = True self.ch.executeCommand(self.chk + " " + service + " off") retcode = self.ch.getReturnCode() if retcode != 0: disabled = False if self.auditService(service): disabled = False if not self.stopService(service): disabled = False return disabled def enableService(self, service, **kwargs): """ Enables a service and starts it if it is not running as long as we are not in install mode @param service: string; Name of the service to be enabled @return: enabled @rtype: bool @author: David Kennel @change: Breen Malmberg - 04/10/2019 - """ enabled = True self.ch.executeCommand(self.chk + " " + service + " on") retcode = self.ch.getReturnCode() if retcode != 0: enabled = False if not self.auditService(service): enabled = False if not self.startService(service): enabled = False return enabled def auditService(self, service, **kwargs): """ Checks the status of a service and returns a bool indicating whether or not the service is enabled @param service: string; Name of the service to audit @return: enabled @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ enabled = True if not self.audit_chkconfig_service(service): enabled = False return enabled def audit_chkconfig_service(self, service): """ uses the chkconfig command to check if a given service is enabled or not @return: enabled @rtype: bool @author: Breen Malmberg """ enabled = True self.ch.executeCommand(self.chk + " --list " + service) retcode = self.ch.getReturnCode() if retcode != 0: enabled = False self.logger.log(LogPriority.DEBUG, "Failed to get status of service: " + service) return enabled output = self.ch.getOutputString() if not re.search(":on", output): enabled = False return enabled def isRunning(self, service, **kwargs): """ Check to see if a service is currently running. @param service: string; Name of the service to check @return: running @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ running = True # see: http://refspecs.linuxbase.org/LSB_3.1.0/LSB-generic/LSB-generic/iniscrptact.html success_codes = [0, 1, 2, 3] self.ch.executeCommand(self.svc + " " + service + " status") retcode = self.ch.getReturnCode() if retcode not in success_codes: running = False self.logger.log(LogPriority.DEBUG, "Command error while getting run status of service: " + service) return running outputlines = self.ch.getOutput() # need to parse for either sysv or systemd output if not self.parse_running(outputlines): running = False return running def parse_running(self, outputlines): """ check whether given service is running, with the service command this is the older (classic) systemV case @param outputlines: list; list of strings to search @return: running @rtype: bool @author: Breen Malmberg """ running = True systemctl_locations = ["/usr/bin/systemctl", "/bin/systemctl"] if any(os.path.exists(sl) for sl in systemctl_locations): searchterms = ["Active:\s+inactive", "Active:\s+unknown"] else: searchterms = ["is stopped", "hook is not installed", "is not running"] for line in outputlines: if any(re.search(st, line) for st in searchterms): running = False break return running def reloadService(self, service, **kwargs): """ Reload (HUP) a service so that it re-reads it's config files. Called by rules that are configuring a service to make the new configuration active. @param service: string; Name of service to be reloaded @return: reloaded @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ reloaded = True # force-reload: cause the configuration to be reloaded if the service supports this, # otherwise restart the service if it is running self.ch.executeCommand(self.svc + " " + service + " force-reload") retcode = self.ch.getReturnCode() if retcode != 0: reloaded = False self.logger.log(LogPriority.DEBUG, "Failed to reload service: " + service) return reloaded def listServices(self, **kwargs): """ Return a list containing strings that are service names. @return: service_list @rtype: list @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ service_list = [] self.ch.executeCommand(self.chk + " --list") outputlines = self.ch.getOutput() for line in outputlines: try: service_list.append(line.split()[0]) except IndexError: pass return service_list def getStartCommand(self, service): ''' retrieve the start command. Mostly used by event recording @return: string - start command @author: dwalker ''' return self.svc + " " + service + " start" def getStopCommand(self, service): ''' retrieve the stop command. Mostly used by event recording @return: string - stop command @author: dwalker ''' return self.svc + " " + service + " stop" def getEnableCommand(self, service): ''' retrieve the enable command. Mostly used by event recording @return: string - enable command @author: dwalker ''' return self.chk + " " + service + " on" def getDisableCommand(self, service): ''' retrieve the start command. Mostly used by event recording @return: string - disable command @author: dwalker ''' return self.chk + " " + service + " off"
class Freebsd(object): '''The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. :version: :author:Derek T Walker 08-06-2012''' def __init__(self,logger): self.logger = logger self.detailedresults = "" self.ch = CommandHelper(self.logger) self.install = "/usr/sbin/pkg_add -r -f " self.remove = "/usr/sbin/pkg_delete " ############################################################################### def installpackage(self, package): ''' Install a package. Return a bool indicating success or failure. @param string package : Name of the package to be installed, must be recognizable to the underlying package manager. @return bool : @author''' try: installed = False # retval = call(self.install + package,stdout=None,shell=True) self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: # if retval == 0: self.detailedresults += package + " pkg installed successfully" installed = True else: self.detailedresults += package + " pkg not able to install" self.logger.log(LogPriority.INFO, self.detailedresults) return installed except(KeyboardInterrupt,SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) ############################################################################### def removepackage(self, package): ''' Remove a package. Return a bool indicating success or failure. @param string package : Name of the package to be removed, must be recognizable to the underlying package manager. @return : bool @author''' try: self.detailedresults = "" removed = False stringToMatch = package + "(.*)" self.ch.executeCommand(["/usr/sbin/pkg_info"]) output = self.ch.getOutput() # temp = Popen(["/usr/sbin/pkg_info"],stdout=PIPE,shell=True) # details = temp.stdout.readlines() # temp.stdout.close() for cell in output: cell2 = cell.split(" ") if re.search(stringToMatch,cell2[0]): retval = call(self.remove + cell2[0],stdout=None,shell=True) if retval == 0: self.detailedresults += package + " pkg removed \ successfully" removed = True else: self.detailedresults += package + " pkg not able\ to be removed" if not self.detailedresults: self.detailedresults += package + " pkg not found to remove" self.logger.log(LogPriority.INFO, self.detailedresults) return removed except(KeyboardInterrupt,SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) ############################################################################### def checkInstall(self, package): '''Check the installation status of a package. Return a bool; True if the package is installed. @param string package : Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return : bool @author''' try: self.detailedresults = "" present = False stringToMatch = package +"(.*)" self.ch.executeCommand(["/usr/sbin/pkg_info"]) output = self.ch.getOutput() # temp = Popen(["/usr/sbin/pkg_info"],stdout=PIPE,shell=False) # info = temp.stdout.readlines() # temp.stdout.close() for cell in output: cell2 = cell.split(" ") if re.search(stringToMatch,cell2[0]): self.detailedresults += package + " pkg found" present = True if not self.detailedresults: self.detailedresults += package + " pkg not found" self.logger.log(LogPriority.INFO, self.detailedresults) return present except(KeyboardInterrupt,SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.INFO, self.detailedresults) ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove
class SHsystemctl(ServiceHelperTemplate): """ SHsystemctl is the Service Helper for systems using the systemctl command to configure services. (Fedora and future RHEL and variants) """ def __init__(self, environment, logdispatcher): """ Constructor """ super(SHsystemctl, self).__init__(environment, logdispatcher) self.environment = environment self.logdispatcher = logdispatcher self.ch = CommandHelper(self.logdispatcher) self.localize() def localize(self): """ @return: """ systemctl_paths = ["/usr/bin/systemctl", "/bin/systemctl"] self.sysctl = "" for sp in systemctl_paths: if os.path.exists(sp): self.sysctl = sp break if not self.sysctl: raise IOError("Cannot find systemctl utility on this system!") # do not attempt to manipulate any service which has a status in this list self.handsoff = ["static", "transient", "generated", "masked", "masked-runtime"] def disableService(self, service, **kwargs): """ Disables the service and terminates it if it is running. @param service: string; Name of the service to be disabled @return: disabled @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ disabled = True self.ch.executeCommand(self.sysctl + " disable " + service) retcode = self.ch.getReturnCode() if retcode != 0: disabled = False errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) if not self.stopService(service): disabled = False return disabled def enableService(self, service, **kwargs): """ Enables a service and starts it if it is not running as long as we are not in install mode @param service: string; Name of the service to be disabled @return: enabled @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; logging edit """ enabled = True if self.getServiceStatus(service, **kwargs) in self.handsoff: enabled = False return enabled self.ch.executeCommand(self.sysctl + " enable " + service) retcode = self.ch.getReturnCode() if retcode != 0: enabled = False errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) return enabled def auditService(self, service, **kwargs): """ Checks the status of a service and returns a bool indicating whether or not the service is configured to run or not. @param service: string; Name of the service to audit @return: enabled @rtype: bool """ enabled = False self.ch.executeCommand(self.sysctl + " is-enabled " + service) if self.ch.findInOutput("not a native service"): self.logdispatcher.log(LogPriority.DEBUG, "Attempted to audit a non-systemd service with systemctl commands") return enabled elif self.ch.findInOutput("enabled"): enabled = True return enabled def isRunning(self, service, **kwargs): """ Check to see if a service is currently running. The enable service uses this so that we're not trying to start a service that is already running. @param service: string; name of service to check @return: running @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; debug logging edit """ running = True inactive_keys = ["inactive", "unknown"] self.ch.executeCommand(self.sysctl + " is-active " + service) for k in inactive_keys: if self.ch.findInOutput(k): running = False return running def reloadService(self, service, **kwargs): """ Reload (HUP) a service so that it re-reads it's config files. Called by rules that are configuring a service to make the new configuration active. @param service: string; Name of the service to reload @return: success @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; doc string edit; debug logging edit """ success = True if self.getServiceStatus(service, **kwargs) in self.handsoff: success = False return success self.ch.executeCommand(self.sysctl + " reload-or-restart " + service) retcode = self.ch.getReturnCode() if retcode != 0: success = False errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) if not self.isRunning(service): success = False return success def listServices(self, **kwargs): """ Return a list containing strings that are service names. @return: service_list @rtype: bool @author: ??? @change: Breen Malmberg - 04/10/2019 - method refactor; debug logging edit; doc string edit """ service_list = [] # list all installed, service-type service units on the system self.ch.executeCommand(self.sysctl + " -a -t service --no-pager list-unit-files") output = self.ch.getOutput() for line in output: try: service_list.append(line.split()[0]) except IndexError: pass except: raise if not service_list: errmsg = self.ch.getErrorString() if errmsg: self.logdispatcher.log(LogPriority.DEBUG, errmsg) return service_list def startService(self, service, **kwargs): """ start given service @param service: @param kwargs: @return: started @rtype: bool @author: Breen Malmberg """ started = True if self.getServiceStatus(service, **kwargs) in self.handsoff: started = False return started self.ch.executeCommand(self.sysctl + " start " + service) retcode = self.ch.getReturnCode() if retcode != 0: started = False errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) return started def stopService(self, service, **kwargs): """ stop given service @param service: @param kwargs: @return: stopped @rtype: bool @author: Breen Malmberg """ stopped = True if not self.isRunning(service): return stopped # nothing to do if self.getServiceStatus(service, **kwargs) in self.handsoff: stopped = False return stopped else: self.ch.executeCommand(self.sysctl + " stop " + service) retcode = self.ch.getReturnCode() if retcode != 0: stopped = False errmsg = self.ch.getErrorString() self.logdispatcher.log(LogPriority.DEBUG, errmsg) return stopped def getServiceStatus(self, service, **kwargs): """ return is-enabled status output possible return values: enabled enabled-runtime linked linked-runtime masked masked-runtime static indirect disabled generated transient unknown (custom status defined in STONIX; not generated by systemctl) @param service: @param kwargs: @return: status @rtype: string @author: Breen Malmberg """ status = "" known_statuses = ["enabled", "enabled-runtime", "linked", "linked-runtime", "masked", "masked-runtime", "static", "indirect", "disabled", "generated", "transient"] self.ch.executeCommand(self.sysctl + " is-enabled " + service) output = self.ch.getOutputString() try: if len(output.split()) == 1: status = str(output) else: status = str(output.split()[0]) except IndexError: pass except: raise if status not in known_statuses: status = "unknown" elif not isinstance(status, basestring): status = "unknown" if status in self.handsoff: self.logdispatcher.log(LogPriority.DEBUG, "Status of service: " + service + " indicates it is either protected, required or immutable. Will not perform operation on this service!") return status def getStartCommand(self, service): ''' retrieve the start command. Mostly used by event recording @return: string - start command @author: dwalker ''' return self.sysctl + " start " + service def getStopCommand(self, service): ''' retrieve the stop command. Mostly used by event recording @return: string - stop command @author: dwalker ''' return self.sysctl + " stop " + service def getEnableCommand(self, service): ''' retrieve the enable command. Mostly used by event recording @return: string - enable command @author: dwalker ''' return self.sysctl + " enable " + service def getDisableCommand(self, service): ''' retrieve the start command. Mostly used by event recording @return: string - disable command @author: dwalker ''' return self.sysctl + " disable " + service
class DisableTouchID(Rule): '''classdocs''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 102 self.rulename = 'DisableTouchID' self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = True self.environ = environ self.sethelptext() self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } datatype = 'bool' key = 'DISABLETOUCHID' instructions = "To prevent this rule from running, set the value of DisableTouchID to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ['(None)'] self.initobjs() def initobjs(self): '''init objects used by this class @author: Breen Malmberg ''' self.ch = CommandHelper(self.logger) self.fixed = False def report(self): '''check status of touch id :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.compliant = True checkstrings = { "System Touch ID configuration:": False, "Operation performed successfully": False } bioutil = "/usr/bin/bioutil" reportcmd = bioutil + " -r -s" touchidinstalled = False try: if not os.path.exists(bioutil): self.logger.log(LogPriority.DEBUG, "The required bioutil utility was not found") self.compliant = False self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant self.ch.executeCommand(reportcmd) outlist = self.ch.getOutput() if not outlist: self.logger.log(LogPriority.DEBUG, "bioutil command returned no output!") self.compliant = False self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant for line in outlist: if re.search("Touch ID functionality", line, re.IGNORECASE): touchidinstalled = True if touchidinstalled: checkstrings = { "System Touch ID configuration:": False, "Touch ID functionality: 0": False, "Touch ID for unlock: 0": False, "Operation performed successfully": False } for line in outlist: for cs in checkstrings: if re.search(cs, line, re.IGNORECASE): checkstrings[cs] = True for cs in checkstrings: if not checkstrings[cs]: self.compliant = False self.detailedresults += "\nTouch ID is still enabled." except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.compliant = False self.detailedresults = traceback.format_exc() self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): '''turn off touch id functionality :returns: self.rulesuccess :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.rulesuccess = True fixcmd1 = "/usr/bin/bioutil -w -s -u 0" fixcmd2 = "/usr/bin/bioutil -w -s -f 0" checkstring = "Operation performed successfully" try: if self.ci.getcurrvalue(): self.ch.executeCommand(fixcmd1) outlist = self.ch.getOutput() if not outlist: self.logger.log(LogPriority.DEBUG, "bioutil command returned no output!") self.detailedresults += "\n" self.rulesuccess = False self.formatDetailedResults("fix", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess cmdsuccess = False for line in outlist: if re.search(checkstring, line, re.IGNORECASE): cmdsuccess = True if not cmdsuccess: self.rulesuccess = False self.detailedresults += "\nFailed to turn off Touch ID unlock" self.ch.executeCommand(fixcmd2) outlist = self.ch.getOutput() if not outlist: self.logger.log(LogPriority.DEBUG, "bioutil command returned no output!") self.rulesuccess = False self.formatDetailedResults("fix", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess cmdsuccess = False for line in outlist: if re.search(checkstring, line, re.IGNORECASE): cmdsuccess = True if not cmdsuccess: self.rulesuccess = False else: self.fixed = True else: self.detailedresults += "\nCI was not enabled. Nothing was fixed." self.logger.log( LogPriority.DEBUG, "User ran rule without CI enabled. Nothing was fixed.") except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults = traceback.format_exc() self.formatDetailedResults("fix", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def undo(self): '''reverse the fix actions which were applied :returns: success :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" success = True undocmd1 = "/usr/bin/bioutil -w -s -f 1" undocmd2 = "/usr/bin/bioutil -w -s -u 1" try: if self.fixed: self.ch.executeCommand(undocmd1) retval = self.ch.getReturnCode() if retval != 0: success = False self.detailedresults += "\nEncountered an error while trying to undo the fix actions." self.ch.executeCommand(undocmd2) retval = self.ch.getReturnCode() if retval != 0: success = False self.detailedresults += "\nEncountered an error while trying to undo the fix actions." if success: self.fixed = False else: self.detailedresults += "\nSystem already in original state. Nothing to undo." except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False success = False self.detailedresults = traceback.format_exc() self.formatDetailedResults("undo", success, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return success
class lanlMacInfo(): ''' lanlMacInfo gets information from the mac and LDAP to help set basi computer data for the mac this includes: ComputerName HostName LocalHostname asset_id (property number) endUserName (owner) @author: ekkehard ''' def __init__(self, logger): ''' initialize lanlMacInfo @author: ekkehard ''' self.logdispatch = logger # Make sure we have the full path for all commands self.ch = CommandHelper(logger) self.LANLAssetTagNVRAM = "" self.LANLAssetTagFilesystem = "" self.macAddressDictionary = {} self.dictionary = {} self.accuracyDictionary = {} self.dictionaryItem = None self.keys = None self.key = None self.keyIndexNumber = 0 self.keysNumberOf = 0 self.entries = -1 self.computerName = "" self.computerNameDiskUtility = "" self.hostName = "" self.hostNameDiskUtility = "" self.localHostname = "" self.localHostnameDiskUtility = "" self.endUsername = "" self.assetTag = "" # Make sure we have the full path for all commands self.ns = "/usr/sbin/networksetup" self.scutil = "/usr/sbin/scutil" self.jamf = "/usr/sbin/jamf" self.nvram = "/usr/sbin/nvram" self.ldap = "/usr/bin/ldapsearch" self.lanl_property_file = "/Library/Preferences/lanl_property_number.txt" # Initialize accuracy modules self.updateAssetTagAccuracy(True, 0, "", True) self.updateEndUserNameAccuracy(True, 0, "", True) self.updateComputerNameAccuracy(True, 0, "", True) # reset messages and initialize everyting self.messageReset() self.initializeLanlAssetTagNVRAM() self.initializeLanlAssetTagFilesystem() self.initializeDiskUtilityInfo() self.populateFromMac() self.determinAccuracy() def gotoFirstItemLDAP(self): ''' go to the first entry in the LDAP dictionary @author: ekkehard @return: dictionary entry ''' self.keyIndexNumber = 0 self.keys = sorted(self.dictionary.keys()) self.keysNumberOf = len(self.keys) if self.keysNumberOf > self.keyIndexNumber: self.key = self.keys[self.keyIndexNumber] self.dictinaryItem = self.dictionary[self.key] else: self.key = None self.dictinaryItem = None return self.dictinaryItem def gotoNextItemLDAP(self): ''' go to the next entry in the LDAP dictionary @author: ekkehard @return: dictionary entry ''' self.keyIndexNumber = self.keyIndexNumber + 1 self.keys = sorted(self.dictionary.keys()) self.keysNumberOf = len(self.keys) if (self.keysNumberOf - 1) < self.keyIndexNumber: self.keyIndexNumber = 0 self.dictinaryItem = None else: self.key = self.keys[self.keyIndexNumber] self.dictinaryItem = self.dictionary[self.key] return self.dictinaryItem def getCurrentItemLDAP(self): ''' get the current item in the LDAP dictionary @author: ekkehard @return: dictionary entry ''' return self.dictinaryItem def getComputerInfoCompliance(self): ''' see if all is set correctly @author: ekkehard @return: boolean - True of False ''' success = True compliant = True # Check computername messagestring = "ComputerName Confidence level of " + \ str(self.getSuggestedComputerNameConfidenceOnly()) + "%" if not(self.getSuggestedComputerNameConfidenceOnly() == 100): compliant = False messagestring = messagestring + " is less than 100%" messagestring = messagestring + "; ComputerName (" + \ self.getDiskUtilityComputerName() + ") and proposed ComputerName (" + \ self.getSuggestedComputerName() + ")" if not(self.getDiskUtilityComputerName() == self.getSuggestedComputerName()): compliant = False messagestring = messagestring + " are not equal;" else: messagestring = messagestring + " are equal;" if not compliant: messagestring = "- Not compliant; " + messagestring if not(self.computerNameAccuracyLevelWhy == "" ): messagestring = messagestring + " - " + self.computerNameAccuracyLevelWhy + ";" success = False else: messagestring = "- compliant; " + messagestring self.messageAppend(messagestring) # Check hostname compliant = True messagestring = "HostName confidence level of " + \ str(self.getSuggestedComputerNameConfidenceOnly()) + "%" if not(self.getSuggestedComputerNameConfidenceOnly() == 100): compliant = False messagestring = messagestring + " is less than 100%" messagestring = messagestring + "; HostName (" + \ self.getDiskUtilityHostName() + ") and proposed HostName (" + \ self.getSuggestedHostName() + ")" if not(self.getDiskUtilityHostName() == self.getSuggestedHostName()): compliant = False messagestring = messagestring + " are not equal;" else: messagestring = messagestring + " are equal;" if not compliant: messagestring = "- Not compliant; " + messagestring if not(self.computerNameAccuracyLevelWhy == "" ): messagestring = messagestring + " - " + self.computerNameAccuracyLevelWhy + ";" success = False else: messagestring = "- compliant; " + messagestring self.messageAppend(messagestring) # Check localhostname compliant = True messagestring = "LocalHostName confidence level of " + \ str(self.getSuggestedComputerNameConfidenceOnly()) + "%" if not(self.getSuggestedComputerNameConfidenceOnly() == 100): compliant = False messagestring = messagestring + " is less than 100%" messagestring = messagestring + "; LocalHostName (" + \ self.getDiskUtilityLocalHostName() + ") and proposed LocalHostName (" + \ self.getSuggestedLocalHostName() + ")" if not(self.getDiskUtilityLocalHostName() == self.getSuggestedLocalHostName()): compliant = False messagestring = messagestring + " are not equal;" else: messagestring = messagestring + " are equal;" if not compliant: messagestring = "- Not compliant; " + messagestring if not(self.computerNameAccuracyLevelWhy == "" ): messagestring = messagestring + " - " + self.computerNameAccuracyLevelWhy + ";" success = False else: messagestring = "- compliant; " + messagestring self.messageAppend(messagestring) return success def getDiskUtilityComputerName(self): ''' get the ComputerName determined by disk utility @author: ekkehard @return: string ''' return self.computerNameDiskUtility def getDiskUtilityHostName(self): ''' get the HostName determined by disk utility @author: ekkehard @return: string ''' return self.hostNameDiskUtility def getDiskUtilityLocalHostName(self): ''' get the LocalHostName determined by disk utility @author: ekkehard @return: string ''' return self.localHostnameDiskUtility def getLANLAssetTagNVRAM(self): ''' get the asset_id set in NVRAM determined @author: ekkehard @return: string ''' return str(self.LANLAssetTagNVRAM) def getLANLAssetTagFilesystem(self): ''' get the asset_id set in file system @author: ekkehard @return: string ''' return str(self.LANLAssetTagFilesystem) def getSuggestedAssetTag(self): ''' get the suggested asset_id @author: ekkehard @return: string ''' return self.assetTag def getSuggestedAssetTagConfidence(self): ''' get the suggested asset_id and asset_id confidence level @author: ekkehard @return: string ''' displayValue = str(self.assetTag) + " (" + str(self.assetTagAccuracyLevel) + "%)" return displayValue def getSuggestedAssetTagConfidenceOnly(self): ''' get the suggested asset_id confidence level @author: ekkehard @return: real ''' return self.assetTagAccuracyLevel def getSuggestedComputerName(self): ''' get the suggested ComputerName @author: ekkehard @return: string ''' return self.computerName def getSuggestedComputerNameConfidence(self): ''' get the suggested ComputerName and ComputerName confidence level @author: ekkehard @return: string ''' displayValue = str(self.computerName) + " (" + str(self.computerNameAccuracyLevel) + "%)" return displayValue def getSuggestedComputerNameConfidenceOnly(self): ''' get the suggested ComputerName confidence level @author: ekkehard @return: real ''' return self.computerNameAccuracyLevel def getSuggestedHostName(self): ''' get the suggested HostName @author: ekkehard @return: string ''' return self.hostName def getSuggestedHostNameConfidence(self): ''' get the suggested HostName and HostName confidence level @author: ekkehard @return: string ''' displayValue = str(self.hostName) + " (" + str(self.computerNameAccuracyLevel) + "%)" return displayValue def getSuggestedHostNameConfidenceOnly(self): ''' get the suggested HostName confidence level @author: ekkehard @return: real ''' return self.computerNameAccuracyLevel def getSuggestedLocalHostName(self): ''' get the suggested LocalHostName @author: ekkehard @return: string ''' return self.localHostname def getSuggestedLocalHostNameConfidence(self): ''' get the suggested LocalHostName and LocalHostName confidence level @author: ekkehard @return: string ''' displayValue = str(self.localHostname) + " (" + str(self.computerNameAccuracyLevel) + "%)" return displayValue def getSuggestedLocalHostNameConfidenceOnly(self): ''' get the suggested LocalHostName confidence level @author: ekkehard @return: real ''' return self.computerNameAccuracyLevel def getSuggestedEndUsername(self): ''' get the suggested EndUserName or Owner @author: ekkehard @return: string ''' return self.endUsername def getSuggestedEndUsernameConfidence(self): ''' get the suggested EndUserName or Owner and EndUserName or Owner confidence level @author: ekkehard @return: string ''' displayValue = str(self.endUsername) + " (" + str(self.endUserNameAccuracyLevel) + "%)" return displayValue def getSuggestedEndUsernameConfidenceOnly(self): ''' get the suggested EndUserName or Owner confidence level @author: ekkehard @return: real ''' return self.endUserNameAccuracyLevel def setComputerInfo(self): ''' set the computer info on the computer @author: ekkehard @return: boolean - True ''' try: success = True updatesWhereMade = False output = [] computerName = self.getSuggestedComputerName() hostname = self.getSuggestedHostName() localHostName = self.getSuggestedLocalHostName() if self.computerNameAccuracyLevel == 100: if not(self.computerNameDiskUtility == computerName): command = [self.scutil,"--set", "ComputerName", computerName] self.ch.executeCommand(command) errorcode = self.ch.getError() output = self.ch.getOutput() updatesWhereMade = True messagestring = " - ComputerName set to " + computerName self.messageAppend(messagestring) else: messagestring = " - ComputerName was alreday " + computerName self.messageAppend(messagestring) if not(self.hostNameDiskUtility == hostname): command = [self.scutil,"--set", "HostName", hostname] self.ch.executeCommand(command) errorcode = self.ch.getError() output = self.ch.getOutput() updatesWhereMade = True messagestring = " - HostName set to " + hostname self.messageAppend(messagestring) else: messagestring = " - HostName was alreday " + hostname self.messageAppend(messagestring) if not(self.localHostnameDiskUtility == localHostName): command = [self.scutil,"--set", "LocalHostName", localHostName] errorcode = self.ch.getError() self.ch.executeCommand(command) output = self.ch.getOutput() updatesWhereMade = True messagestring = " - LocalHostName set to " + localHostName self.messageAppend(messagestring) else: messagestring = " - LocalHostName was alreday " + localHostName self.messageAppend(messagestring) except(KeyboardInterrupt, SystemExit): raise except Exception: messagestring = traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, messagestring) if updatesWhereMade == True: self.initializeDiskUtilityInfo() return success def setJAMFInfo(self): ''' set the assetTag and endUserName via the jamf recon command @author: ekkehard @return: boolean - True ''' try: success = True assetTag = self.getSuggestedAssetTag() endUser = self.getSuggestedEndUsername() if self.assetTagAccuracyLevel == 100 and self.endUserNameAccuracyLevel == 100: command = [self.jamf, "recon", "-assetTag", assetTag, "-endUsername", endUser] self.ch.executeCommand(command) output = self.ch.getOutput() messagestring = " - JAMF assetTag set to " + assetTag + " and endUsername set to " + endUser self.messageAppend(messagestring) elif self.assetTagAccuracyLevel == 100: command = [self.jamf, "recon", "-assetTag", assetTag] self.ch.executeCommand(command) output = self.ch.getOutput() endUser = "" messagestring = " - JAMF assetTag set to " + assetTag self.messageAppend(messagestring) elif self.endUserNameAccuracyLevel == 100: command = [self.jamf, "recon", "-endUsername", endUser] self.ch.executeCommand(command) output = self.ch.getOutput() assetTag = "" messagestring = " - JAMF endUsername set to " + endUser self.messageAppend(messagestring) else: success = False messagestring = " - JAMF settings were not changed because confidence was only " + \ str(self.assetTagAccuracyLevel) + "%" self.messageAppend(messagestring) except(KeyboardInterrupt, SystemExit): raise except Exception: messagestring = traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, messagestring) return success def setLANLAssetTagNVRAM(self): ''' set the assetTag and endUserName via the jamf recon command @author: ekkehard @return: boolean - True ''' try: assetTag = self.getSuggestedAssetTag() if self.assetTagAccuracyLevel == 100: if not(self.getLANLAssetTagNVRAM() == assetTag): command = [self.nvram, "asset_id=" + assetTag] self.ch.executeCommand(command) self.initializeLanlAssetTagNVRAM() messagestring = " - NVRAM asset_id set to " + assetTag self.messageAppend(messagestring) else: messagestring = " - NVRAM asset_id was already set to " + assetTag self.messageAppend(messagestring) else: assetTag = "" messagestring = " - NVRAM asset_id was not changed because confidence was only " + \ str(self.assetTagAccuracyLevel) + "%" self.messageAppend(messagestring) except(KeyboardInterrupt, SystemExit): raise except Exception: messagestring = traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, messagestring) return assetTag def setLANLAssetTagFilesystem(self): ''' set the assetTag on the file system @author: ekkehard @return: boolean - True ''' try: assetTag = self.getSuggestedAssetTag() if self.assetTagAccuracyLevel == 100: if not(self.getLANLAssetTagFilesystem() == assetTag): try : filepointer = open(self.lanl_property_file, "w") filepointer.write(assetTag) filepointer.close() self.initializeLanlAssetTagFilesystem() assetTag = self.getLANLAssetTagFilesystem() except Exception, err : messagestring = "Problem writing: " + self.lanl_property_file + \ " error: " + str(err) self.logdispatch.log(LogPriority.DEBUG, messagestring) else:
class DisableRemoteAppleEvents(Rule): '''classdocs''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.statechglogger = statechglogger self.rulenumber = 213 self.rulename = 'DisableRemoteAppleEvents' self.formatDetailedResults("initialize") self.compliant = False self.mandatory = True self.rootrequired = True self.guidance = ['CIS 1.4.14.10'] self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.sethelptext() # set up CIs datatype = 'bool' key = 'DISABLEREMOTEAPPLEEVENTS' instructions = 'To allow the use of remote apple events on this system, set the value of DisableRemoteAppleEvents to False.' default = True self.disableremoteevents = self.initCi(datatype, key, instructions, default) datatype2 = 'list' key2 = 'REMOTEAPPLEEVENTSUSERS' instructions2 = 'If you have a business requirement to have remote apple events turned on, enter a list of users who will be allowed access to remote apple events on this system' default2 = [] self.secureremoteevents = self.initCi(datatype2, key2, instructions2, default2) def report(self): '''return the compliance status of the system with this rule :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.cmhelper = CommandHelper(self.logger) self.compliant = True try: print(("Value of disableremoteevents CI = " + str(self.disableremoteevents.getcurrvalue()))) if self.disableremoteevents.getcurrvalue(): if not self.reportDisabled(): self.compliant = False print(("Value of secureremoteevents CI = " + str(self.secureremoteevents.getcurrvalue()))) if self.secureremoteevents.getcurrvalue() != []: if not self.reportSecured(): self.compliant = False 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 reportDisabled(self): ''' ''' self.logger.log(LogPriority.DEBUG, "Checking if remote apple events are disabled...") retval = False get_remote_ae = "/usr/sbin/systemsetup -getremoteappleevents" searchphrase = "Remote Apple Events: Off" self.cmhelper.executeCommand(get_remote_ae) outputlist = self.cmhelper.getOutput() for line in outputlist: if re.search(searchphrase, line, re.IGNORECASE): retval = True if not retval: self.detailedresults += "\nRemote Apple Events are On" else: self.detailedresults += "\nRemote Apple Events are Off" return retval def reportSecured(self): ''' ''' self.logger.log(LogPriority.DEBUG, "Checking if remote apple events is secured...") retval = True uuid_list = self.getUUIDs(self.secureremoteevents.getcurrvalue()) remote_ae_users = self.getRemoteAEUsers() difference = list(set(uuid_list) - set(remote_ae_users)) if difference: retval = False self.detailedresults += "\nThe current list of allowed remote access users does not match the desired list of remote access users" self.detailedresults += "\nDifference: " + " ".join(difference) return retval def getRemoteAEUsers(self): '''return a list of uuid's of current remote ae users (mac os x stores the remote ae users as uuid's in a plist) ''' get_remote_ae_users = "/usr/bin/dscl . read /Groups/com.apple.access_remote_ae GroupMembers" remote_ae_users = [] # it is possible that the key "GroupMembers" does not exist # this is because when you remove the last remote ae user from the list, # mac os x deletes this key from the plist as well.. self.cmhelper.executeCommand(get_remote_ae_users) retcode = self.cmhelper.getReturnCode() if retcode == 0: remote_ae_users = self.cmhelper.getOutputString().split() else: errmsg = self.cmhelper.getErrorString() self.logger.log(LogPriority.DEBUG, str(errmsg)) if "GroupMembers:" in remote_ae_users: remote_ae_users.remove("GroupMembers:") return remote_ae_users def fix(self): '''run commands needed to bring the system to a compliant state with this rule :returns: self.rulesuccess :rtype: bool @author: Breen Malmberg ''' self.iditerator = 0 self.rulesuccess = True self.detailedresults = "" try: if self.disableremoteevents.getcurrvalue(): if not self.disable_remote_ae(): self.rulesuccess = False if self.secureremoteevents.getcurrvalue(): if not self.secure_remote_ae(): self.rulesuccess = False 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 disable_remote_ae(self): ''' ''' retval = True disable_remote_ae_cmd = "/usr/sbin/systemsetup setremoteappleevents off" undocmd = "/usr/sbin/systemsetup setremoteappleevents on" self.cmhelper.executeCommand(disable_remote_ae_cmd) retcode = self.cmhelper.getReturnCode() if retcode != 0: retval = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "commandstring", "command": undocmd} self.statechglogger.recordchgevent(myid, event) return retval def secure_remote_ae(self): ''' ''' retval = True desired_remote_ae_users = self.getUUIDs( self.secureremoteevents.getcurrvalue()) securecmd = "/usr/bin/dscl . create /Groups/com.apple.access_remote_ae GroupMembers " + " ".join( desired_remote_ae_users) original_remote_ae_users = self.getRemoteAEUsers() if original_remote_ae_users: undocmd = "/usr/bin/dscl . create /Groups/com.apple.access_remote_ae GroupMembers " + " ".join( original_remote_ae_users) else: undocmd = "/usr/bin/dscl . delete /Groups/com.apple.access_remote_ae GroupMembers" self.cmhelper.executeCommand(securecmd) retcode = self.cmhelper.getReturnCode() if retcode == 0: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "commandstring", "command": undocmd} self.statechglogger.recordchgevent(myid, event) else: retval = False self.detailedresults += "\nFailed to properly configure the desired remote apple events users" # we assume the user wants to use the service if they configure the user list for it # and turn off the disable events CI if not self.disableremoteevents.getcurrvalue(): undo_enable_remote_ae_cmd = "/usr/sbin/systemsetup setremoteappleevents off" enable_remote_ae_cmd = "/usr/sbin/systemsetup setremoteappleevents on" self.cmhelper.executeCommand(enable_remote_ae_cmd) retcode = self.cmhelper.getReturnCode() if retcode != 0: retval = False self.detailedresults += "\nFailed to enable remote apple events service" else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "commandstring", "command": undo_enable_remote_ae_cmd } self.statechglogger.recordchgevent(myid, event) return retval def getUUIDs(self, userlist): '''convert the desired (user-specified) list of ae user names into uuid's; return as list :param userlist: ''' uuidlist = [] for user in userlist: output = "" get_uuid = "/usr/bin/dsmemberutil getuuid -U " + user self.cmhelper.executeCommand(get_uuid) output = self.cmhelper.getOutputString() if re.search("no uuid", output, re.IGNORECASE): continue else: if output: uuidlist.append(output.strip()) return uuidlist
class ConfigureLinuxFirewall(Rule): '''The configureLinuxFirewall class attempts to audit and configure firewalls for Linux OS based systems. Note: there is tremendous variations in the approach taken by the various distributions on how to manage firewalls, this code should work effectively for debian, ubuntu, RHEL and close derivatives. Note: unlike many other rules this behaves as a binary state manager, the undo will set the system back to an as new state with no firewalls. ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.config = config self.environ = environ self.logger = logger self.statechglogger = statechglogger self.rulenumber = 92 self.rulename = 'ConfigureLinuxFirewall' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.applicable = {'type': 'white', 'family': ['linux']} self.servicehelper = ServiceHelper(self.environ, self.logger) self.serviceTarget = "" self.cmdhelper = CommandHelper(self.logger) self.guidance = ['NIST 800-53 AC-4', 'DISA RHEL 7 STIG 2.5.7.1', 'DISA RHEL 7 STIG 2.5.7.1.1', 'DISA RHEL 7 STIG 2.5.8.1.1', 'DISA RHEL 7 STIG 2.5.8.1.2', 'DISA RHEL 7 STIG 2.5.8.1.3', 'DISA RHEL 7 STIG 2.5.8.2.1', 'DISA RHEL 7 STIG 2.5.8.2.2', 'DISA RHEL 7 STIG 2.5.8.2.3', 'DISA RHEL 7 STIG 2.5.8.2.4'] datatype = 'bool' key = 'CONFIGURELINUXFIREWALL' instructions = '''To disable this rule set the value of \ CONFIGURELINUXFIREWALL to False.''' default = False self.clfci = self.initCi(datatype, key, instructions, default) self.scriptType = "" self.iptScriptPath = "" self.iptables, self.ip6tables, self.iprestore, self.ip6restore = "", "", "", "" self.checkIptables() self.iditerator = 0 def report(self): '''Report on whether the firewall meets baseline expectations. :returns: bool @author: D.Kennel @change: dwalker - updating rule to check for every possible firewall implementation and configure it rather than mutually exclusively checking based on system. ''' try: compliant = True iptablesrunning = False ip6tablesrunning = False iptablesenabled = False ip6tablesenabled = False catchall = False catchall6 = False self.detailedresults = "" scriptExists = "" if self.checkFirewalld(): if not self.servicehelper.auditService('firewalld.service', serviceTarget=self.serviceTarget): compliant = False self.detailedresults = 'This system appears to have firewalld but it is not running as required' elif self.checkUFW(): cmdufw = '/usr/sbin/ufw status' if not self.cmdhelper.executeCommand(cmdufw): self.detailedresults += "Unable to run " + \ "ufw status command\n" compliant = False else: outputufw = self.cmdhelper.getOutputString() if re.search('Status: inactive', outputufw): compliant = False self.detailedresults += 'This system appears to have ' + \ 'ufw but it is not running as required' elif re.search('Status: active', outputufw): cmdufw = "/usr/sbin/ufw status verbose" if not self.cmdhelper.executeCommand(cmdufw): compliant = False self.detailedresults += "Cannot retrieve firewall rules\n" else: outputufw = self.cmdhelper.getOutputString() if not re.search("Default\:\ deny\ \(incoming\)", outputufw): compliant = False self.detailedresults += "The default value for " + \ "incoming unspecified packets is not deny\n" else: if os.path.exists("/etc/network/if-pre-up.d"): self.iptScriptPath = "/etc/network/if-pre-up.d/iptables" self.scriptType = "debian" elif os.path.exists("/etc/sysconfig/scripts"): self.iptScriptPath = "/etc/sysconfig/scripts/SuSEfirewall2-custom" self.scriptType = "suse" else: self.logger.log(LogPriority.DEBUG, ['ConfigureLinuxFirewall.report', "No acceptable path for a startup " + "script found"]) if self.iptScriptPath: if os.path.exists(self.iptScriptPath): scriptExists = True else: scriptExists = False else: scriptExists = True if self.scriptType != "debian": self.servicehelper.stopService('iptables') self.servicehelper.startService('iptables') self.servicehelper.stopService('ip6tables') self.servicehelper.startService('ip6tables') if self.servicehelper.isRunning('iptables'): iptablesrunning = True if self.servicehelper.isRunning('ip6tables'): ip6tablesrunning = True if self.servicehelper.auditService('iptables'): iptablesenabled = True if self.servicehelper.auditService('ip6tables'): ip6tablesenabled = True else: iptablesrunning = True ip6tablesrunning = True iptablesenabled = True ip6tablesenabled = True if self.iptables: cmd = [self.iptables, "-L"] if not self.cmdhelper.executeCommand(cmd): self.detailedresults += "Unable to run " + \ "iptables -L command\n" compliant = False else: output = self.cmdhelper.getOutput() for line in output: if re.search('Chain INPUT \(policy REJECT\)|REJECT' + '\s+all\s+--\s+anywhere\s+anywhere', line): catchall = True break # check to see if the kernel was compiled with ipv6 support first lsmod_paths = ["/sbin/lsmod", "/usr/sbin/lsmod"] lsmod_path = "" for p in lsmod_paths: if os.path.exists(p): lsmod_path = p if lsmod_path: check_ipv6_kernel = "/sbin/lsmod | grep -w 'ipv6'" self.cmdhelper.executeCommand(check_ipv6_kernel) retcode = self.cmdhelper.getReturnCode() if retcode == 0: # if system has kernel support for ipv6, then check for ip6tables bin, then do ip6tables rules check if self.ip6tables: cmd6 = [self.ip6tables, "-L"] if not self.cmdhelper.executeCommand(cmd6): self.detailedresults += "Unable to run " + \ "ip6tables -L command\n" compliant = False else: output6 = self.cmdhelper.getOutput() for line in output6: if re.search('Chain INPUT \(policy REJECT\)|REJECT' + '\s+all\s+anywhere\s+anywhere', line): catchall6 = True break if not catchall6: compliant = False self.detailedresults += 'This system appears to use ' + \ 'ip6tables but does not contain the expected rules. ' + \ 'If the DisableIPV6 rule was run before this rule, this is ' + \ 'acceptable behavior.\n' self.logger.log(LogPriority.DEBUG, ['ConfigureLinuxFirewall.report', "Missing v6 deny all."]) if not ip6tablesenabled: compliant = False self.detailedresults += "ip6tables not enabled " + \ 'If the DisableIPV6 rule was run before this rule, this is ' + \ 'acceptable behavior.\n' if not ip6tablesrunning: compliant = False self.detailedresults += 'This system appears to use ' + \ 'ip6tables but it is not running as required. ' + \ 'If the DisableIPV6 rule was run before this rule, this is ' + \ 'acceptable behavior.\n' self.logger.log(LogPriority.DEBUG, ['ConfigureLinuxFirewall.report', "RHEL 6 type system. IP6tables not running."]) else: self.logger.log(LogPriority.DEBUG, "This system's kernel was compiled without ipv6 support. Skipping ip6tables checks...") self.detailedresults += "\nThis system does not have support for ipv6. Skipping ip6tables checks..." else: self.logger.log(LogPriority.DEBUG, "Unable to detect lsmod utility - which is required to perform a necessary pre-check before attempting to run ip6tables commands.") self.detailedresults += "\nUnable to determine if this system has support for ipv6. Skipping ip6tables checks..." if not catchall: compliant = False self.detailedresults += 'This system appears to use ' + \ 'iptables but does not contain the expected rules\n' self.logger.log(LogPriority.DEBUG, ['ConfigureLinuxFirewall.report', "Missing v4 deny all."]) if not iptablesrunning: compliant = False self.detailedresults += 'This system appears to use ' + \ 'iptables but it is not running as required.\n' self.logger.log(LogPriority.DEBUG, ['ConfigureLinuxFirewall.report', "RHEL 6 type system. IPtables not running."]) if not iptablesenabled: compliant = False self.detailedresults += "iptables not enabled\n" if not scriptExists: compliant = False self.detailedresults += 'This system appears to use ' + \ 'iptables but the startup script is not present\n' self.logger.log(LogPriority.DEBUG, ['ConfigureLinuxFirewall.report', "Missing startup script"]) 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): '''Enable the firewall services and establish basic rules if needed. @author: D. Kennel ''' try: if not self.clfci.getcurrvalue(): return self.iditerator = 0 self.detailedresults = "" success = True # delete past state change records from previous fix eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) #firewall-cmd portion if self.checkFirewalld(): if self.servicehelper.enableService('firewalld.service', serviceTarget=self.serviceTarget): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = "/usr/bin/systemctl disable firewalld.service" event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) self.detailedresults += "Firewall configured.\n " else: success = False self.detailedresults += "Unable to enable firewall\n" debug = "Unable to enable firewall\n" self.logger.log(LogPriority.DEBUG, debug) #ufw command portion elif self.checkUFW(): self.logger.log(LogPriority.DEBUG, "System uses ufw. Running ufw commands...") cmdufw = '/usr/sbin/ufw status' if not self.cmdhelper.executeCommand(cmdufw): self.detailedresults += "Unable to run " + \ "ufw status command\n" success = False else: outputufw = self.cmdhelper.getOutputString() if re.search('Status: inactive', outputufw): ufwcmd = '/usr/sbin/ufw --force enable' if not self.cmdhelper.executeCommand(ufwcmd): self.detailedresults += "Unable to run " + \ "ufw enable command\n" success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) undocmd = "/usr/sbin/ufw --force disable" event = {"eventtype": "commandstring", "command": undocmd} self.statechglogger.recordchgevent(myid, event) cmdufw = "/usr/sbin/ufw status verbose" if not self.cmdhelper.executeCommand(cmdufw): self.detailedresults += "Unable to retrieve firewall rules\n" success = False else: outputfw = self.cmdhelper.getOutputString() if not re.search("Default\:\ deny\ \(incoming\)", outputfw): ufwcmd = "/usr/sbin/ufw default deny incoming" if not self.cmdhelper.executeCommand(ufwcmd): self.detailedresults += "Unable to set default " + \ "rule for incoming unspecified packets\n" success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) undocmd = "/usr/sbin/ufw default allow incoming" event = {"eventtype": "commandstring", "command": undocmd} self.statechglogger.recordchgevent(myid, event) elif re.search('Status: active', outputufw): cmdufw = "/usr/sbin/ufw status verbose" if not self.cmdhelper.executeCommand(cmdufw): self.detailedresults += "Cannot retrieve firewall rules\n" success = False else: outputufw = self.cmdhelper.getOutputString() if not re.search("Default\:\ deny\ \(incoming\)", outputufw): ufwcmd = "/usr/sbin/ufw default deny incoming" if not self.cmdhelper.executeCommand(ufwcmd): self.detailedresults += "Unable to set default " + \ "rule for incoming unspecified packets\n" success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) undocmd = "/usr/sbin/ufw default allow incoming" event = {"eventtype": "commandstring", "command": undocmd} self.statechglogger.recordchgevent(myid, event) else: #following portion is mainly for debian and opensuse systems only if os.path.exists("/etc/network/if-pre-up.d"): self.iptScriptPath = "/etc/network/if-pre-up.d/iptables" self.scriptType = "debian" servicename = "networking" elif os.path.exists("/etc/sysconfig/scripts"): self.iptScriptPath = "/etc/sysconfig/scripts/SuSEfirewall2-custom" self.scriptType = "suse" servicename = "network" #this script will ensure that iptables gets configured #each time the network restarts iptables = self.getScriptValues("iptables") ip6tables = self.getScriptValues("ip6tables") iptScript = "" created = False if self.iptScriptPath: if self.scriptType == "debian": if self.iprestore and self.ip6restore: iptScript = '#!/bin/bash\n' + self.iprestore + \ ' <<< "' + iptables + '"\n' + self.ip6restore + \ ' <<< "' + ip6tables + '"' else: iptScript = self.getScriptValues("iptscript") if iptScript: if not os.path.exists(self.iptScriptPath): if not createFile(self.iptScriptPath, self.logger): success = False self.detailedresults += "Unable to create file " + self.iptScriptPath + "\n" else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.iptScriptPath} self.statechglogger.recordchgevent(myid, event) if os.path.exists(self.iptScriptPath): if not checkPerms(self.iptScriptPath, [0, 0, 0o755], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.iptScriptPath, [0, 0, 0o755], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set permissions on " + self.iptScriptPath + "\n" contents = readFile(self.iptScriptPath, self.logger) if contents != iptScript: tempfile = self.iptScriptPath + ".tmp" if not writeFile(tempfile, iptScript, self.logger): success = False self.detailedresults += "Unable to write contents to " + self.iptScriptPath + "\n" else: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.iptScriptPath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.iptScriptPath, tempfile, myid) os.rename(tempfile, self.iptScriptPath) os.chown(self.iptScriptPath, 0, 0) os.chmod(self.iptScriptPath, 0o755) resetsecon(self.iptScriptPath) stonixfilepath = "/var/db/stonix/" savecmd = "/sbin/iptables-save > " + stonixfilepath + "user-firewall-pre-stonix" if not self.cmdhelper.executeCommand(savecmd): success = False self.detailedresults += "Unable to save current ipv4 " + \ "firewall rules for revert\n" debug = "Unable to save current ipv4 " + \ "firewall rules for revert\n" self.logger.log(LogPriority.DEBUG, debug) save6cmd = "/sbin/ip6tables-save > " + stonixfilepath + "user-firewall6-pre-stonix" if not self.cmdhelper.executeCommand(save6cmd): success = False self.detailedresults += "Unable to save current ipv6 " + \ "firewall rules for revert\n" debug = "Unable to save current ipv6 " + \ "firewall rules for revert\n" self.logger.log(LogPriority.DEBUG, debug) self.servicehelper.stopService(servicename) if not self.servicehelper.startService(servicename): success = False self.detailedresults += "Unable to restart networking\n" debug = "Unable to restart networking\n" self.logger.log(LogPriority.DEBUG, debug) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = "/sbin/iptables-restore < " + stonixfilepath + "user-firewall-pre-stonix" event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = "/sbin/ip6tables-restore < " + stonixfilepath + "user-firewall6-pre-stonix" event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) else: success = False self.detailedresults += "There is no iptables startup script\n" debug = "There is no iptables startup script\n" self.logger.log(LogPriority.DEBUG, debug) #this portion mostly applies to RHEL6 and Centos6 if os.path.exists('/usr/bin/system-config-firewall') or \ os.path.exists('/usr/bin/system-config-firewall-tui'): systemconfigfirewall = self.getScriptValues("systemconfigfirewall") sysconfigiptables = self.getScriptValues("sysconfigiptables") sysconfigip6tables = self.getScriptValues("sysconfigip6tables") fwpath = '/etc/sysconfig/system-config-firewall' iptpath = '/etc/sysconfig/iptables' ip6tpath = '/etc/sysconfig/ip6tables' #portion to handle the system-config-firewall file created = False if not os.path.exists(fwpath): if not createFile(fwpath, self.logger): success = False self.detailedresults += "Unable to create file " + fwpath + "\n" else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": fwpath} self.statechglogger.recordchgevent(myid, event) if os.path.exists(fwpath): if not checkPerms(fwpath, [0, 0, 0o600], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(fwpath, [0, 0, 0o600], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set permissions on " + fwpath + "\n" contents = readFile(fwpath, self.logger) if contents != systemconfigfirewall: print("contents don't equal systemconfigurefirewall contents\n") tempfile = fwpath + ".tmp" if not writeFile(tempfile, systemconfigfirewall, self.logger): success = False self.detailedresults += "Unable to write contents to " + fwpath + "\n" else: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": fwpath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(fwpath, tempfile, myid) os.rename(tempfile, fwpath) os.chown(fwpath, 0, 0) os.chmod(fwpath, 0o600) resetsecon(fwpath) created = False #portion to handle the iptables rules file if not os.path.exists(iptpath): if not createFile(iptpath, self.logger): success = False self.detailedresults += "Unable to create file " + iptpath + "\n" else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": iptpath} self.statechglogger.recordchgevent(myid, event) if os.path.exists(iptpath): if not checkPerms(iptpath, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(iptpath, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set permissions on " + iptpath + "\n" contents = readFile(iptpath, self.logger) if contents != sysconfigiptables: tempfile = iptpath + ".tmp" if not writeFile(tempfile, sysconfigiptables, self.logger): success = False self.detailedresults += "Unable to write contents to " + iptpath + "\n" else: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": iptpath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(iptpath, tempfile, myid) os.rename(tempfile, iptpath) os.chown(iptpath, 0, 0) os.chmod(iptpath, 0o644) resetsecon(iptpath) created = False #portion to handle ip6tables rules file if not os.path.exists(ip6tpath): if not createFile(ip6tpath, self.logger): success = False self.detailedresults += "Unable to create file " + ip6tpath + "\n" else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": ip6tpath} self.statechglogger.recordchgevent(myid, event) if os.path.exists(ip6tpath): if not checkPerms(ip6tpath, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(ip6tpath, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set permissions on " + ip6tpath + "\n" contents = readFile(ip6tpath, self.logger) if contents != sysconfigip6tables: tempfile = ip6tpath + ".tmp" if not writeFile(tempfile, sysconfigip6tables, self.logger): success = False self.detailedresults += "Unable to write contents to " + ip6tpath + "\n" else: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": ip6tpath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(ip6tpath, tempfile, myid) os.rename(tempfile, ip6tpath) os.chown(ip6tpath, 0, 0) os.chmod(ip6tpath, 0o644) resetsecon(ip6tpath) # check if iptables is enabled to run at start if not self.servicehelper.auditService('iptables'): # enable service to run at start if not if not self.servicehelper.enableService('iptables'): self.detailedresults += "Unable to enable iptables service\n" debug = "Unable to enable iptables service\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: # record event if successful self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = self.servicehelper.getDisableCommand('iptables') event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) self.servicehelper.stopService('iptables') # start iptables if not if not self.servicehelper.startService('iptables'): self.detailedresults += "Unable to start iptables service\n" debug = "Unable to start iptables service\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: stonixfilepath = "/var/db/stonix/" savecmd = "/sbin/iptables-save > " + stonixfilepath + "user-firewall-pre-stonix" if not self.cmdhelper.executeCommand(savecmd): success = False self.detailedresults += "Unable to save current ipv4 " + \ "firewall rules for revert\n" debug = "Unable to save current ipv4 " + \ "firewall rules for revert\n" self.logger.log(LogPriority.DEBUG, debug) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = "/sbin/iptables-restore < " + stonixfilepath + "user-firewall-pre-stonix" event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) savecmd = "/sbin/ip6tables-save > " + stonixfilepath + "user-firewall6-pre-stonix" if not self.cmdhelper.executeCommand(savecmd): success = False self.detailedresults += "Unable to save current ipv6 " + \ "firewall rules for revert\n" debug = "Unable to save current ipv6 " + \ "firewall rules for revert\n" self.logger.log(LogPriority.DEBUG, debug) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = "/sbin/ip6tables-restore < " + stonixfilepath + "user-firewall6-pre-stonix" event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) # check if ip6tables is enabled to run at start if not self.servicehelper.auditService('ip6tables'): # enable service to run at start if not if not self.servicehelper.enableService('ip6tables'): self.detailedresults += "Unable to enable ip6tables service\n" debug = "Unable to enable ip6tables service\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: # record event if successful self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) cmd = self.servicehelper.getDisableCommand('ip6tables') event = {"eventtype": "commandstring", "command": cmd} self.statechglogger.recordchgevent(myid, event) self.servicehelper.stopService('ip6tables') # start ip6tables if not if not self.servicehelper.startService('ip6tables'): self.detailedresults += "Unable to start ip6tables service\n" debug = "Unable to start ip6tables service\n" self.logger.log(LogPriority.DEBUG, debug) success = False # Sleep for a bit to let the restarts occur time.sleep(10) self.rulesuccess = success 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 checkFirewalld(self): """ :return: bool; True if the firewall-cmd path exists, False if not """ firewalld_paths = ["/bin/firewall-cmd", "/usr/bin/firewall-cmd"] for p in firewalld_paths: if os.path.exists(p): return True def checkUFW(self): #for Ubuntu systems mostly if os.path.exists('/usr/sbin/ufw'): return True def checkIsOther(self): #for debian and opensuse mostly if "iptables" not in self.servicehelper.listServices(): return True def checkIptables(self): # mostly pertains to RHEL6, Centos6 if os.path.exists("/usr/sbin/iptables"): self.iptables = "/usr/sbin/iptables" elif os.path.exists("/sbin/iptables"): self.iptables = "/sbin/iptables" if os.path.exists("/usr/sbin/ip6tables"): self.ip6tables = "/usr/sbin/ip6tables" elif os.path.exists("/sbin/ip6tables"): self.ip6tables = "/sbin/ip6tables" if os.path.exists("/usr/sbin/iptables-restore"): self.iprestore = "/usr/sbin/iptables-restore" elif os.path.exists("/sbin/iptables-restore"): self.iprestore = "/sbin/iptables-restore" if os.path.exists("/usr/sbin/ip6tables-restore"): self.ip6restore = "/usr/sbin/ip6tables-restore" elif os.path.exists("/sbin/ip6tables-restore"): self.ip6restore = "/sbin/ip6tables-restore" def getScriptValues(self, scriptname): if scriptname == "iptscript": iptScript = '''fw_custom_after_chain_creation() { *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited -A INPUT -p ipv6-icmp -j ACCEPT -A INPUT -m state --state NEW -m udp -p udp --dport 546 -d fe80::/64 -j ACCEPT -A INPUT -j REJECT --reject-with icmp6-adm-prohibited -A FORWARD -j REJECT --reject-with icmp6-adm-prohibited true } fw_custom_before_port_handling() { true } fw_custom_before_masq() { true } fw_custom_before_denyall() { true } fw_custom_after_finished() { true } ''' return iptScript elif scriptname == "iptables": iptables = '''*filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT ''' return iptables elif scriptname == "ip6tables": ip6tables = '''*filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p ipv6-icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m state --state NEW -m udp -p udp --dport 546 -d fe80::/64 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp6-adm-prohibited -A FORWARD -j REJECT --reject-with icmp6-adm-prohibited COMMIT ''' return ip6tables elif scriptname == "systemconfigfirewall": systemconfigfirewall = '''# Configuration file for system-config-firewall --enabled --service=ssh ''' return systemconfigfirewall elif scriptname == "sysconfigiptables": sysconfigiptables = '''# Firewall configuration written by system-config-firewall # Manual customization of this file is not recommended. *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp-host-prohibited -A FORWARD -j REJECT --reject-with icmp-host-prohibited COMMIT ''' return sysconfigiptables elif scriptname == "sysconfigip6tables": sysconfigip6tables = '''# Firewall configuration written by system-config-firewall # Manual customization of this file is not recommended. *filter :INPUT ACCEPT [0:0] :FORWARD ACCEPT [0:0] :OUTPUT ACCEPT [0:0] -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT -A INPUT -p ipv6-icmp -j ACCEPT -A INPUT -i lo -j ACCEPT -A INPUT -m state --state NEW -m udp -p udp --dport 546 -d fe80::/64 -j ACCEPT -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT -A INPUT -j REJECT --reject-with icmp6-adm-prohibited -A FORWARD -j REJECT --reject-with icmp6-adm-prohibited COMMIT ''' return sysconfigip6tables
class RestrictAccessToKernelMessageBuffer(Rule): '''Unprivileged access to the kernel syslog can expose sensitive kernel address information.''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.environ = environ self.logger = logger self.rulenumber = 86 self.rulename = "RestrictAccessToKernelMessageBuffer" self.mandatory = True self.rootrequired = True self.rulesuccess = True self.formatDetailedResults("initialize") self.sethelptext() self.guidance = ["CCE-RHEL7-CCE-TBD 2.2.4.5"] self.applicable = {"type": "white", "family": "linux"} datatype = "bool" key = "RESTRICTACCESSTOKERNELMESSAGEBUFFER" instructions = "To prevent this rule from running, set the value of RestrictAccessToKernelMessageBuffer to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.localize() self.initobjs() def localize(self): '''determine system-specific settings @author: Breen Malmberg ''' # set defaults self.fixcommand = "sysctl -w kernel.dmesg_restrict=1" self.reportcommand = "sysctl kernel.dmesg_restrict" def initobjs(self): '''initialize class objects @author: Breen Malmberg ''' self.ch = CommandHelper(self.logger) def report(self): '''run report actions for this rule :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.compliant = True kernelopt = False try: self.ch.executeCommand(self.reportcommand) output = self.ch.getOutput() retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "\nError while running command: " + str(self.reportcommand) for line in output: if re.search("kernel\.dmesg\_restrict", line): kernelopt = True sline = line.split('=') if len(sline) > 1: if str(sline[1]).strip() == '0': self.compliant = False self.detailedresults += "\nKernel message buffer is currently not restricted." else: self.compliant = False self.detailedresults += "\nCould not determine the state of the kernel message buffer access restrictions." if not kernelopt: self.compliant = False self.detailedresults += "\nCould not determine the state of the kernel message buffer access restrictions." except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant def fix(self): '''run fix actions for this rule :returns: success :rtype: bool @author: Breen Malmberg ''' success = True self.detailedresults = "" try: self.ch.executeCommand(self.fixcommand) rcode = self.ch.getReturnCode() errmsg = self.ch.getErrorString() if rcode != '0': success = False self.detailedresults += "\n" + str(errmsg) except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", success, self.detailedresults) return success