class DisableGUILogon(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 105 self.rulename = "DisableGUILogon" self.formatDetailedResults("initialize") self.mandatory = False self.applicable = {'type': 'white', 'family': ['linux']} # Configuration item instantiation datatype = "bool" key = "DISABLEX" instructions = "To enable this item, set the value of DISABLEX " + \ "to True. When enabled, this rule will disable the automatic " + \ "GUI login, and the system will instead boot to the console " + \ "(runlevel 3). This will not remove any GUI components, and the " + \ "GUI can still be started using the \"startx\" command." default = False self.ci1 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "LOCKDOWNX" instructions = "To enable this item, set the value of LOCKDOWNX " + \ "to True. When enabled, this item will help secure X Windows by " + \ "disabling the X Font Server (xfs) service and disabling X " + \ "Window System Listening. This item should be enabled if X " + \ "Windows is disabled but will be occasionally started via " + \ "startx, unless there is a mission-critical need for xfs or " + \ "a remote display." default = False self.ci2 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "REMOVEX" instructions = "To enable this item, set the value of REMOVEX " + \ "to True. When enabled, this item will COMPLETELY remove X " + \ "Windows from the system, and on most platforms will disable " + \ "any currently running display manager. It is therefore " + \ "recommended that this rule be run from a console session " + \ "rather than from the GUI.\nREMOVEX cannot be undone." default = False self.ci3 = self.initCi(datatype, key, instructions, default) self.guidance = ["NSA 3.6.1.1", "NSA 3.6.1.2", "NSA 3.6.1.3", "CCE 4462-8", "CCE 4422-2", "CCE 4448-7", "CCE 4074-1"] self.iditerator = 0 self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.sh = ServiceHelper(self.environ, self.logger) self.myos = self.environ.getostype().lower() self.sethelptext() def report(self): '''@author: Eric Ball :param self: essential if you override this definition :returns: bool - True if system is compliant, False if it isn't ''' try: compliant = True results = "" if os.path.exists("/bin/systemctl"): self.initver = "systemd" compliant, results = self.reportSystemd() elif re.search("debian", self.myos): self.initver = "debian" compliant, results = self.reportDebian() elif re.search("ubuntu", self.myos): self.initver = "ubuntu" compliant, results = self.reportUbuntu() else: self.initver = "inittab" compliant, results = self.reportInittab() # NSA guidance specifies disabling of X Font Server (xfs), # however, this guidance seems to be obsolete as of RHEL 6, # and does not apply to the Debian family. if self.sh.auditService("xfs", _="_"): compliant = False results += "xfs is currently enabled\n" xremoved = True self.xservSecure = True if re.search("debian|ubuntu", self.myos): if self.ph.check("xserver-xorg-core"): compliant = False xremoved = False results += "Core X11 components are present\n" elif re.search("opensuse", self.myos): if self.ph.check("xorg-x11-server"): compliant = False xremoved = False results += "Core X11 components are present\n" else: if self.ph.check("xorg-x11-server-Xorg"): compliant = False xremoved = False results += "Core X11 components are present\n" # Removing X will take xserverrc with it. If X is not present, we # do not need to check for xserverrc if not xremoved: self.serverrc = "/etc/X11/xinit/xserverrc" self.xservSecure = False if os.path.exists(self.serverrc): serverrcText = readFile(self.serverrc, self.logger) if re.search("opensuse", self.myos): for line in serverrcText: reSearch = r'exec (/usr/bin/)?X \$dspnum.*\$args' if re.search(reSearch, line): self.xservSecure = True break else: for line in serverrcText: reSearch = r'^exec (/usr/bin/)?X (:0 )?-nolisten tcp ("$@"|.?\$@)' if re.search(reSearch, line): self.xservSecure = True break if not self.xservSecure: compliant = False results += self.serverrc + " does not contain proper " \ + "settings to disable X Window System " + \ "Listening/remote display\n" else: compliant = False results += self.serverrc + " does not exist; X Window " + \ "System Listening/remote display has not " + \ "been disabled\n" self.detailedresults = results self.compliant = compliant except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults = "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def reportInittab(self): compliant = False results = "" inittab = "/etc/inittab" if os.path.exists(inittab): initData = readFile(inittab, self.logger) for line in initData: if line.strip() == "id:3:initdefault:": compliant = True break else: self.logger.log(LogPriority.ERROR, inittab + " not found, init " + "system unknown") if not compliant: results = "inittab not set to runlevel 3; GUI logon is enabled\n" return compliant, results def reportSystemd(self): compliant = True results = "" cmd = ["/bin/systemctl", "get-default"] self.ch.executeCommand(cmd) defaultTarget = self.ch.getOutputString() if not re.search("multi-user.target", defaultTarget): compliant = False results = "systemd default target is not multi-user.target; " + \ "GUI logon is enabled\n" return compliant, results def reportDebian(self): compliant = True results = "" dmlist = ["gdm", "gdm3", "lightdm", "xdm", "kdm"] for dm in dmlist: if self.sh.auditService(dm, _="_"): compliant = False results = dm + \ " is still in init folders; GUI logon is enabled\n" return compliant, results def reportUbuntu(self): compliant = True results = "" ldmover = "/etc/init/lightdm.override" grub = "/etc/default/grub" if os.path.exists(ldmover): lightdmText = readFile(ldmover, self.logger) if not re.search("manual", lightdmText[0], re.IGNORECASE): compliant = False results += ldmover + ' exists, but does not contain text ' + \ '"manual". GUI logon is still enabled\n' else: compliant = False results += ldmover + " does not exist; GUI logon is enabled\n" if os.path.exists(grub): tmppath = grub + ".tmp" data = {"GRUB_CMDLINE_LINUX_DEFAULT": '"quiet"'} editor = KVEditorStonix(self.statechglogger, self.logger, "conf", grub, tmppath, data, "present", "closedeq") if not editor.report(): compliant = False results += grub + " does not contain the correct values: " + \ str(data) else: compliant = False results += "Cannot find file " + grub if not compliant: results += "/etc/init does not contain proper override file " + \ "for lightdm; GUI logon is enabled\n" return compliant, results def fix(self): '''@author: Eric Ball :param self: essential if you override this definition :returns: bool - True if fix is successful, False if it isn't ''' try: if not self.ci1.getcurrvalue() and not self.ci2.getcurrvalue() \ and not self.ci3.getcurrvalue(): return success = True self.detailedresults = "" # Delete past state change records from previous fix self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) # If we are doing DISABLEX or REMOVEX, we want to boot to # non-graphical multi-user mode. if self.ci1.getcurrvalue() or self.ci3.getcurrvalue(): success &= self.fixBootMode() # Since LOCKDOWNX depends on having X installed, and REMOVEX # completely removes X from the system, LOCKDOWNX fix will only be # executed if REMOVEX is not. if self.ci3.getcurrvalue(): success &= self.fixRemoveX() elif self.ci2.getcurrvalue(): success &= self.fixLockdownX() 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 fixBootMode(self): success = True if self.initver == "systemd": cmd = ["/bin/systemctl", "set-default", "multi-user.target"] if not self.ch.executeCommand(cmd): success = False self.detailedresults += '"systemctl set-default ' \ + 'multi-user.target" did not succeed\n' else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) commandstring = "/bin/systemctl set-default " + \ "graphical.target" event = {"eventtype": "commandstring", "command": commandstring} self.statechglogger.recordchgevent(myid, event) elif self.initver == "debian": dmlist = ["gdm", "gdm3", "lightdm", "xdm", "kdm"] for dm in dmlist: cmd = ["update-rc.d", "-f", dm, "disable"] if not self.ch.executeCommand(cmd): self.detailedresults += "Failed to disable desktop " + \ "manager " + dm else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "servicehelper", "servicename": dm, "startstate": "enabled", "endstate": "disabled"} self.statechglogger.recordchgevent(myid, event) elif self.initver == "ubuntu": ldmover = "/etc/init/lightdm.override" tmpfile = ldmover + ".tmp" created = False if not os.path.exists(ldmover): createFile(ldmover, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": ldmover} self.statechglogger.recordchgevent(myid, event) created = True writeFile(tmpfile, "manual\n", self.logger) if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": ldmover} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(ldmover, tmpfile, myid) os.rename(tmpfile, ldmover) resetsecon(ldmover) grub = "/etc/default/grub" if not os.path.exists(grub): createFile(grub, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": grub} self.statechglogger.recordchgevent(myid, event) tmppath = grub + ".tmp" data = {"GRUB_CMDLINE_LINUX_DEFAULT": '"quiet"'} editor = KVEditorStonix(self.statechglogger, self.logger, "conf", grub, tmppath, data, "present", "closedeq") editor.report() if editor.fixables: if editor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) editor.setEventID(myid) debug = "kveditor fix ran successfully\n" self.logger.log(LogPriority.DEBUG, debug) if editor.commit(): debug = "kveditor commit ran successfully\n" self.logger.log(LogPriority.DEBUG, debug) else: error = "kveditor commit did not run " + \ "successfully\n" self.logger.log(LogPriority.ERROR, error) success = False else: error = "kveditor fix did not run successfully\n" self.logger.log(LogPriority.ERROR, error) success = False cmd = "update-grub" self.ch.executeCommand(cmd) else: inittab = "/etc/inittab" tmpfile = inittab + ".tmp" if os.path.exists(inittab): initText = open(inittab, "r").read() initre = r"id:\d:initdefault:" if re.search(initre, initText): initText = re.sub(initre, "id:3:initdefault:", initText) writeFile(tmpfile, initText, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": inittab} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(inittab, tmpfile, myid) os.rename(tmpfile, inittab) resetsecon(inittab) else: initText += "\nid:3:initdefault:\n" writeFile(tmpfile, initText, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": inittab} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(inittab, tmpfile, myid) os.rename(tmpfile, inittab) resetsecon(inittab) else: self.detailedresults += inittab + " not found, no other " + \ "init system found. If you are using a supported " + \ "Linux OS, please report this as a bug\n" return success def fixRemoveX(self): success = True # Due to automatic removal of dependent packages, the full # removal of X and related packages cannot be undone if re.search("opensuse", self.myos): cmd = ["zypper", "-n", "rm", "-u", "xorg-x11*", "kde*", "xinit*"] self.ch.executeCommand(cmd) elif re.search("debian|ubuntu", self.myos): xpkgs = ["unity.*", "xserver-xorg-video-ati", "xserver-xorg-input-synaptics", "xserver-xorg-input-wacom", "xserver-xorg-core", "xserver-xorg", "lightdm.*", "libx11-data"] for xpkg in xpkgs: self.ph.remove(xpkg) elif re.search("fedora", self.myos): # Fedora does not use the same group packages as other # RHEL-based OSs. Removing this package will remove the X # Windows system, just less efficiently than using a group self.ph.remove("xorg-x11-server-Xorg") self.ph.remove("xorg-x11-xinit*") else: cmd = ["yum", "groups", "mark", "convert"] self.ch.executeCommand(cmd) self.ph.remove("xorg-x11-xinit") self.ph.remove("xorg-x11-server-Xorg") cmd2 = ["yum", "groupremove", "-y", "X Window System"] if not self.ch.executeCommand(cmd2): success = False self.detailedresults += '"yum groupremove -y X Window System" command failed\n' return success def fixLockdownX(self): success = True if self.sh.disableService("xfs", _="_"): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "servicehelper", "servicename": "xfs", "startstate": "enabled", "endstate": "disabled"} self.statechglogger.recordchgevent(myid, event) else: success = False self.detailedresults += "STONIX was unable to disable the " + \ "xfs service\n" if not self.xservSecure: serverrcString = "exec X :0 -nolisten tcp $@" if not os.path.exists(self.serverrc): createFile(self.serverrc, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.serverrc} self.statechglogger.recordchgevent(myid, event) writeFile(self.serverrc, serverrcString, self.logger) else: open(self.serverrc, "a").write(serverrcString) return success
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 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 DisableAFPFileSharing(RuleKVEditor): '''AFP & SMB start up and stop AFP Service Starting and Stopping AFP Service To start AFP service: $ sudo serveradmin start afp To stop AFP service: $ sudo serveradmin stop afp Checking AFP Service Status To see if AFP service is running: $ sudo serveradmin status afp To see complete AFP status: $ sudo serveradmin fullstatus afp Viewing AFP Settings To list all AFP service settings: $ sudo serveradmin settings afp To list a particular setting: $ sudo serveradmin settings afp:setting This method disables AFP file sharing on mac os x systems @author: Breen Malmberg ''' ############################################################################### def __init__(self, config, environ, logger, statechglogger): RuleKVEditor.__init__(self, config, environ, logger, statechglogger) self.rulenumber = 164 self.rulename = 'DisableAFPFileSharing' self.formatDetailedResults("initialize") self.mandatory = True self.logger = logger self.rootrequired = True self.guidance = ['CIS 1.4.14.3'] self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } if self.environ.getostype() == "Mac OS X": self.addKVEditor( "DisableAFPFileSharing", "defaults", "/System/Library/LaunchDaemons/com.apple.AppleFileServer", "", {"Disabled": ["1", "-bool True"]}, "present", "", "Disable AFP File Sharing", None, False, {}) self.initObjs() self.determineOrigAFPstatus() self.sethelptext() def initObjs(self): '''initialize any objects to be used by this class :returns: void @author: Breen Malmberg ''' self.cmdhelper = CommandHelper(self.logger) def determineOrigAFPstatus(self): '''store the original operational state/status of Apple File Server as a bool ''' # default init self.afporigstatus = False # if version = 10.10.*, then use KVEditor and ignore the other code if not re.search("10\.10.*", self.environ.getosver(), re.IGNORECASE): getafpstatuscmd = "launchctl list com.apple.AppleFileServer" self.cmdhelper.executeCommand(getafpstatuscmd) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) outputstr = self.cmdhelper.getOutputString() if not re.search("Could not find", outputstr, re.IGNORECASE): self.afporigstatus = True def fix(self): '''disable Apple File Sharing service :returns: success :rtype: bool @author: Breen Malmberg ''' success = True try: # if version = 10.10.*, then use KVEditor and ignore the other code if re.search("10\.10.*", self.environ.getosver(), re.IGNORECASE): RuleKVEditor.fix(self) else: clientfixpath1 = "/System/Library/LaunchDaemons/com.apple.AppleFileServer" clientfixtool = "launchctl" clientfixcmd1 = clientfixtool + " unload " + clientfixpath1 # the below 'path' is actually an alias # which is understood by launchctl. # in mac terminology, this is called a 'target' clientfixpath2 = "system/com.apple.AppleFileServer" clientfixcmd2 = clientfixtool + " disable " + clientfixpath2 self.cmdhelper.executeCommand(clientfixcmd1) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) success = False self.cmdhelper.executeCommand(clientfixcmd2) retcode = self.cmdhelper.getReturnCode() if retcode != 0: errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) success = False if success: self.detailedresults += "\nApple File Server has successfully been disabled." self.formatDetailedResults('fix', success, self.detailedresults) return success except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) def undo(self): '''restore Apple File Sharing service to its original state :returns: void @author: Breen Malmberg ''' success = True try: # if version = 10.10.*, then use KVEditor and ignore the other code if re.search("10\.10.*", self.environ.getosver(), re.IGNORECASE): RuleKVEditor.undo(self) else: if not self.afporigstatus: undocmd1 = "launchctl enable system/com.apple.AppleFileServer" undocmd2 = "launchctl load /System/Library/LaunchDaemons/com.apple.AppleFileServer.plist" self.cmdhelper.executeCommand(undocmd1) retcode = self.cmdhelper.getReturnCode() if retcode != 0: success = False errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) self.cmdhelper.executeCommand(undocmd2) retcode = self.cmdhelper.getReturnCode() if retcode != 0: success = False errstr = self.cmdhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) if not success: self.detailedresults += "\nUndo failed to restore Apple File Sharing to its original state on this system." else: self.detailedresults += "\nUndo has successfully restored Apple File Sharing to its original state on this system." except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults('undo', success, self.detailedresults) return success
class DisablePrelinking(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 89 self.rulename = "DisablePrelinking" self.sethelptext() self.formatDetailedResults("initialize") self.mandatory = True self.applicable = {'type': 'white', 'family': ['linux']} # Configuration item instantiation datatype = "bool" key = "DISABLEPRELINKING" instructions = "To disable this rule, set the value of " + \ "DISABLEPRELINKING to false." default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ["CCE-RHEL7-CCE-TBA 2.1.3.1.2"] self.iditerator = 0 self.ch = CommandHelper(self.logger) if re.search("debian|ubuntu", self.environ.getostype().lower()): self.isDebian = True else: self.isDebian = False def report(self): try: if self.isDebian: path = "/etc/default/prelink" else: path = "/etc/sysconfig/prelink" self.path = path prelink = "/usr/sbin/prelink" self.compliant = True self.detailedresults = "" if os.path.exists(path): tmppath = path + ".tmp" data = {"PRELINKING": "no"} 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" if os.path.exists(prelink): self.ch.executeCommand([prelink, "-p"]) output = self.ch.getOutputString() splitout = output.split() try: if len(splitout) > 0: numPrelinks = int(splitout[0]) # Potential ValueError if numPrelinks > 0: self.compliant = False self.detailedresults += "There are currently " + \ str(numPrelinks) + " prelinked binaries.\n" except ValueError: debug = "Unexpected result from " + prelink + ". This " + \ "does not affect compliance." self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += debug + "\n" 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 success = True path = self.path tmppath = path + ".tmp" prelinkCache = "/etc/prelink.cache" self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) 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: success = False self.detailedresults += "Failed to create file: " + \ path + "\n" if writeFile(tmppath, "PRELINKING=no", self.logger): os.rename(tmppath, path) resetsecon(path) else: success = False self.detailedresults += "Failed to write settings " + \ "to file: " + path + "\n" elif not self.editor.report(): if self.editor.fix(): if self.editor.commit(): self.detailedresults += "Changes successfully " + \ "committed to " + path + "\n" else: success = False self.detailedresults += "Changes could not be " + \ "committed to " + path + "\n" else: success = False self.detailedresults += "Could not fix file " + path + "\n" # Although the guidance and documentation recommends using "prelink # -ua" command, testing has shown this command to be completely # unreliable. Instead, the prelink cache will be removed entirely. if os.path.exists(prelinkCache): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordfiledelete(prelinkCache, myid) os.remove(prelinkCache) 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
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 Dnf(object): '''The template class that provides a framework that must be implemented by all platform specific pkgmgr classes. Specifically for Fedora :version: :author:Derek T Walker 08-13-2015''' def __init__(self, logger): self.logger = logger self.detailedresults = "" self.ch = CommandHelper(self.logger) self.install = "/usr/bin/dnf install -y " self.remove = "/usr/bin/dnf remove -y " self.search = "/usr/bin/dnf search " 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''' try: installed = False self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: installed = True self.detailedresults = package + \ " pkg installed successfully\n" else: self.detailedresults = package + " pkg not able to install\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return installed 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: removed = False self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: removed = True self.detailedresults += package + " pkg removed successfully\n" else: self.detailedresults += package + \ " pkg not able to be removed\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return removed 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: found = False self.ch.executeCommand(self.rpm + package) if self.ch.getReturnCode() == 0: found = True self.detailedresults += package + " pkg found\n" else: self.detailedresults += package + " pkg not found\n" 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(self.detailedresults) ############################################################################### def checkAvailable(self, package): try: found = False self.ch.executeCommand(self.search + package) output = self.ch.getOutputString() if re.search("no matches found", output.lower()): self.detailedresults += package + " pkg is not available " + \ " or may be misspelled\n" elif re.search("matched", output.lower()): self.detailedresults += package + " pkg is available\n" found = True 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(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 self.install ############################################################################### def getRemove(self): return self.remove
class PasswordExpiration(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 42 self.rulename = "PasswordExpiration" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.iditerator = 0 self.guidance = ["2.3.1.7"] self.applicable = {'type': 'black', 'family': ['darwin']} self.universal = "#The following lines were added by stonix\n" datatype = 'bool' key = 'PASSWORDEXPIRATION' instructions = "To disable this rule set the value of " + \ "PASSWORDEXPIRATION to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.libusercreate = False self.libuserinstall = False self.useraddcreate = False self.logindefcreate = False self.fixable, self.shadow = True, True self.editor1, self.editor2 = "", "" self.fixusers = [] ############################################################################### def report(self): try: self.detailedresults = "" self.ch = CommandHelper(self.logger) self.lockedpwds = '^\*LK\*|^!|^\*|^x$' if self.environ.getosfamily() == "linux": self.ph = Pkghelper(self.logger, self.environ) self.specs = {"PASS_MAX_DAYS": "180", "PASS_MIN_DAYS": "1", "PASS_MIN_LEN": "8", "PASS_WARN_AGE": "28"} if self.ph.manager in ("apt-get", "zypper"): # apt-get systems do not set min length in the same file # as other systems(login.defs) del self.specs["PASS_MIN_LEN"] self.shadowfile = "/etc/shadow" self.logdeffile = "/etc/login.defs" self.useraddfile = "/etc/default/useradd" self.libuserfile = "/etc/libuser.conf" self.compliant = self.reportLinux() elif self.environ.getosfamily() == "solaris": self.specs = {"PASSLENGTH": "8", "MINWEEKS": "1", "MAXWEEKS": "26", "WARNWEEKS": "4"} self.shadowfile = "/etc/shadow" self.logdeffile = "/etc/default/passwd" self.compliant = self.reportSolaris() elif self.environ.getosfamily() == "freebsd": self.specs = {"warnpassword": "******", "minpasswordlen": "8", "passwordtime": "180d"} self.shadowfile = "/etc/master.passwd" self.loginfile = "/etc/login.conf" self.compliant = self.reportFreebsd() except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant ############################################################################### def reportLinux(self): compliant1 = self.checklogindefs() compliant2 = self.chkShadow() compliant3 = self.chkUserAdd() compliant4 = self.checklibuser() if compliant1 and compliant2 and compliant3 and compliant4: return True else: return False ############################################################################### def reportSolaris(self): compliant1 = self.checklogindefs() compliant2 = self.chkShadow() if compliant1 and compliant2: return True else: return False ############################################################################### def reportFreebsd(self, specs): compliant1 = self.chkPasswd() compliant2 = self.chkLogin(specs) if compliant1 and compliant2: return True else: return False ############################################################################### def checklogindefs(self): '''report method for various distros of linux and solaris''' compliant = True debug = "" if not os.path.exists(self.logdeffile): compliant = False self.detailedresults += self.logdeffile + " file does not exist\n" elif not checkPerms(self.logdeffile, [0, 0, 0o644], self.logger): compliant = False self.detailedresults += self.logdeffile + " does not have " + \ "the correct permissions. Expected 644, found " + \ str(getOctalPerms(self.logdeffile)) + ".\n" tmpfile = self.logdeffile + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.logdeffile, tmpfile, self.specs, "present", "space") if not self.editor1.report(): self.detailedresults += self.logdeffile + " does not " + \ "contain the correct contents\n" debug = self.logdeffile + " doesn't contain the correct " + \ "contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False return compliant ############################################################################### def chkShadow(self): debug = "" compliant = True if os.path.exists(self.shadowfile): if self.ph.manager == "apt-get": statdata = os.stat(self.shadowfile) mode = stat.S_IMODE(statdata.st_mode) retval = getUserGroupName(self.shadowfile) if retval[0] != "root" or retval[1] != "shadow": compliant = False self.detailedresults += self.shadowfile + " ownership " + \ "is not correct (either owner is not root, or " + \ "group is not shadow).\n" if mode != 0o640: compliant = False self.detailedresults += self.shadowfile + " does not have " + \ "the correct permissions. Expected 640, found " + \ str(getOctalPerms(self.shadowfile)) + ".\n" elif not checkPerms(self.shadowfile, [0, 0, 0o400], self.logger) and \ not checkPerms(self.shadowfile, [0, 0, 0], self.logger): compliant = False self.detailedresults += self.shadowfile + " does not have " + \ "the correct permissions. Expected 400 or 0, found " + \ str(getOctalPerms(self.shadowfile)) + ".\n" contents = readFile(self.shadowfile, self.logger) if self.environ.getosfamily() == "solaris" or \ self.environ.getosfamily() == "linux": if self.environ.getosfamily() == "linux": whichid = "/usr/bin/id" elif self.environ.getosfamily() == "solaris": whichid = "/usr/xpg4/bin/id" for line in contents: badacct = False debug = "" if re.search("^\#", line) or re.match("^\s*$", line): continue if re.search(":", line): field = line.split(":") cmd = [whichid, "-u", field[0]] self.ch.executeCommand(cmd) output = self.ch.getOutputString().strip() error = self.ch.getError() if error: continue if output: if output.isdigit(): uid = int(output) else: uid = 100 else: continue try: if uid >= 500 and not re.search(self.lockedpwds, field[1]): for i in [3, 4, 5, 6]: if field[i]: val = field[i] if val.isdigit(): field[i] = int(field[i]) elif i == 6: field[i] = 99 else: field[i] = 0 elif i == 6: field[i] = 99 else: field[i] = 0 if field[3] != 1 or field[3] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Minimum age is not equal to 1\n" badacct = True if field[4] > 180 or field[4] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Expiration is not 180 or less\n" badacct = True if field[5] != 28 or field[5] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Password expiration warnings are " + \ "not set to 28 days\n" badacct = True if field[6] != 35 or field[6] == "": compliant = False self.detailedresults += "Shadow file: " + \ "Account lock is not set to 35 days\n" badacct = True except IndexError: compliant = False debug = traceback.format_exc() debug += ' Index out of range\n' badacct = True if debug: self.logger.log(LogPriority.DEBUG, debug) if badacct: self.fixusers.append(field[0]) if self.environ.getosfamily() == 'freebsd': for line in contents: debug = "" if re.search("^\#", line) or re.match('^\s*$', line): continue if re.search(':', line): field = line.split(':') message = Popen(['/usr/bin/id', '-u', field[0]], stderr=PIPE, stdout=PIPE, shell=False) uid = message.stdout.readline() uid = uid.strip() message.stdout.close() if uid.isdigit(): uid = int(uid) else: uid = 100 try: if uid >= 500 and not re.search(self.lockedpwds, field[1]): for i in [5, 6]: if field[i]: val = field[i] if not val.isdigit(): field[i] = 0 else: field[i] = 0 if int(field[5]) > 180 or field[5] == "": self.shadow = False compliant = False debug += "expiration is not 180 or less" if int(field[6]) != 1 or field[6] == "": self.shadow = False compliant = False debug += "Account lock is not set to 1" except IndexError: self.shadow = False compliant = False debug = traceback.format_exc() debug += ' Index out of range' self.logger.log(LogPriority.DEBUG, debug) if debug: self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += self.shadowfile + " does not exist\n" compliant = False debug = "chkShadow method is returning " + str(compliant) + \ " compliance\n" self.logger.log(LogPriority.DEBUG, debug) return compliant ############################################################################### def chkUserAdd(self): compliant = True debug = "" if not os.path.exists(self.useraddfile): self.detailedresults += self.useraddfile + " file does not exist\n" compliant = False else: if not checkPerms(self.useraddfile, [0, 0, 0o600], self.logger): compliant = False self.detailedresults += self.useraddfile + " does not have " + \ "the correct permissions. Expected 600, found " + \ str(getOctalPerms(self.useraddfile)) + ".\n" contents = readFile(self.useraddfile, self.logger) found = False valcorrect = True for line in contents: if re.search("^\#", line) or re.match('^\s*$', line): continue if re.search('^INACTIVE', line.strip()) and re.search('=', line): found = True temp = line.split('=') if int(temp[1].strip()) <= -1 or int(temp[1].strip()) > 35: valcorrect = False break if not found: compliant = False self.detailedresults += "INACTIVE key was not found in " + \ self.useraddfile + "\n" if found and not valcorrect: compliant = False self.detailedresults += "INACTIVE key was found in " + \ self.useraddfile + ", but value is incorrect\n" debug += "chkUserAdd method is returning " + str(compliant) + \ " compliance\n" if debug: self.logger.log(LogPriority.DEBUG, debug) return compliant ############################################################################### def checklibuser(self): '''Private method to check the password hash algorithm settings in libuser.conf. @author: dwalker :returns: bool ''' compliant = True '''check if libuser is intalled''' if not self.ph.check("libuser"): '''if not, check if available''' if self.ph.checkAvailable("libuser"): self.detailedresults += "libuser available but not installed\n" return False else: '''not available, not a problem''' return True '''create a kveditor for file if it exists, if not, we do it in the setlibuser method inside the fix''' if os.path.exists(self.libuserfile): data = {"userdefaults": {"LU_SHADOWMAX": "", "LU_SHADOWMIN": "", "LU_SHADOWWARNING": "", "LU_UIDNUMBER": "", "LU_SHADOWINACTIVE": "", "LU_SHADOWEXPIRE": ""}} datatype = "tagconf" intent = "notpresent" tmppath = self.libuserfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") if not self.editor2.report(): debug = "/etc/libuser.conf doesn't contain the correct " + \ "contents\n" self.detailedresults += "/etc/libuser.conf doesn't " + \ "contain the correct contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.libuserfile + "\n" compliant = False else: self.detailedresults += "Libuser installed but libuser " + \ "file doesn't exist\n" compliant = False return compliant ############################################################################### def chkLogin(self): compliant = True if os.path.exists(self.loginfile): if not checkPerms(self.loginfile, [0, 0, 0o644], self.logger): compliant = False self.detailedresults += self.libuserfile + " does not have " + \ "the correct permissions. Expected 644, found " + \ str(getOctalPerms(self.libuserfile)) + ".\n" contents = readFile(self.loginfile, self.logger) iterator1 = 0 for line in contents: if re.search("^#", line) or re.match('^\s*$', line): iterator1 += 1 elif re.search('^default:\\\\$', line.strip()): found = True temp = contents[iterator1 + 1:] length2 = len(temp) - 1 iterator2 = 0 for line2 in temp: if re.search('^[^:][^:]*:\\\\$', line2): contents2 = temp[:iterator2] break elif iterator2 < length2: iterator2 += 1 elif iterator2 == length2: contents2 = temp[:iterator2] break else: iterator1 += 1 if contents2: for key in self.Fspecs: found = False for line in contents2: if re.search("^#", line) or re.match('^\s*$', line): continue elif re.search('^:' + key, line.strip()): if re.search('=', line): temp = line.split('=') if re.search(str(self.Fspecs[key]) + '(:\\\\|:|\\\\|\s)', temp[1]): found = True continue else: found = False break if not found: compliant = False return compliant else: self.detailedresults += self.loginfile + "does not exist. " + \ "Please note that the fix for this rule will not attempt " + \ "to create this file.\n" compliant = False debug = "chkLogin method is returning " + (compliant) + " compliance\n" self.logger.log(LogPriority.DEBUG, debug) return compliant def fix(self): try: if not self.ci.getcurrvalue(): return self.detailedresults = "" # clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getosfamily() == "linux": self.rulesuccess = self.fixLinux() if self.environ.getosfamily() == "solaris": self.rulesuccess = self.fixSolaris() if self.environ.getosfamily() == "freebsd": self.rulesuccess = self.fixFreebsd() except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess ############################################################################### def fixLinux(self): success1 = self.fixLogDef(self.specs) success2 = self.fixShadow() success3 = self.fixUserAdd() success4 = self.setlibuser() if success1 and success2 and success3 and success4: return True else: return False ############################################################################### def fixSolaris(self): success1 = self.fixLogDef() success2 = self.fixShadow() if success1 and success2: return True else: return False ############################################################################### def fixFreebsd(self): success1 = self.fixPasswd() success2 = self.fixLogin() if success1 and success2: return True else: return False ############################################################################### def fixLogDef(self, specs): success = True debug = "" if not os.path.exists(self.logdeffile): if createFile(self.logdeffile, self.logger): self.logindefcreate = True setPerms(self.logdeffile, [0, 0, 0o644], self.logger) tmpfile = self.logdeffile + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.logdeffile, tmpfile, specs, "present", "space") else: self.detailedresults += "Was not able to create " + \ self.logdeffile + " file\n" success = False if self.logindefcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.logdeffile} self.statechglogger.recordchgevent(myid, event) elif not checkPerms(self.logdeffile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.logdeffile, [0, 0, 0o644], self.logger, self.statechglogger, myid): debug += "permissions not correct on: " + \ self.logdeffile + "\n" success = False if self.editor1.fixables or self.editor1.removeables: if not self.logindefcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): debug += "fixLogDef editor.fix did not complete successfully\n" success = False elif not self.editor1.commit(): debug += "fixLogDef editor.commit did not complete successfully\n" success = False os.chown(self.logdeffile, 0, 0) os.chmod(self.logdeffile, 0o644) resetsecon(self.logdeffile) if debug: self.logger.log(LogPriority.DEBUG, debug) return success ############################################################################### def fixShadow(self): success = True if not os.path.exists(self.shadowfile): self.detailedresults += self.shadowfile + "does not exist. \ Will not perform fix on shadow file\n" return False if self.fixusers: contents = readFile(self.shadowfile, self.logger) if self.ph.manager == "apt-get": perms = [0, 42, 0o640] else: perms = [0, 0, 0o400] if not checkPerms(self.shadowfile, perms, self.logger) and \ not checkPerms(self.shadowfile, [0, 0, 0], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) setPerms(self.shadowfile, perms, self.logger, self.statechglogger, myid) tmpdate = strftime("%Y%m%d") tmpdate = list(tmpdate) date = tmpdate[0] + tmpdate[1] + tmpdate[2] + tmpdate[3] + "-" + \ tmpdate[4] + tmpdate[5] + "-" + tmpdate[6] + tmpdate[7] for user in self.fixusers: cmd = ["chage", "-d", date, "-m", "1", "-M", "180", "-W", "28", "-I", "35", user] self.ch.executeCommand(cmd) # We have to do some gymnastics here, because chage writes directly # to /etc/shadow, but statechglogger expects the new contents to # be in a temp file. newContents = readFile(self.shadowfile, self.logger) shadowTmp = "/tmp/shadow.stonixtmp" createFile(shadowTmp, self.logger) writeFile(shadowTmp, "".join(newContents) + "\n", self.logger) writeFile(self.shadowfile, "".join(contents) + "\n", self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': self.shadowfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.shadowfile, shadowTmp, myid) shutil.move(shadowTmp, self.shadowfile) os.chmod(self.shadowfile, perms[2]) os.chown(self.shadowfile, perms[0], perms[1]) resetsecon(self.shadowfile) return success ############################################################################### def fixUserAdd(self): success = True if not os.path.exists(self.useraddfile): if createFile(self.useraddfile, self.logger): self.useraddcreate = True setPerms(self.useraddfile, [0, 0, 0o600], self.logger) else: self.detailedresults += self.useraddfile + \ " could not be created\n" success = False if self.useraddcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.useraddfile} self.statechglogger.recordchgevent(myid, event) if not checkPerms(self.useraddfile, [0, 0, 0o600], self.logger): if not self.useraddcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.useraddfile, [0, 0, 0o600], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions on " + \ self.useraddfile success = False tempstring = "" contents = readFile(self.useraddfile, self.logger) found = False for line in contents: if re.search("^\#", line) or re.match('^\s*$', line): tempstring += line continue if re.search("^INACTIVE", line.strip()): if re.search("=", line): temp = line.split("=") if int(temp[1].strip()) <= -1 or \ int(temp[1].strip()) > 35: continue else: found = True tempstring += line else: continue elif re.search("^" + self.universal, line.strip()): continue else: tempstring += line if not found: tempstring += "INACTIVE=35\n" tmpfile = self.useraddfile + ".tmp" if not writeFile(tmpfile, tempstring, self.logger): return False if not self.useraddcreate: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': self.useraddfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.useraddfile, tmpfile, myid) shutil.move(tmpfile, self.useraddfile) os.chown(self.useraddfile, 0, 0) os.chmod(self.useraddfile, 0o600) resetsecon(self.useraddfile) return success ############################################################################### def setlibuser(self): success = True debug = "" created = False data = {"userdefaults": {"LU_SHADOWMAX": "", "LU_SHADOWMIN": "", "LU_SHADOWWARNING": "", "LU_UIDNUMBER": "", "LU_SHADOWINACTIVE": "", "LU_SHADOWEXPIRE": ""}} '''check if installed''' if not self.ph.check("libuser"): '''if not installed, check if available''' if self.ph.checkAvailable("libuser"): '''if available, install it''' if not self.ph.install("libuser"): self.detailedresults += "Unable to install libuser\n" return False else: '''since we're just now installing it we know we now need to create the kveditor''' self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) datatype = "tagconf" intent = "notpresent" tmppath = self.libuserfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") self.editor2.report() else: return True if not os.path.exists(self.libuserfile): if not createFile(self.libuserfile, self.logger): self.detailedresults += "Unable to create libuser file\n" debug = "Unable to create the libuser file\n" self.logger.log(LogPriority.DEBUG, debug) return False created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.libuserfile} self.statechglogger.recordchgevent(myid, event) tmppath = self.libuserfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.libuserfile, tmppath, data, "notpresent", "openeq") self.editor2.report() if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.libuserfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions on " + \ self.libuserfile success = False elif not setPerms(self.libuserfile, [0, 0, 0o644], self.logger): success = False self.detailedresults += "Unable to set the " + \ "permissions on " + self.libuserfile + "\n" if self.editor2.removeables: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if self.editor2.fix(): if self.editor2.commit(): debug += "/etc/libuser.conf has been corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.libuserfile, 0, 0) os.chmod(self.libuserfile, 0o644) resetsecon(self.libuserfile) else: self.detailedresults += "/etc/libuser.conf " + \ "couldn't be corrected\n" success = False else: self.detailedresults += "/etc/libuser.conf couldn't " + \ "be corrected\n" success = False return success ############################################################################### def fixLogin(self): success = True tempstring = "" debug = "" if not os.path.exists(self.loginfile): self.detailedresults = self.loginfile + "does not exist. \ Will not perform fix on useradd file\n" return False if not checkPerms(self.loginfile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.loginfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False contents = readFile(self.loginfile, self.logger) iterator1 = 0 for line in contents: if re.search("^#", line) or re.match('^\s*$', line): iterator1 += 1 elif re.search('^default:\\\\$', line.strip()): contents1 = contents[:iterator1 + 1] temp = contents[iterator1 + 1:] length2 = len(temp) - 1 iterator2 = 0 for line2 in temp: if re.search('^[^:][^:]*:\\\\$', line2): contents3 = temp[iterator2:] contents2 = temp[:iterator2] break elif iterator2 < length2: iterator2 += 1 elif iterator2 == length2: contents2 = temp[:iterator2] break else: iterator1 += 1 if contents2: for key in self.Fspecs: iterator = 0 found = False for line in contents2: if re.search("^#", line) or re.match('^\s*$', line): iterator += 1 continue elif re.search('^:' + key, line.strip()): if re.search('=', line): temp = line.split('=') if re.search(str(self.Fspecs[key]) + '(:\\\\|:|\\\\|\s)', temp[1]): iterator += 1 found = True else: contents2.pop(iterator) else: iterator += 1 if not found: contents2.append('\t' + key + '=' + str(self.Fspecs[key]) + ':\\\\\n') final = [] for line in contents1: final.append(line) for line in contents2: final.append(line) for line in contents3: final.append(line) for line in final: tempstring += line debug += "tempstring to be written to: " + self.loginfile + "\n" self.logger.log(LogPriority.DEBUG, debug) tmpfile = self.loginfile + ".tmp" if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': self.loginfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.loginfile, tmpfile, myid) shutil.move(tmpfile, self.loginfile) os.chown(self.loginfile, 0, 0) os.chmod(self.loginfile, 0o644) resetsecon(self.loginfile) else: success = False return success
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 DisableUncommonProtocols(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 132 self.rulename = "DisableUncommonProtocols" self.formatDetailedResults("initialize") self.mandatory = False self.applicable = {'type': 'white', "family": ["linux"]} # Configuration item instantiation datatype = "bool" key = "DISABLEUNCOMMONPROTOCOLS" instructions = "To disable this rule, set the value of " + \ "DISABLEUNCOMMONPROTOCOLS to false." default = True self.ci = self.initCi(datatype, key, instructions, default) datatype = "list" key = "PROTOCOLS" instructions = "List all network protocols to disable" default = ["dccp", "sctp", "rds", "tipc"] self.ci2 = self.initCi(datatype, key, instructions, default) self.guidance = [ "NSA 2.5.7", "CIS 4.6", "CCE-26448-1", "CCE-26410-1", "CCE-26239-4", "CCE-26696-5", "CCE-26828-4", "CCE-27106-4" ] self.iditerator = 0 self.ch = CommandHelper(self.logger) self.sethelptext() def report(self): try: protocols = self.ci2.getcurrvalue() self.compliant = True self.detailedresults = "" mpdir = "/etc/modprobe.d/" for proto in protocols: if type(proto) is bytes: proto = proto.decode('utf-8') cmd = ["grep", "-R", proto, mpdir] self.ch.executeCommand(cmd) if not re.search(":install " + proto + " /bin/true", self.ch.getOutputString()): self.compliant = False self.detailedresults += proto + " is not disabled\n" 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 success = True self.detailedresults = "" protocols = self.ci2.getcurrvalue() mpdir = "/etc/modprobe.d/" protoconf = mpdir + "stonix-protocols.conf" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not os.path.exists(protoconf): createFile(protoconf, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": protoconf} self.statechglogger.recordchgevent(myid, event) for proto in protocols: cmd = ["grep", "-R", proto, mpdir] self.ch.executeCommand(cmd) if not re.search(":install " + proto + " /bin/true", self.ch.getOutputString()): open(protoconf, "a").write("install " + proto + " /bin/true\n") 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
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 SetSSCorners(Rule): '''classdocs''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 188 self.rulename = 'SetSSCorners' self.compliant = True self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = False self.sethelptext() self.guidance = ['CIS', '1.4.8.1', '1.4.8.2'] self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] }, 'noroot': True } # set up configuration items for this rule datatype = 'bool' key = 'SETSSCORNERS' instructions = 'To disable this rule, set the value of ' + \ 'SetSSCorners to False' default = True self.ci = self.initCi(datatype, key, instructions, default) def setVars(self): ''' ''' ssfound = False try: self.homedir = self.environ.geteuidhome() self.conffile = self.homedir + \ '/Library/Preferences/com.apple.dock.plist' self.readcmd = '/usr/bin/defaults read ' + '\"' + \ self.conffile + '\"' self.optlist = [ "wvous-bl-corner", "wvous-br-corner", "wvous-tl-corner", "wvous-tr-corner" ] self.optdict = {} self.writecmd = '/usr/bin/defaults write ' + '\"' + \ self.conffile + '\"' self.detailedresults = "" self.cmdhelper = CommandHelper(self.logger) self.compliant = True self.moddict = {} for opt in self.optlist: self.cmdhelper.executeCommand(self.readcmd + ' ' + opt) errout = self.cmdhelper.getErrorString() output = self.cmdhelper.getOutputString() if not re.search('^6', output) and not errout: self.optdict[opt] = output if re.search('^5', output): ssfound = True for opt in self.optlist: if opt not in self.optdict: self.optdict[opt] = 1 if not ssfound: self.optdict["wvous-tl-corner"] = 5 for opt in self.optdict: if self.optdict[opt] == 6: self.optdict[opt] = 1 except Exception: raise def report(self): ''' ''' found = False self.detailedresults = "" try: if self.environ.geteuid() == 0: self.detailedresults += '\nYou are running SetSSCorners ' + \ 'in Admin mode. This rule must be run in regular ' + \ 'user context.' self.logger.log(LogPriority.INFO, self.detailedresults) return False self.setVars() if os.path.exists(self.conffile): for item in self.optdict: self.cmdhelper.executeCommand(self.readcmd + ' ' + item) output = self.cmdhelper.getOutputString() errout = self.cmdhelper.getErrorString() sitem = item.split('-') location = str(sitem[1]) if errout: self.compliant = False self.detailedresults += '\nSpecified key not found : ' \ + str(item) elif re.search('^6', output): self.compliant = False self.detailedresults += '\nIncorrect configuration ' + \ 'value for key: ' + str(item) self.moddict['wvous-' + location + '-modifier'] = 1048576 elif re.search('^5', output): found = True self.moddict['wvous-' + location + '-modifier'] = 0 if not found: self.compliant = False self.detailedresults += '\nNo corner is configured to ' + \ 'activate screen saver' else: self.compliant = False self.detailedresults += '\nRequired configuration file ' + \ 'com.apple.dock.plist could not be found' except (KeyboardInterrupt, SystemExit): raise except Exception: 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 fix(self): ''' ''' success = True self.detailedresults = "" try: if self.environ.geteuid() == 0: self.detailedresults += '\nYou are running SetSSCorners ' + \ 'in Admin mode. This rule must be run in regular ' + \ 'user context.' self.logger.log(LogPriority.INFO, self.detailedresults) return False if self.ci.getcurrvalue(): if os.path.exists(self.conffile): for item in self.optdict: cmd = self.writecmd + ' ' + item + ' -int ' + \ str(self.optdict[item]) self.cmdhelper.executeCommand(cmd) errout = self.cmdhelper.getErrorString() if errout: success = False self.detailedresults += '\nUnable to execute ' + \ 'command ' + str(cmd) if self.moddict: for item in self.moddict: cmd = self.writecmd + ' ' + item + ' -int ' + \ str(self.moddict[item]) self.cmdhelper.executeCommand(cmd) errout = self.cmdhelper.getErrorString() if errout: success = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return success
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 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 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 SecureIPV6(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 124 self.rulename = "SecureIPV6" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() datatype = "bool" key = "SECUREIPV6" instructions = '''To disable this rule set the value of SECUREIPV6 to \ False.''' default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ["NSA 2.5.3.2", "CCE 4269-7", "CCE 4291-1", "CCE 4313-3", "CCE 4198-8", "CCE 3842-2", "CCE 4221-8", "CCE 4137-6", "CCE 4159-0", "CCE 3895-0", "CCE 4287-9", "CCE 4058-4", "CCE 4128-5"] self.applicable = {'type': 'white', 'family': ['linux'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.iditerator = 0 # self.editor1: sysctl file editor # self.editor2: network file editor self.editor1, self.editor2 = "", "" self.ch = CommandHelper(self.logger) def report(self): try: self.detailedresults = "" if self.environ.getosfamily() == "linux": self.compliant = self.reportLinux() if self.environ.getosfamily() == "freebsd": self.compliant = self.reportFree() if self.environ.getosfamily() == "darwin": self.compliant = self.reportMac() elif self.environ.getosfamily() == "solaris": self.compliant = self.reportSol() 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 the values of the directives, specified in self.directives check that self.path (/private/etc/sysctl.conf) exists check that the permissions and ownership on file sysctl.conf are 0o600 and 0,0 :returns: compliant :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - added doc string; try/except; fixed perms for file sysctl.conf (should be 0o600; was 420) ''' compliant = True self.editor = "" self.path = "/private/etc/sysctl.conf" self.tmpPath = self.path + ".tmp" sysctl = "/usr/sbin/sysctl" self.directives = {"net.inet6.ip6.forwarding": "0", "net.inet6.ip6.maxifprefixes": "1", "net.inet6.ip6.maxifdefrouters": "1", "net.inet6.ip6.maxfrags": "0", "net.inet6.ip6.maxfragpackets": "0", "net.inet6.ip6.neighborgcthresh": "1024", "net.inet6.ip6.use_deprecated": "0", "net.inet6.ip6.hdrnestlimit": "0", "net.inet6.ip6.only_allow_rfc4193_prefixes": "1", "net.inet6.ip6.dad_count": "0", "net.inet6.icmp6.nodeinfo": "0", "net.inet6.icmp6.rediraccept": "1", "net.inet6.ip6.maxdynroutes": "0"} self.fixables = {} try: self.cmdhelper = CommandHelper(self.logger) for directive in self.directives: cmd = [sysctl, "-n", directive] if self.cmdhelper.executeCommand(cmd): output = self.cmdhelper.getOutputString().strip() if output != self.directives[directive]: self.detailedresults += "The value for " + directive + \ " is not " + self.directives[directive] + ", it's " + \ output + "\n" compliant = False self.fixables[directive] = self.directives[directive] else: error = self.cmdhelper.getErrorString() self.detailedresults += "There was an error running the " + \ "the command " + cmd + "\n" self.logger.log(LogPriority.DEBUG, error) self.fixables[directive] = self.directives[directive] compliant = False if not os.path.exists(self.path): compliant = False else: self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, self.directives, "present", "closedeq") if not self.editor.report(): compliant = False self.detailedresults += "Didn't find the correct contents " + \ "inside " + self.path + "\n" if not checkPerms(self.path, [0, 0, 0o600], self.logger): compliant = False except Exception: raise return compliant ############################################################################### def reportLinux(self): netwrkfile = "" ifacefile = "" sysctl = "/etc/sysctl.conf" compliant = True self.interface1 = {"IPV6_AUTOCONF": "no"} self.interface2 = {"IPV6_PRIVACY": "rfc3041"} self.sysctls = {"net.ipv6.conf.default.router_solicitations": "0", "net.ipv6.conf.default.accept_ra_rtr_pref": "0", "net.ipv6.conf.default.accept_ra_pinfo": "0", "net.ipv6.conf.default.accept_ra_defrtr": "0", "net.ipv6.conf.default.autoconf": "0", "net.ipv6.conf.default.dad_transmits": "0", "net.ipv6.conf.default.max_addresses": "1", "net.ipv6.conf.default.accept_ra": "0", "net.ipv6.conf.default.accept_redirects": "0"} self.ph = Pkghelper(self.logger, self.environ) # check compliancy of /etc/sysctl.conf file if not os.path.exists(sysctl): compliant = False self.detailedresults += sysctl + " file doesn't exist\n" 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 of " + key + " key with sysctl command\n" self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: if output.strip() != key + " = " + self.sysctls[key]: compliant = False self.detailedresults += "sysctl output has incorrect value: " + \ output + "\n" # set the appropriate files based on the system 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 = "" # Check contents of network file 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.interface1, "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 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.interface2: found = False iterator = 0 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.interface2[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 return compliant ############################################################################### def fix(self): try: if not self.ci.getcurrvalue(): return self.detailedresults = "" 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() elif self.environ.getosfamily() == "solaris": self.detailedresults = "Solaris systems require a manual fix" self.logger.log(LogPriority.INFO, 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("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess ############################################################################### def fixMac(self): '''use the sysctl command to write directives create the sysctl.conf file if needed set permissions and ownership of sysctl.conf file to 0o600 and 0,0 :returns: success :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - added doc string; try/except; fixed perms for file sysctl.conf (should be 0o600; was 420) ''' success = True created = False try: if self.fixables: sysctl = "/usr/sbin/sysctl" for directive in self.fixables: cmd = [sysctl, "-w", directive + "=" + self.fixables[directive]] if not self.cmdhelper.executeCommand(cmd): error = self.cmdhelper.getErrorString() self.detailedresults += "There was an error running " + \ "the command " + cmd + "\n" self.logger.log(LogPriority.DEBUG, error) success = False if not os.path.exists(self.path): if createFile(self.path, self.logger): created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) else: return False if not self.editor: self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, self.directives, "present", "closedeq") if not self.editor.report(): if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "KVEditor commit to " + \ self.path + " was not successful\n" else: success = False self.detailedresults += "KVEditor fix of " + self.path + \ " was not successful\n" else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "KVEditor commit to " + \ self.path + " was not successful\n" else: success = False self.detailedresults += "KVEditor fix of " + self.path + \ " was not successful\n" if not checkPerms(self.path, [0, 0, 0o600], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o600], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions" + \ " on " + self.path + "\n" success = False else: if not setPerms(self.path, [0, 0, 0o600], self.logger): self.detailedresults += "Could not set permissions" + \ " on " + self.path + "\n" success = False except Exception: raise return success ############################################################################### def fixLinux(self): universal = "#The following lines were added by stonix\n" debug = "" success = True ifacefile = "" netwrkfile = "" sysctl = "/etc/sysctl.conf" interface = {"IPV6_AUTOCONF": "no"} interface2 = {"IPV6_PRIVACY": "rfc3041"} # "IPV6_DEFAULTGW": self.gateway, # "IPV6ADDR":self.ipaddr} if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" netwrkfile = "/etc/sysconfig/network" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" created = False # fix sysctl / tuning kernel parameters # manually write key value pairs to /etc/sysctl.conf 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: self.detailedresults += "Could not create file " + sysctl + \ "\n" success = False 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 to record the file write 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) # permissions on file are incorrect if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not setPerms(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ sysctl + "\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) # correct the network file if it exists if netwrkfile: created = False if not os.path.exists(netwrkfile): if not createFile(netwrkfile, self.logger): success = False debug = "Unable to create " + netwrkfile + " file\n" self.logger.log(LogPriority.DEBUG, debug) else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": netwrkfile} self.statechglogger.recordchgevent(myid, event) tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface1, "present", "closedeq") self.editor2.report() if os.path.exists(netwrkfile): if not checkPerms(netwrkfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(netwrkfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False 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 elif not self.editor2.commit(): success = False os.chown(netwrkfile, 0, 0) os.chmod(netwrkfile, 0o644) resetsecon(netwrkfile) if ifacefile: if os.path.exists(ifacefile): dirs = glob.glob(ifacefile + "*") if dirs: for loc in dirs: interface2 = {"IPV6_PRIVACY": "rfc3041"} # "IPV6_DEFAULTGW": self.gateway, # "IPV6ADDR":self.ipaddr} interface3 = {"IPV6_PRIVACY": "rfc3041"} # "IPV6_DEFAULTGW": self.gateway, # "IPV6ADDR":self.ipaddr} 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, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(filename, [0, 0, 0o644], self.logger, self.statechglogger, myid): return False for key in interface2: 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() == \ interface2[key]: if found: continue found = True else: contents.remove(line) if found: del interface3[key] for line in contents: tempstring += line tempstring += universal for key in interface3: tempstring += key + "=" + interface3[key] + \ "\n" if not writeFile(tmpfile, tempstring, self.logger): return False 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, 0o644) resetsecon(filename) elif not os.path.exists(ifacefile) and ifacefile != "": # will not attempt to create the interface files self.detailedresults += "Interface directory which holds interface \ files, doesn't exist. Stonix will not attempt to make this \ directory or the files contained therein." success = False return success
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 Yum(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/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 = "/usr/bin/yum install -y " self.remove = "/usr/bin/yum remove -y " self.search = "/usr/bin/yum search " 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''' try: installed = False self.ch.executeCommand(self.install + package) if self.ch.getReturnCode() == 0: installed = True self.detailedresults = package + \ " pkg installed successfully\n" else: self.detailedresults = package + " pkg not able to install\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return installed 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: removed = False self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: removed = True self.detailedresults += package + " pkg removed successfully\n" else: self.detailedresults += package + \ " pkg not able to be removed\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return removed 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''' try: found = False self.ch.executeCommand(self.rpm + package) if self.ch.getReturnCode() == 0: found = True self.detailedresults += package + " pkg found\n" else: self.detailedresults += package + " pkg not found\n" 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 (self.detailedresults) ############################################################################### def checkAvailable(self, package): try: found = False self.ch.executeCommand(self.search + package) output = self.ch.getOutputString() if re.search("no matches found", output.lower()): self.detailedresults += package + " pkg is not available " + \ " or may be misspelled\n" elif re.search("matched", output.lower()): self.detailedresults += package + " pkg is available\n" found = True 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 (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 self.install ############################################################################### def getRemove(self): return self.remove
class RestrictMounting(Rule): '''Class help text''' def __init__(self, config, enviro, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 112 self.rulename = "RestrictMounting" self.formatDetailedResults("initialize") self.mandatory = False self.sethelptext() # Configuration item instantiation datatype1 = "bool" key1 = "RESTRICTCONSOLEACCESS" instructions1 = "To restrict console device access, set " + \ "RESTRICTCONSOLEACCESS to True." default1 = False self.consoleCi = self.initCi(datatype1, key1, instructions1, default1) datatype2 = "bool" key2 = "DISABLEAUTOFS" instructions2 = "To disable dynamic NFS mounting through the " + \ "autofs service, set DISABLEAUTOFS to True." default2 = False self.autofsCi = self.initCi(datatype2, key2, instructions2, default2) datatype3 = "bool" key3 = "DISABLEGNOMEAUTOMOUNT" instructions3 = "To disable the GNOME desktop environment from " + \ "automounting devices and removable media, set " + \ "DISABLEGNOMEAUTOMOUNT to True." default3 = False self.gnomeCi = self.initCi(datatype3, key3, instructions3, default3) self.guidance = ["NSA 2.2.2.1", "NSA 2.2.2.3", "NSA 2.2.2.4", "CCE 3685-5", "CCE 4072-5", "CCE 4231-7", "CCE-RHEL7-CCE-TBD 2.2.2.6"] self.applicable = {"type": "white", "family": ["linux"]} self.iditerator = 0 self.gsettings = "/usr/bin/gsettings" self.gconftool = "/usr/bin/gconftool-2" self.dbuslaunch = "/usr/bin/dbus-launch" def report(self): ''' ''' self.automountMedia = True self.automountDrives = True self.sec_console_perms1 = "/etc/security/console.perms.d/50-default.perms" self.sec_console_perms2 = "/etc/security/console.perms" self.console_perms_temppath = self.sec_console_perms2 + ".stonixtmp" self.data = {"<console>": "tty[0-9][0-9]* vc/[0-9][0-9]* :0\.[0-9] :0", "<xconsole>": "0\.[0-9] :0"} self.autofspkg = "autofs" self.autofssvc = "autofs" self.ph = Pkghelper(self.logdispatch, self.environ) self.sh = ServiceHelper(self.environ, self.logdispatch) self.ch = CommandHelper(self.logdispatch) self.compliant = True self.detailedresults = "" try: if os.path.exists(self.sec_console_perms1): current_config = readFile(self.sec_console_perms1, self.logger) for line in current_config: if re.search("^<[x]?console>", line, re.M): self.compliant = False self.detailedresults += self.sec_console_perms1 + " contains unrestricted device access\n" break if os.path.exists(self.sec_console_perms2): self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sec_console_perms2, self.console_perms_temppath, self.data, "present", "closedeq") if not self.editor2.report(): self.compliant = False self.detailedresults += self.sec_console_perms2 + " does not contain the correct values\n" if self.ph.check(self.autofspkg): if self.sh.auditService(self.autofssvc, _="_"): self.compliant = False self.detailedresults += "autofs is installed and enabled\n" if os.path.exists(self.gsettings): automountOff = False autorunNever = False cmd = [self.gsettings, "get", "org.gnome.desktop.media-handling", "automount"] self.ch.executeCommand(cmd) if re.search("false", self.ch.getOutputString()): automountOff = True cmd = [self.gsettings, "get", "org.gnome.desktop.media-handling", "autorun-never"] self.ch.executeCommand(cmd) if re.search("true", self.ch.getOutputString()): autorunNever = True debug = "autorun-never is enabled" self.logger.log(LogPriority.DEBUG, debug) self.automountOff = automountOff self.autorunNever = autorunNever if not automountOff or not autorunNever: self.compliant = False self.detailedresults += "GNOME automounting is enabled\n" # check for gnome automounting if os.path.exists(self.gconftool): cmd = [self.gconftool, "-R", "/desktop/gnome/volume_manager"] if os.path.exists("/desktop/gnome/volume_manager"): self.ch.executeCommand(cmd) retcode = self.ch.getReturnCode() if retcode != 0: self.compliant = False self.detailedresults += "\nFailed to read gnome volume manager config" output = self.ch.getOutputString() if re.search("automount_media.*false", output): self.automountMedia = False if re.search("automount_drives.*false", output): self.automountDrives = False else: self.automountMedia = False self.automountDrives = False mylist = [self.automountMedia, self.automountDrives] if any(mylist): self.compliant = False self.detailedresults += "GNOME automounting is enabled\n" # reset these directories to be owned by their respective users dirs = '' if os.path.exists('/run/user'): dirs = os.listdir('/run/user') if dirs: for d in dirs: # check if the directory is an integer representing a uid if re.search('^([+-]?[1-9]\d*|0)$', d, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Found UID directory") try: os.chown('/run/user/' + d + '/dconf/user', int(d), int(d)) except Exception as errmsg: self.logger.log(LogPriority.DEBUG, str(errmsg)) continue else: self.logger.log(LogPriority.DEBUG, "no directories in /run/user") except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): ''' ''' self.detailedresults = "" self.iditerator = 0 self.rulesuccess = True success = True consoleaccess = self.consoleCi.getcurrvalue() autofs = self.autofsCi.getcurrvalue() gnomeautomount = self.gnomeCi.getcurrvalue() mylist = [consoleaccess, autofs, gnomeautomount] try: # if none of the CIs are enabled, skip fix if not any(mylist): self.detailedresults += "\nNone of the CI's were enabled. Nothing was done." self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess # clear event list data eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) # if restrict console access CI is enabled, restrict console access if self.consoleCi.getcurrvalue(): if os.path.exists(self.sec_console_perms1): tmpfile = self.sec_console_perms1 + ".stonixtmp" defaultPerms = open(self.sec_console_perms1, "r").read() defaultPerms = re.sub("(<[x]?console>)", r"#\1", defaultPerms) if writeFile(tmpfile, defaultPerms, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.sec_console_perms1} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.sec_console_perms1, tmpfile, myid) os.rename(tmpfile, self.sec_console_perms1) resetsecon(self.sec_console_perms1) else: success = False self.detailedresults += "Problem writing new contents to " + \ "temporary file" if os.path.exists(self.sec_console_perms2): self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sec_console_perms2, self.console_perms_temppath, self.data, "present", "closedeq") self.editor.report() if self.editor.fixables: if self.editor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.commit(): debug = self.sec_console_perms2 + "'s contents have been " \ + "corrected\n" self.logger.log(LogPriority.DEBUG, debug) resetsecon(self.sec_console_perms2) else: debug = "kveditor commit not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.sec_console_perms2 + \ " properties could not be set\n" else: debug = "kveditor fix not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.sec_console_perms2 + \ " properties could not be set\n" # if autofs CI is enabled, disable autofs if self.autofsCi.getcurrvalue(): if self.ph.check(self.autofspkg) and \ self.sh.auditService(self.autofssvc, _="_"): if self.sh.disableService(self.autofssvc, _="_"): debug = "autofs service successfully disabled\n" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "servicehelper", "servicename": self.autofssvc, "startstate": "enabled", "endstate": "disabled"} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to disable autofs service\n" self.logger.log(LogPriority.DEBUG, debug) returnCode = 0 if self.gnomeCi.getcurrvalue(): if os.path.exists(self.gsettings): # gsettings requires a D-Bus session bus in order to make # any changes. This is because the dconf daemon must be # activated using D-Bus. if not os.path.exists(self.dbuslaunch): self.ph.install("dbus-x11") if os.path.exists(self.dbuslaunch): if not self.automountOff: cmd = [self.dbuslaunch, self.gsettings, "set", "org.gnome.desktop.media-handling", "automount", "false"] self.ch.executeCommand(cmd) returnCode = self.ch.getReturnCode() if not returnCode: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": ["dbus-launch", "gsettings", "set", "org.gnome.desktop.media-handling", "automount", "true"]} self.statechglogger.recordchgevent(myid, event) if not self.autorunNever: cmd = [self.dbuslaunch, "gsettings", "set", "org.gnome.desktop.media-handling", "autorun-never", "true"] self.ch.executeCommand(cmd) returnCode += self.ch.getReturnCode() if not self.ch.getReturnCode(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": [self.dbuslaunch, self.gsettings, "set", "org.gnome.desktop.media-handling", "autorun-never", "false"]} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to disable GNOME automounting: " + \ "dbus-x11 is not installed" self.logger.log(LogPriority.DEBUG, debug) if os.path.exists(self.gconftool): if self.automountMedia: cmd = [self.gconftool, "--direct", "--config-source", "xml:readwrite:/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/automount_media", "false"] self.ch.executeCommand(cmd) returnCode = self.ch.getReturnCode() if not returnCode: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": [self.gconftool, "--direct", "--config-source", "xml:readwrite:" + "/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/" + "automount_media", "true"]} self.statechglogger.recordchgevent(myid, event) if self.automountDrives: cmd = [self.gconftool, "--direct", "--config-source", "xml:readwrite:/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/automount_drives", "false"] self.ch.executeCommand(cmd) returnCode += self.ch.getReturnCode() if not self.ch.getReturnCode(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": [self.gconftool, "--direct", "--config-source", "xml:readwrite:" + "/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/" + "automount_drives", "true"]} self.statechglogger.recordchgevent(myid, event) if returnCode: success = False self.detailedresults += "Fix failed to disable GNOME automounting\n" # reset these directories to be owned by their respective users dirs = '' if os.path.exists('/run/user'): dirs = os.listdir('/run/user') if dirs: for d in dirs: # check if the directory is an integer representing a uid if re.search('^([+-]?[1-9]\d*|0)$', d, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Found UID directory") try: os.chown('/run/user/' + d + '/dconf/user', int(d), int(d)) except Exception as errmsg: self.logger.log(LogPriority.DEBUG, str(errmsg)) continue else: self.logger.log(LogPriority.DEBUG, "no directories in /run/user") 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 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 Yum(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/06 dwalker - Original Implementation @change: 2015/08/20 eball - Added getPackageFromFile ''' def __init__(self, logger): self.environ = Environment() self.logger = logger self.ch = CommandHelper(self.logger) self.yumloc = "/usr/bin/yum" self.install = self.yumloc + " install -y " self.remove = self.yumloc + " remove -y " self.search = self.yumloc + " list " self.checkupdates = self.search + "updates " self.listavail = self.search + "available " self.listinstalled = self.search + "installed " self.updatepkg = self.yumloc + " update -y --obsoletes " myos = self.environ.getostype().lower() if re.search("red hat.*?release 6", myos) or \ re.search("^centos$", myos.strip()): self.rpmloc = "/bin/rpm" else: self.rpmloc = "/usr/bin/rpm" self.provides = self.rpmloc + " -qf " self.query = self.rpmloc + " -qa " 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 T. Walker @change: Breen Malmberg - 4/24/2017 - refactored method; added logging; replaced detailedresults with logging @change: Breen Malmberg - 10/1/2018 - added check for package manager lock and retry loop ''' installed = True maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to install package due to yum package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Yum 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() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(repoerr)) installed = False if installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was 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 T. Walker @change: Breen Malmberg - 4/24/2017 - refactored method; added logging; replaced detailedresults with logging ''' removed = True maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to remove package, due to yum package manager being in-use by another process.") removed = False return removed else: self.logger.log(LogPriority.DEBUG, "Yum 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() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(repoerr)) removed = False if removed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was successfully removed") 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 package: string; Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return: found @rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/24/2017 - refactored method; added logging; replaced detailedresults with logging ''' installed = True errstr = "" maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check status of package, due to yum package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Yum package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.listinstalled + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(repoerr)) 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 Update(self, package=""): ''' update specified package if any updates are available for it if no package is specified, update all packages which can be updated 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.updatepkg + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) updated = False if package: if updated: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was successfully updated") else: self.logger.log(LogPriority.DEBUG, "No updates were found for package " + str(package)) else: if updated: self.logger.log(LogPriority.DEBUG, "All packages were successfully updated") else: self.logger.log(LogPriority.DEBUG, "No updates were found for this system") except Exception: raise return updated def checkUpdate(self, package=""): ''' check if there are any updates available for specified package if no package is specified, check if any updates are available for the current system @param package: string; name of package to check @return: updatesavail @rtype: bool @author: Breen Malmberg ''' updatesavail = False try: try: self.ch.executeCommand(self.checkupdates + package) retcode = self.ch.getReturnCode() output = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) else: if re.search("Updated packages", output, re.IGNORECASE): updatesavail = True except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) else: if re.search("Updated packages", output, re.IGNORECASE): updatesavail = True if package: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "No updates are available for package " + str(package)) else: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for this system") else: self.logger.log(LogPriority.DEBUG, "No updates are available for this system") except Exception: raise return updatesavail def checkAvailable(self, package): ''' check if specified package is available to install return True if it is return False if not @param package: string; name of package to check @return: available @rtype: bool @author: Breen Malmberg ''' available = True maxtries = 12 trynum = 0 while psRunning("yum"): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to yum package manager being in-use by another process.") available = False return available else: self.logger.log(LogPriority.DEBUG, "Yum package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.listavail + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.DEBUG, str(repoerr)) available = False if available: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is available to install") else: self.logger.log(LogPriority.DEBUG, "No package " + str(package) + " was found to install") except Exception: raise return available def getPackageFromFile(self, filename): ''' Returns the name of the package that provides the given filename/path. @param filename: string; The name or path of the file to resolve @return: packagename @rtype: string @author: Eric Ball @change: Breen Malmberg - 4/24/2017 - refactored method; added logging; replaced detailedresults with logging ''' packagename = "" try: try: self.ch.executeCommand(self.provides + filename) retcode = self.ch.getReturnCode() outputstr = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('yum', retcode, str(errstr)) else: packagename = outputstr except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) except Exception: raise return packagename def getInstall(self): return self.install def getRemove(self): return self.remove
class SecureSNMP(Rule): '''classdocs''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.statechglogger = statechglogger self.rulenumber = 144 self.rulename = 'SecureSNMP' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = ['NSA 3.20', 'CCE 4540-1'] self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} datatype = 'bool' key = 'DISABLESNMP' instructions = "If there is a mission-critical need for hosts at" + \ "this site to be remotely monitored by a SNMP " + \ "tool, then prevent the disabling and removal " + \ "of SNMP by setting the value of DisableSNMP " + \ "to False." default = True self.disablesnmp = self.initCi(datatype, key, instructions, default) datatype2 = 'bool' key2 = 'CONFIGURESNMP' instructions2 = "To configure SNMP on this system, make sure " + \ "you have the value for DisableSNMP set to " + \ "False, and set the value of ConfigureSNMP to True." default2 = True self.configuresnmp = self.initCi(datatype2, key2, instructions2, default2) self.snmpdconflocations = ['/etc/snmp/conf/snmpd.conf', '/etc/snmp/conf/snmp.conf', '/etc/snmp/snmpd.conf', '/etc/snmp/snmp.conf'] self.snmpv3directives = {'defContext': 'none', 'defVersion': '3', 'defAuthType': 'SHA', 'defSecurityLevel': 'authNoPriv'} # add any other possible snmp configuration file paths from the environment # variable SNMPCONFPATH # .get does not throw keyerror but instead returns None if env doesn't exist snmpconfpathstring = os.environ.get('SNMPCONFPATH') if snmpconfpathstring: snmpconfpathlist = snmpconfpathstring.split() for path in snmpconfpathlist: path = str(path).strip() self.snmpdconflocations.append(path) def report(self): '''Determine which report method(s) to run and run them :returns: bool @author bemalmbe ''' # defaults self.detailedresults = "" try: if self.environ.getostype() == 'Mac OS X': self.compliant = self.reportmac() self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant compliant = True self.svchelper = ServiceHelper(self.environ, self.logger) self.pkghelper = Pkghelper(self.logger, self.environ) if self.disablesnmp.getcurrvalue(): if not self.reportDisableSNMP(): compliant = False if self.configuresnmp.getcurrvalue(): if not self.reportConfigureSNMP(): compliant = False self.compliant = compliant except AttributeError: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) 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 reportmac(self): '''@author: Breen Malmberg''' configured = True try: self.cmdhelper = CommandHelper(self.logger) filepath = '/System/Library/LaunchDaemons/org.net-snmp.snmpd.plist' if not os.path.exists(filepath): configured = False self.detailedresults += '\nrequired plist configuration file not found: ' + filepath cmd = '/usr/bin/defaults read ' + filepath + ' Disabled' self.cmdhelper.executeCommand(cmd) output = self.cmdhelper.getOutputString() errout = self.cmdhelper.getErrorString() if errout: configured = False self.detailedresults += '\nunable to execute defaults read command, or \"Disabled\" key does not exist' else: if not re.search('^1', output): configured = False self.detailedresults += '\nsnmpd is not yet disabled' except Exception: raise return configured def reportDisableSNMP(self): '''Determine whether SNMP service is disabled and uninstalled :returns: bool @author bemalmbe ''' # defaults secure = False svcenabled = False pkginstalled = False try: svcenabled = self.svchelper.auditService('snmpd', _="_") pkginstalled = self.pkghelper.check('net-snmpd') if not svcenabled and not pkginstalled: secure = True return secure except AttributeError: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) return False def reportConfigureSNMP(self): '''Determine whether the SNMP service is securely configured :returns: bool @author bemalmbe ''' # defaults kvintent = 'present' kvconftype = 'conf' kvtype = 'space' secure = True # check to make sure perms on conf files are 640 # check to make sure ownership of conf files is root:root # check to make sure conf files are not using weak or default community # string and that they are not using anything other than version 3 # security model as per NSA guidance try: if self.reportDisableSNMP(): return True for location in self.snmpdconflocations: if os.path.exists(location): perms = getOctalPerms(location) if perms != 640: secure = False kvpath = location kvtmppath = location + '.stonixtmp' self.kvosnmp = KVEditorStonix(self.statechglogger, self.logger, kvtype, kvpath, kvtmppath, self.snmpv3directives, kvintent, kvconftype) kvosnmpretval = self.kvosnmp.report() if not kvosnmpretval: secure = False f = open(location, 'r') contentlines = f.readlines() f.close() for line in contentlines: if re.search('^group', line): line = line.split() if line[2] in ['v1', 'v2', 'v2c']: secure = False self.detailedresults += '''You are currently using an outdated security model for your SNMP configuration. Please update to model 3.''' for line in contentlines: if re.search('^com2sec', line): line = line.split() if line[3] in ['public', 'community']: secure = False self.detailedresults += '''You are currently using a default or weak community string.''' for line in contentlines: if re.search('^access', line): line = line.split() if line[3] in ['any', 'v1', 'v2', 'v2c']: secure = False self.detailedresults += '''You are currently using an outdated security model for your SNMP configuration. Please update to model 3.''' if line[4] == 'noauth': secure = False self.detailedresults += '''You are currently not requiring authentication for SNMP. This is an unsecure practice. Please change to authNoPriv or authPriv.''' return secure except (IndexError, OSError): self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) return False def fix(self): '''Determine which fix method(s) to run and run them @author bemalmbe ''' self.detailedresults = "" self.rulesuccess = True try: if self.environ.getostype() == 'Mac OS X': if self.disablesnmp.getcurrvalue() or self.configuresnmp.getcurrvalue(): self.rulesuccess = self.fixmac() self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess if self.disablesnmp.getcurrvalue(): self.fixDisableSNMP() if self.configuresnmp.getcurrvalue(): self.fixConfigureSNMP() except AttributeError: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception as err: 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 fixmac(self): '''@author: Breen Malmberg''' success = True try: defaults = '/usr/bin/defaults ' operation = 'write ' filepath = '/System/Library/LaunchDaemons/org.net-snmp.snmpd.plist ' key = 'DISABLED' val = ' -bool true' cmd = defaults + operation + filepath + key + val self.cmdhelper.executeCommand(cmd) errout = self.cmdhelper.getErrorString() if errout: success = False self.detailedresults += '\ncould not set Disabled key to true in org.net-snmp.snmpd.plist' except Exception: raise return success def fixDisableSNMP(self): '''Disable the SNMP service and uninstall the package for it @author bemalmbe ''' try: if not self.reportDisableSNMP(): self.svchelper.disableService('snmpd', _="_") self.pkghelper.remove('net-snmpd') except AttributeError: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: raise def fixConfigureSNMP(self): '''Securely configure the SNMP service. This option should be used instead of disabling SNMP only if there is a mission-critical need for the SNMP service to operate in the environment. @author bemalmbe ''' # set auth type to SHA, security model version to 3, and security level to # authNoPriv set permissions for the SNMP conf file to 640 # change owner and group of the SNMP conf file to root and root # admin must set up security on version 3 themselves because it is # account-based security and they must set up their own account(s) try: myid = '0144001' self.kvosnmp.setEventID(myid) self.kvosnmp.fix() self.kvosnmp.commit() for location in self.snmpdconflocations: if os.path.exists(location): os.chmod(location, 0o640) os.chown(location, 0, 0) except (KeyError, OSError): self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: raise
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 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 DisableWebSharing(Rule): '''Web Sharing uses the Apache 2.2.x web server to turn the Mac into an HTTP/Web server. As with file sharing, web sharing is best left off and a dedicated, well-managed web server is recommended. ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.rulenumber = 208 self.rulename = 'DisableWebSharing' self.formatDetailedResults("initialize") self.mandatory = True self.compliant = False self.rootrequired = True self.guidance = ['CIS 1.4.14.6'] self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.logger = logger # set up CIs datatype = 'bool' key = 'DISABLEWEBSHARING' instructions = 'To prevent web sharing from being disabled, set the value of DisableWebSharing to False.' default = True self.disableWebSharing = self.initCi(datatype, key, instructions, default) # set up class var's self.maclongname = '/System/Library/LaunchDaemons/org.apache.httpd.plist' self.macshortname = 'org.apache.httpd' self.svchelper = ServiceHelper(self.environ, self.logger) self.cmhelper = CommandHelper(self.logger) self.sethelptext() def report(self): '''Report status of web sharing and compliance :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' # defaults self.detailedresults = '' self.compliant = False # init servicehelper object if not os.path.exists(self.maclongname): self.compliant = True self.detailedresults += '\norg.apache.httpd.plist does not exist. Nothing to configure' self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant try: self.logger.log( LogPriority.DEBUG, "starting audit service for service: " + str(self.maclongname)) if not self.svchelper.auditService( self.maclongname, serviceTarget=self.macshortname): self.logger.log( LogPriority.DEBUG, str(self.maclongname) + " is not running/loaded") self.logger.log( LogPriority.DEBUG, "Checking if " + str(self.maclongname) + " is disabled in the plist") self.cmhelper.executeCommand( 'defaults read /System/Library/LaunchDaemons/org.apache.httpd Disabled' ) retcode = self.cmhelper.getReturnCode() if retcode != 0: errout = self.cmhelper.getErrorString() self.logger.log(LogPriority.DEBUG, errout) else: output = self.cmhelper.getOutputString() if re.search('1', output): self.logger.log( LogPriority.DEBUG, str(self.maclongname) + " is disabled in the plist") self.compliant = True else: self.logger.log( LogPriority.DEBUG, str(self.maclongname) + " is NOT disabled in the plist") else: self.detailedresults += '\n' + str( self.maclongname) + ' is still loaded/enabled' except (KeyboardInterrupt, SystemExit): raise except Exception as err: 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): '''Perform operations to disable web sharing :returns: self.rulesuccess :rtype: bool @author: Breen Malmberg ''' # defaults self.detailedresults = '' self.rulesuccess = True self.id = 0 try: if self.disableWebSharing.getcurrvalue(): #if not self.cmhelper.executeCommand('defaults write /System/Library/LaunchDaemons/org.apache.httpd Disabled -bool true'): # self.rulesuccess = False if not self.svchelper.disableService( self.maclongname, servicename=self.macshortname): self.rulesuccess = False self.logger.log( LogPriority.DEBUG, "Failed to disable service: " + str(self.maclongname)) else: self.id += 1 myid = iterate(self.id, self.rulenumber) event = { 'eventtype': 'commandstring', 'command': 'defaults delete /System/Library/LaunchDaemons/org.apache.httpd Disabled' } self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += '\nRule was not enabled, so nothing was done.' except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.rulesuccess = False 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 afterfix(self): afterfixsuccessful = True afterfixsuccessful &= self.svchelper.auditService(self.maclongname) return afterfixsuccessful
class Yum(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/bin/yum install -y " self.remove = "/usr/bin/yum remove -y " self.search = "/usr/bin/yum search " 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''' try: installed = False self.ch.executeCommand(self.install + package) output = self.ch.getOutputString() if self.ch.getReturnCode() == 0: installed = True self.detailedresults = package + " pkg installed successfully\n" else: self.detailedresults = package + " pkg not able to install\n" self.logger.log(LogPriority.DEBUG,self.detailedresults) return installed 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''' method = "yum.remove" try: removed = False self.ch.executeCommand(self.remove + package) if self.ch.getReturnCode() == 0: removed = True self.detailedresults += package + " pkg removed successfully\n" else: self.detailedresults += package + " pkg not able to be removed\n" self.logger.log(LogPriority.DEBUG,[method,self.detailedresults]) return removed except(KeyboardInterrupt,SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR,[method,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''' try: found = False self.ch.executeCommand(self.rpm + package) output = self.ch.getOutputString() #for redhat systems only if self.ch.getReturnCode() == 0: found = True self.detailedresults += package + " pkg found\n" else: self.detailedresults += package + " pkg not found\n" 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(self.detailedresults) ############################################################################### def checkAvailable(self,package): try: found = False self.ch.executeCommand(self.search + package) output = self.ch.getOutputString() if re.search("no matches found", output.lower()): self.detailedresults += package + " pkg is not available " + \ " or may be misspelled\n" elif re.search("matched", output.lower()): self.detailedresults += package + " pkg is available\n" found = True 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(self.detailedresults) ############################################################################### def getInstall(self): return self.install ############################################################################### def getRemove(self): return self.remove
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 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 Derek Walker - Original Implementation @change: 2015/08/20 eball - Added getPackageFromFile @change: 2017/04/27 Breen Malmberg - added two methods checkUpdate and Update; fixed doc string formatting; removed detailedresults reset in init; replaced with --force-yes flag with --assume-yes (from the man page for apt-get: Force yes. This is a dangerous option that will cause apt-get to continue without prompting if it is doing something potentially harmful. It should not be used except in very special situations. Using --force-yes can potentially destroy your system!) @change: 2017/08/16 bgonz12 - Added DEBIAN_FRONTEND=noninteractive env var to remove function @change: 2017/10/18 Breen Malmberg - changed class var names to be more self-explanatory; changed command to check whether there are available packages to use the canonical debian/ubuntu method; added calls to repoError exception to determine exact nature and cause of any errors with querying or calling repositories on the system (this adds logging of the nature and cause(s) as well); changed log messaging to be more consistent in style/format; removed calls to validateParam due to concerns about the stability and reliability of that method ''' def __init__(self, logger): self.logger = logger self.ch = CommandHelper(self.logger) self.aptgetloc = "/usr/bin/apt-get" self.aptcacheloc = "/usr/bin/apt-cache" self.dpkgloc = "/usr/bin/dpkg" self.aptinstall = "DEBIAN_FRONTEND=noninteractive " + self.aptgetloc + " -y --assume-yes install " self.aptremove = "DEBIAN_FRONTEND=noninteractive " + self.aptgetloc + " -y remove " self.dpkgsearch = self.dpkgloc + " -S " self.dpkgchkinstalled = self.dpkgloc + " -l " self.aptchkupdates = self.aptgetloc + " -u upgrade --assume-no " self.aptupgrade = self.aptgetloc + " -u upgrade --assume-yes " self.aptchkavail = self.aptcacheloc + " policy " 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 - 4/27/2017 - fixed doc string formatting; method now returns a variable; parameter validation added detailedresults replaced with logging @change: Breen Malmberg - 10/1/2018 - added check for package manager lock and retry loop ''' installed = True maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to install package, due to Apt package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.aptinstall + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode) except repoError as repoerr: if not repoerr.success: installed = False self.logger.log(LogPriority.WARNING, str(errstr)) if installed: self.logger.log(LogPriority.DEBUG, "Successfully installed package " + str(package)) 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 T. Walker ''' removed = True maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to remove package, due to Apt package manager being in-use by another process.") removed = False return removed else: self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.aptremove + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode) except repoError as repoerr: if not repoerr.success: removed = False self.logger.log(LogPriority.WARNING, str(errstr)) if removed: self.logger.log(LogPriority.DEBUG, "Successfully removed package " + str(package)) 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: package: string; Name of the package whose installation status is to be checked, must be recognizable to the underlying package manager. @return: installed @rtype: bool @author: Derek Walker @change: Breen Malmberg - 4/27/2017 - fixed doc string formatting; method now returns a variable; replaced detailedresults with logging ''' installed = False stringToMatch = "ii\s+" + str(package) outputstr = "" maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check status of package, due to Apt package manager being in-use by another process.") installed = False return installed else: self.logger.log(LogPriority.DEBUG, "Apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.dpkgchkinstalled + package) retcode = self.ch.getReturnCode() outputstr = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(repoerr)) return False if re.search(stringToMatch, outputstr, re.IGNORECASE): installed = True if not installed: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is NOT installed") else: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " is installed") except Exception: raise return installed def checkAvailable(self, package): ''' check if a given package is available @param package: string; Name of package to check @return: found @rtype: bool @author: Derek T. Walker @change: Breen Malmberg - 4/27/2017 - created doc string; pulled result logging out of conditional ''' found = False repoerr = "" outputstr = "" maxtries = 12 trynum = 0 pslist = ["apt", "apt-get", "dpkg"] for ps in pslist: while psRunning(ps): trynum += 1 if trynum == maxtries: self.logger.log(LogPriority.DEBUG, "Timed out while attempting to check availability of package, due to apt package manager being in-use by another process.") available = False return available else: self.logger.log(LogPriority.DEBUG, "apt package manager is in-use by another process. Waiting for it to be freed...") time.sleep(5) try: try: self.ch.executeCommand(self.aptchkavail + package) retcode = self.ch.getReturnCode() outputstr = self.ch.getOutputString() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(repoerr)) return False if re.search(package, outputstr, re.IGNORECASE): found = True if found: 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 found def Update(self, package=""): ''' update the specified package if any updates are available for it if no package is specified, apply all available updates for the system @param package: string; (OPTIONAL) name of package to update @return: updated @rtype: bool @author: Breen Malmberg ''' updated = True try: try: self.ch.executeCommand(self.aptupgrade + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) updated = False if package: if updated: self.logger.log(LogPriority.DEBUG, "Package " + str(package) + " was updated successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to apply updates to package " + str(package)) else: if updated: self.logger.log(LogPriority.DEBUG, "All updates were installed successfully") else: self.logger.log(LogPriority.DEBUG, "Failed to apply updates") except Exception: raise return updated def checkUpdate(self, package=""): ''' check for updates for specified package if no package is specified, then check for updates for the entire system @param package: string; (OPTIONAL) Name of package to check @return: updatesavail @rtype: bool @author: Breen Malmberg ''' updatesavail = False try: try: self.ch.executeCommand(self.aptchkupdates + package) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode, str(errstr)) except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) return False else: updatesavail = True if package: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for package " + str(package)) else: self.logger.log(LogPriority.DEBUG, "No updates are available for package " + str(package)) else: if updatesavail: self.logger.log(LogPriority.DEBUG, "Updates are available for this system") else: self.logger.log(LogPriority.DEBUG, "No updates are available for this system") except Exception: raise return updatesavail def getPackageFromFile(self, filename): ''' Returns the name of the package that provides the given filename/path. @param: filename: string; The name or path of the file to resolve @return: packagename @rtype: string @author: Eric Ball @change: Breen Malmberg - 4/17/2017 - fixed doc string formatting; method now returns a variable; added param validation ''' packagename = "" try: try: self.ch.executeCommand(self.dpkgsearch + filename) retcode = self.ch.getReturnCode() errstr = self.ch.getErrorString() if retcode != 0: raise repoError('apt', retcode, str(errstr)) if self.ch.getReturnCode() == 0: output = self.ch.getOutputString() packagename = output.split(":")[0] except repoError as repoerr: if not repoerr.success: self.logger.log(LogPriority.WARNING, str(errstr)) pass except Exception: raise return packagename def getInstall(self): return self.aptinstall def getRemove(self): return self.aptremove
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 SecureIPV4(Rule): def __init__(self, config, environ, logger, statechglogger): '''Constructor''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 15 self.cmdhelper = CommandHelper(self.logger) self.rulename = "SecureIPV4" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() if self.environ.getostype() == "Mac OS X": self.networkTuning2 = self.__InitializeNetworkTuning2() else: self.networkTuning1 = self.__InitializeNetworkTuning1() self.networkTuning2 = self.__InitializeNetworkTuning2() self.guidance = ["NSA 2.5.1.1", "NSA 2.5.1.2"] self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.iditerator = 0 self.ch = CommandHelper(self.logger) def __InitializeNetworkTuning1(self): '''Private method to initialize the configurationitem object for the NetworkTuning1 bool. @return: configurationitem object instance''' datatype = 'bool' key = "NETWORKTUNING1" instructions = "Network Parameter Tuning. You should not need " + \ "to override this under normal circumstances." default = True ci = self.initCi(datatype, key, instructions, default) return ci def __InitializeNetworkTuning2(self): '''Private method to initialize the configurationitem object for the NetworkTuning2 bool. @return: configurationitem object instance''' key = "NETWORKTUNING2" instructions = "Additional network parameters. Set this to False " + \ "if you are running a router or a bridge. Also, in rare " + \ "cases, you may need to set this to False for VMware (if you " + \ "are using normal VMware routing, True should be fine)." default = True datatype = "bool" ci = self.initCi(datatype, key, instructions, default) return ci def report(self): '''Main parent report method that calls the sub report methods :returns: bool ''' try: self.detailedresults = "" if self.environ.getosfamily() == "linux": self.path = "/etc/sysctl.conf" self.tmpPath = "/etc/sysctl.conf.tmp" self.original = readFile(self.path, self.logger) rep1success = self.reportLinux1() rep2success = self.reportLinux2() elif self.environ.getosfamily() == "freebsd": self.path = "/etc/sysctl.conf" self.tmpPath = "/etc/sysctl.conf.tmp" self.original = readFile(self.path, self.logger) rep1success = self.reportFreebsd1() rep2success = self.reportFreebsd2() elif self.environ.getostype() == "Mac OS X": self.path = "/private/etc/sysctl.conf" self.tmpPath = "/private/etc/sysctl.conf.tmp" rep1success = True rep2success = self.reportMac() if rep1success and rep2success: self.compliant = True else: self.compliant = False 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 return self.rulesuccess def reportLinux1(self): '''Linux specific report method that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: lfc = {"net.ipv4.conf.all.secure_redirects": "0", "net.ipv4.conf.all.accept_redirects": "0", "net.ipv4.conf.all.rp_filter": "1", "net.ipv4.conf.all.log_martians": "1", "net.ipv4.conf.all.accept_source_route": "0", "net.ipv4.conf.default.accept_redirects": "0", "net.ipv4.conf.default.secure_redirects": "0", "net.ipv4.conf.default.rp_filter": "1", "net.ipv4.conf.default.accept_source_route": "0", "net.ipv4.tcp_syncookies": "1", "net.ipv4.icmp_echo_ignore_broadcasts": "1", "net.ipv4.tcp_max_syn_backlog": "4096"} editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, lfc, "present", "openeq") if not editor.report(): self.detailedresults += self.path + " is not configured " + \ "correctly for configuration item 1\n" compliant = False if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + "\n" compliant = False for key in lfc: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "Failed to get value of core dumps configuration with sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: output = self.ch.getOutputString() if output.strip() != key + " = " + lfc[key]: compliant = False self.detailedresults += "sysctl output has incorrect value: " + \ output + "\n" return compliant def reportLinux2(self): '''Linux specific report method2 that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): compliant = False else: lfc = {"net.ipv4.conf.default.send_redirects": "0", "net.ipv4.conf.all.send_redirects": "0", "net.ipv4.ip_forward": "0"} editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, lfc, "present", "openeq") if not editor.report(): self.detailedresults += self.path + " is not configured " + \ "correctly for configuration item 2\n" compliant = False for key in lfc: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "Failed to get value of core dumps configuration with sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: output = self.ch.getOutputString() if output.strip() != key + " = " + lfc[key]: compliant = False self.detailedresults += "sysctl output has incorrect value: " + \ output + "\n" return compliant def reportMac(self): '''Mac specific report method1 that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. :returns: compliant :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - minor doc string adjustments; fixed permissions on file /etc/sysctl.conf (needs to be 0o600; was 0o644); try/except ''' compliant = True try: self.editor = None if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: mfc = {"net.inet.ip.forwarding": "0", "net.inet.ip.redirect": "0"} kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, mfc, intent, "closedeq") if not self.editor.report(): self.detailedresults += self.path + " is not " + \ "configured correctly\n" compliant = False else: self.detailedresults += self.path + " is " + \ "configured correctly\n" if not checkPerms(self.path, [0, 0, 0o600], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + ": Expected 644, found " + \ str(getOctalPerms(self.path)) + "\n" compliant = False except Exception: raise return compliant def reportFreebsd1(self): '''Freebsd specific report method1 that ensures the items in the file exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: ffc = {"net.inet.icmp.bmcastecho": "0", "net.inet.ip.redirect": "0", "net.inet.icmp.maskrepl": "0", "net.inet.ip.sourceroute": "0", "net.inet.ip.accept_sourceroute": "0", "net.inet.tcp.syncookies": "1"} kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, ffc, intent, "openeq") if not self.editor.report(): compliant = False if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + ": Expected 644, found " + \ str(getOctalPerms(self.path)) + "\n" compliant = False return compliant def reportFreebsd2(self): '''Freebsd specific report method1 that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: ffc = {"net.inet.ip.forwarding": "0", "net.inet.ip.fastforwarding": "0"} if not self.networkTuning1.getcurrvalue(): kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, ffc, intent, "closedeq") else: self.editor.setData(ffc) if not self.editor.report(): compliant = False if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + ": Expected 644, found " + \ str(getOctalPerms(self.path)) + "\n" compliant = False return compliant def fix(self): '''Main parent fix method that calls the sub fix methods :returns: bool ''' try: self.detailedresults = "" success = True self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getosfamily() == "linux": if self.networkTuning1 and self.networkTuning2: success = self.fixLinux() else: self.detailedresults += "Required CI has not been initialized." success = False elif self.environ.getosfamily() == "freebsd": if self.networkTuning1 and self.networkTuning2: success = self.fixFreebsd() else: self.detailedresults += "Required CI has not been initialized." success = False elif self.environ.getosfamily() == "darwin": if self.networkTuning2: success = self.fixMac() else: self.detailedresults += "Required CI has not been initialized." success = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) success = False self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) self.rulesuccess = success return success def fixLinux(self): success = True created = False debug = "" sysctl = "/etc/sysctl.conf" tmpfile = sysctl + ".tmp" 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: self.detailedresults += "Could not create file " + self.path + \ "\n" self.formatDetailedResults("fix", False, self.detailedresults) 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 lfc = {} if self.networkTuning1 and self.networkTuning1.getcurrvalue(): lfc.update({"net.ipv4.conf.all.secure_redirects": "0", "net.ipv4.conf.all.accept_redirects": "0", "net.ipv4.conf.all.rp_filter": "1", "net.ipv4.conf.all.log_martians": "1", "net.ipv4.conf.all.accept_source_route": "0", "net.ipv4.conf.default.accept_redirects": "0", "net.ipv4.conf.default.secure_redirects": "0", "net.ipv4.conf.default.rp_filter": "1", "net.ipv4.conf.default.accept_source_route": "0", "net.ipv4.tcp_syncookies": "1", "net.ipv4.icmp_echo_ignore_broadcasts": "1", "net.ipv4.tcp_max_syn_backlog": "4096"}) if self.networkTuning2 and self.networkTuning2.getcurrvalue(): lfc.update({"net.ipv4.conf.default.send_redirects": "0", "net.ipv4.conf.all.send_redirects": "0", "net.ipv4.ip_forward": "0"}) self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, lfc, "present", "openeq") if not self.editor.report(): if self.editor.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event to record the file write if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.fix(): success = False debug = "KVEditor fix of " + self.path + \ " was not successful\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor.commit(): success = False debug = "KVEditor commit to " + \ self.path + " was not successful\n" self.logger.log(LogPriority.DEBUG, debug) # permissions on file are incorrect if not checkPerms(self.path, [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(self.path) # here we also check the output of the sysctl command for each key # to cover all bases for key in lfc: if self.ch.executeCommand("/sbin/sysctl " + key): output = self.ch.getOutputString().strip() if not re.search(lfc[key] + "$", output): undovalue = output[-1] self.ch.executeCommand("/sbin/sysctl -w " + key + "=" + lfc[key]) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set " + key + " = " + lfc[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 -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) return success def fixMac(self): '''run fix actions for mac systems :returns: success :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - added doc string; try/except; fixed perms for file sysctl.conf (should be 0o600; was 0o644) ''' success = True try: if not os.path.exists(self.path): if createFile(self.path, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) else: return False if self.networkTuning2.getcurrvalue(): if not self.editor: mfc = {"net.inet.ip.forwarding": "0", "net.inet.ip.redirect": "0"} kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, mfc, intent, "closedeq") if not self.editor.report(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "KVEditor commit to " + \ self.path + " was not successful\n" else: success = False self.detailedresults += "KVEditor fix of " + self.path + \ " was not successful\n" resetsecon(self.path) if not checkPerms(self.path, [0, 0, 0o600], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o600], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False except Exception: raise return success def fixFreebsd(self): if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o644], self.logger, self.statechglogger, myid): return False if self.networkTuning1.getcurrvalue() or \ self.networkTuning2.getcurrvalue(): if self.editor.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.fix(): return False elif not self.editor.commit(): return False os.chown(self.path, 0, 0) os.chmod(self.path, 0o644) resetsecon(self.path) cmd = ["/usr/sbin/service", "sysctl", "restart"] self.ch.executeCommand(cmd) if self.ch.getReturnCode() != 0: self.detailedresults = "Unable to restart sysctl\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return False else: return True else: return True
class ConfigureSpotlight(Rule): '''@author: ekkehard j. koch''' ############################################################################### def __init__(self, config, environ, logdispatcher, statechglogger): ''' This rule should normally be a rulekveditor rule, but there has recently been an issue discovered with subprocess for this particular rule where a paramaterized list for the command doesn't behave as would expected which KVADefault class uses when performing the defaults read command. Can change back if this issue is resolved with Mac/Python. ''' Rule.__init__(self, config, environ, logdispatcher, statechglogger) self.rulenumber = 17 self.rulename = 'ConfigureSpotlight' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = False self.guidance = [] self.applicable = {'type': 'white', 'os': {'Mac OS X': ['10.12', 'r', '10.14.10']}} datatype1 = "bool" key1 = "CONFIGURESPOTLIGHT" instructions1 = "To disable this configuration set the value of " + \ "CONFIGURESPOTLIGHT to False." default1 = True self.ci1 = self.initCi(datatype1, key1, instructions1, default1) datatype2 = "bool" key2 = "SAFARISEARCH" instructions2 = "To disable this configuration set the value of " + \ "SAFARISEARCH to False." default2 = True self.ci2 = self.initCi(datatype2, key2, instructions2, default2) def report(self): try: self.detailedresults = "" self.slv = {0: {'enabled': '1', 'name': 'APPLICATIONS'}, 1: {'enabled': '0', 'name': '\"MENU_SPOTLIGHT_SUGGESTIONS\"'}, 2: {'enabled': '1', 'name': '\"MENU_CONVERSION\"'}, 3: {'enabled': '1', 'name': '\"MENU_EXPRESSION\"'}, 4: {'enabled': '1', 'name': '\"MENU_DEFINITION\"'}, 5: {'enabled': '1', 'name': '\"SYSTEM_PREFS\"'}, 6: {'enabled': '1', 'name': 'DOCUMENTS'}, 7: {'enabled': '1', 'name': 'DIRECTORIES'}, 8: {'enabled': '1', 'name': 'PRESENTATIONS'}, 9: {'enabled': '1', 'name': 'SPREADSHEETS'}, 10: {'enabled': '1', 'name': 'PDF'}, 11: {'enabled': '1', 'name': 'MESSAGES'}, 12: {'enabled': '1', 'name': 'CONTACT'}, 13: {'enabled': '1', 'name': '\"EVENT_TODO\"'}, 14: {'enabled': '1', 'name': 'IMAGES'}, 15: {'enabled': '1', 'name': 'BOOKMARKS'}, 16: {'enabled': '1', 'name': 'MUSIC'}, 17: {'enabled': '1', 'name': 'MOVIES'}, 18: {'enabled': '1', 'name': 'FONTS'}, 19: {'enabled': '1', 'name': '\"MENU_OTHER\"'}, 20: {'enabled': '0', 'name': '\"MENU_WEBSEARCH\"'}} self.spotRead = "/usr/bin/defaults read com.apple.Spotlight " + \ "orderedItems " self.spotwrite = "/usr/bin/defaults write com.apple.Spotlight " + \ "orderedItems " self.safRead = "/usr/bin/defaults read com.apple.Safari " + \ "UniversalSearchEnabled " self.safWrite = "/usr/bin/defaults write com.apple.Safari " + \ "UniversalSearchEnabled " compliant = True self.slvlook = "(" self.slvset = "\'(" for _, v in sorted(self.slv.items()): self.slvset += "{\"enabled\"=" + str(v['enabled']) + "; " + \ "\"name\"=" + str(v['name']) + ";}," self.slvset += ")\';" i = 0 for _, v in sorted(self.slv.items()): if i == 20: self.slvlook += "{enabled=" + str(v["enabled"]) + ";" + \ "name=" + str(v["name"]) + ";}" break else: self.slvlook += "{enabled=" + str(v["enabled"]) + ";" + \ "name=" + str(v["name"]) + ";}," i += 1 self.slvlook += ")" lookstring = "The regex we are looking for from the defaults " + \ "read command: " + self.slvlook + "\n" self.logdispatch.log(LogPriority.DEBUG, lookstring) setstring = "The plist string we will set defaults write " + \ "command to: " + self.slvset + "\n" self.logdispatch.log(LogPriority.DEBUG, setstring) self.ch = CommandHelper(self.logdispatch) if self.ch.executeCommand(self.spotRead): output = self.ch.getOutputString() output = re.sub("(\s)", "", output) error = self.ch.getErrorString() if output: if not re.search(self.slvlook, output): self.detailedresults += "Output for orderedItems " + \ "key in com.apple.Spotlight plist isn't correct\n" compliant = False elif error: if re.search("does not exist", error): self.detailedresults += "Either com.apple." + \ "Spotlight plist doesn't exist or the key " + \ "orderedItems doesn't exist\n" compliant = False output, error = "", "" if self.ch.executeCommand(self.safRead): output = self.ch.getOutputString() error = self.ch.getErrorString() if output: if not re.search("0", output): self.detailedresults += "Output for " + \ "UniversalSearchEnabled key in " + \ "com.apple.Safari plist isn't correct\n" compliant = False elif error: if re.search("does not exist", error): self.detailedresults += "Either com.apple.Safari " + \ "plist doesn't exist or the key " + \ "UniversalSearchEnabled doesn't exist\n" self.compliant = compliant if self.compliant: self.detailedresults += "ConfigureSpotlight has been run " + \ "and is compliant\n" else: self.detailedresults += "ConfigureSpotlight has been run " + \ "and is not compliant\n" 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.ci1.getcurrvalue() and not self.ci2.getcurrvalue(): return self.detailedresults = "" success = True if self.ch.executeCommand(self.spotRead): output = self.ch.getOutputString() output = re.sub("(\s)", "", output) re.sub error = self.ch.getErrorString() if output: if not re.search(self.slvlook, output): cmd = self.spotwrite + self.slvset if not self.ch.executeCommand(cmd): self.detailedresults += "Unable to perform " + \ "defaults write command for " + \ "com.apple.Spotlight\n" success = False elif error: if re.search("does not exist", error): cmd = self.spotwrite + self.slvset if not self.ch.executeCommand(cmd): self.detailedresults += "Unable to perform " + \ "defaults write command for " + \ "com.apple.Safari\n" success = False output, error = "", "" if self.ch.executeCommand(self.safRead): output = self.ch.getOutputString() error = self.ch.getErrorString() if output: if not re.search("0", output): cmd = self.safWrite + "-bool no" if not self.ch.executeCommand(cmd): self.detailedresults += "Unable to perform " + \ "defaults write command for com.apple.Safari\n" success = False elif error: if re.search("does not exist", error): cmd = self.safWrite + "-bool no" if not self.ch.executeCommand(cmd): self.detailedresults += "Unable to perform " + \ "defaults write command for com.apple.Safari\n" self.rulesuccess = success if self.rulesuccess: self.detailedresults += "ConfigureSpotlight rule ran to " + \ "completion successfully\n" else: self.detailedresults += "ConfigureSpotlight rule did not " + \ "run to completion successfully\n" 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 undo(self): try: self.detailedresults = "no undo available" self.logger.log(LogPriority.INFO, self.detailedresults) except(KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults)
class SetDefaultUserUmask(Rule): """The SetDefaultUserUmask class sets the default user umask to 077. Also accepts user input of alternate 027 umask. For OS X documentation on this can be found at: http://support.apple.com/kb/HT2202 """ def __init__(self, config, environ, logdispatch, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logdispatch, statechglogger) self.config = config self.environ = environ self.logger = logdispatch self.statechglogger = statechglogger self.rulenumber = 48 self.rulename = 'SetDefaultUserUmask' self.formatDetailedResults("initialize") self.compliant = False self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = [ 'CIS', 'NSA(2.3.4.4)', 'CCE-3844-8', 'CCE-4227-5', 'CCE-3870-3', 'CCE-4737-6' ] # set up which system types this rule will be applicable to self.applicable = { 'type': 'white', 'family': 'linux', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # decide what the default umask value should be, based on osfamily if self.environ.getosfamily() == 'darwin': self.userumask = "022" else: self.userumask = "077" self.rootumask = "022" self.ci = self.initCi("bool", "SetDefaultUserUmask", "To prevent stonix from setting the " + \ "default user umask, set the value of " + \ "SetDefaultUserUmask to False.", True) # init CIs user_ci_type = "string" user_ci_name = "DEFAULTUSERUMASK" user_ci_instructions = "Set the default user umask value. Correct format is " + "a 3-digit, 0-padded integer. This value will determine the default permissions for every file created by non-privileged users." self.userUmask = self.initCi(user_ci_type, user_ci_name, user_ci_instructions, self.userumask) root_ci_type = "string" root_ci_name = "DEFAULTROOTUMASK" root_ci_instructions = "Set the default root umask value. Correct format is a 3-digit, 0-padded integer. Setting this to a value more restrictive than 022 may cause issues on your system. This value will determine the default permissions for every file created by the root user." self.rootUmask = self.initCi(root_ci_type, root_ci_name, root_ci_instructions, self.rootumask) 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.detailedresults and self.currstate properties are updated to reflect the system status. self.rulesuccess will be updated if the rule does not succeed. :return: self.compliant :rtype: bool """ # defaults self.detailedresults = "" self.ch = CommandHelper(self.logger) # set up list of files which need to be checked and configured self.rootfiledict = { "/root/.bash_profile": False, "/root/.bashrc": False, "/root/.cshrc": False, "/root/.tcshrc": False } self.userfiledict = { "/etc/profile": False, "/etc/csh.login": False, "/etc/csh.cshrc": False, "/etc/bashrc": False, "/etc/zshrc": False, "/etc/login.conf": False, "/etc/bash.bashrc": False, "/etc/login.defs": False } try: # decide which report method to run based on osfamily if self.environ.getosfamily() == 'darwin': self.compliant = self.reportmac() else: self.compliant = self.reportnix() except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.compliant = 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 reportnix(self): """ private method for reporting compliance status of *nix based systems :return: configured :rtype: bool """ # defaults configured = True try: # check for presence of umask config line in user files for item in self.userfiledict: if not os.path.exists(item): self.detailedresults += "\nMissing required configuration file: " + str( item) configured = False elif self.searchString( '^umask\s*' + str(self.userUmask.getcurrvalue()), item): self.userfiledict[item] = True for item in self.userfiledict: if os.path.exists(item): if not self.userfiledict[item]: self.detailedresults += "\nFile: " + str( item ) + " has an incorrect user umask configuration." configured = False # check for presence of umask config line in root files for item in self.rootfiledict: if not os.path.exists(item): self.detailedresults += "\nMissing required configuration file: " + str( item) configured = False elif self.searchString( '^umask\s*' + str(self.rootUmask.getcurrvalue()), item): self.rootfiledict[item] = True for item in self.rootfiledict: if os.path.exists(item): if not self.rootfiledict[item]: self.detailedresults += "\nFile: " + str( item ) + " has an incorrect root umask configuration." configured = False except Exception: raise return configured def reportmac(self): """the system's default user umask value (if set to something other than 022, will be stored in the file /var/db/com.apple.xpc.launchd/config/user.plist on versions of mac os x equal to or greater than 10.10 :return: retval :rtype: bool @author: Breen Malmberg """ valid = False pathexists = True plistpath = "/var/db/com.apple.xpc.launchd/config/user.plist" cmd = "/usr/bin/defaults read " + str(plistpath) + " Umask" # mac transforms the umask value to a different integer value # the algorithm is unknown, but these values are tested and determined to be # 022, 027 and 077 respectively umaskTrans = {'022': '18', '027': '23', '077': '63'} if not os.path.exists(plistpath): pathexists = False else: self.ch.executeCommand(cmd) retcode = self.ch.getReturnCode() if retcode != 0: errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) outputstr = self.ch.getOutputString() if self.userumask in umaskTrans: if re.search(umaskTrans[self.userumask], outputstr, re.IGNORECASE): valid = True else: valid = False retval = bool(valid and pathexists) return retval 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. Method to set the default users umask to 077 (or 027 if specified in the related config file. :return: bool @author: Breen Malmberg """ # defaults self.detailedresults = "" self.iditerator = 0 try: # if the ci is enabled/True, proceed if self.ci.getcurrvalue(): # decide which fix method to run, based on osfamily if self.environ.getosfamily() == 'darwin': self.rulesuccess = self.fixmac() else: self.rulesuccess = self.fixnix() # if the ci is not enabled, or False, report this in # detailedresults else: self.detailedresults = str(self.ci.getkey()) + \ " was disabled. No action was taken." 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 def fixmac(self): """Canonical way of setting user umask in mac os x 10.10 and later reference: https://support.apple.com/en-us/HT201684 :return: retval :rtype: bool @author: Breen Malmberg @change: 01/17/2018 - Breen Malmberg - added this method for newer mac os versions """ retval = True user_command = "/bin/launchctl config user umask " + str( self.userumask) umask_conf_file = "/private/var/db/com.apple.xpc.launchd/config" if not os.path.exists(umask_conf_file): os.makedirs(umask_conf_file, 0o755) self.ch.executeCommand(user_command) if self.ch.getReturnCode() != 0: errstr = self.ch.getErrorString() retval = False self.detailedresults += "\nFailed to set user umask" self.logger.log(LogPriority.DEBUG, "Failed to set user umask\n" + str(errstr)) return retval def fixnix(self): """ private method to apply umask config changes to *nix systems :return: success :rtype: bool """ # defaults success = True try: # iterate through list of user files # append the umask config line to each file for item in self.userfiledict: if not self.userfiledict[item]: self.configFile( 'umask ' + str(self.userUmask.getcurrvalue()) + "\n", item, 0o644, [0, 0], True) # do any of the root umask conf files exist? for item in self.rootfiledict: if not self.rootfiledict[item]: self.configFile( 'umask ' + str(self.rootUmask.getcurrvalue()) + "\n", item, 0o644, [0, 0], True) except Exception: raise return success def searchString(self, searchRE, filepath): """ private method for searching for a given string in a given file :param searchRE: :param filepath: :return: retval :rtype: bool """ # defaults stringfound = False noduplicates = True entries_found = 0 try: # check if path exists, then open it and read its contents if os.path.exists(filepath): self.logger.log(LogPriority.DEBUG, "\nFound configuration file: " + str(filepath)) f = open(filepath, 'r') contentlines = f.readlines() f.close() self.logger.log( LogPriority.DEBUG, "Checking " + str(filepath) + " for configuration...\n") # search for the searchRE; if found, set return val to True for line in contentlines: if re.search(searchRE, line, re.IGNORECASE): stringfound = True entries_found += 1 self.logger.log( LogPriority.DEBUG, "Found correct configuration in file: " + str(filepath)) if not stringfound: self.logger.log( LogPriority.DEBUG, "File: " + str(filepath) + " did NOT contain correct config.") if entries_found > 1: duplicates = entries_found - 1 self.detailedresults += "\n" + str( duplicates ) + " duplicate entries found in file: " + str(filepath) self.logger.log( LogPriority.DEBUG, str(duplicates) + " duplicate entries found in file: " + str(filepath)) noduplicates = False else: self.logger.log( LogPriority.DEBUG, "Specified file: " + str(filepath) + " does not exist.") except Exception: raise retval = stringfound and noduplicates return retval def configFile(self, configString, filepath, perms, owner, create=False): """private method for adding a configString to a given filepath :param configString: :param filepath: :param perms: :param owner: :param create: (Default value = False) """ umask_entries = 0 try: if os.path.exists(filepath): tmpfile = filepath + '.stonixtmp' # open the file, read its contents f = open(filepath, 'r') contentlines = f.readlines() f.close() for line in contentlines: self.logger.log(LogPriority.DEBUG, "Searching new line for umask entry...") if re.search('^umask', line, re.IGNORECASE): self.logger.log( LogPriority.DEBUG, "Found umask config line. Is it a duplicate?") umask_entries += 1 if umask_entries > 1: self.logger.log( LogPriority.DEBUG, "Yes. It is a duplicate. umask_entries = " + str(umask_entries)) self.logger.log(LogPriority.DEBUG, "Deleting duplicate line: " + line) contentlines.remove(line) else: self.logger.log( LogPriority.DEBUG, "No. It is not a duplicate. umask_entries = " + str(umask_entries)) self.logger.log( LogPriority.DEBUG, "Replacing existing umask config line with the provided one..." ) contentlines = [ c.replace(line, configString) for c in contentlines ] if umask_entries == 0: # append the config string contentlines.append('\n' + configString) # open temporary file, write new contents tf = open(tmpfile, 'w') tf.writelines(contentlines) tf.close() # create undo id and dict and save change record event = {'eventtype': 'conf', 'filepath': filepath} self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(tmpfile, filepath, myid) # set permission and ownership on rewritten file os.rename(tmpfile, filepath) os.chmod(filepath, perms) os.chown(filepath, owner[0], owner[1]) elif create: f = open(filepath, 'w') f.write(configString) f.close() event = {'eventtype': 'creation', 'filepath': filepath} self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) os.chmod(filepath, perms) os.chown(filepath, owner[0], owner[1]) except Exception: raise def appendDetailedResults(self, message): """ append given message to self.detailedresults :param message: string; ? """ self.detailedresults += '\n' + str(message) + '\n' def removeStonixUMASKCodeFromFile(self, filelist=[]): """ Removes the STONIX sets default umask block from list of files presented :param filelist: (Default value = []) :return: success :rtype: bool """ success = True bakFile = "" for myfile in filelist: if os.path.exists(myfile): removedBlockSuccessfully = False try: rfh = open(myfile, "r") except Exception: self.appendDetailedResults("File: " + \ str(myfile) + " - Open For Reading Failed - " + \ str(traceback.format_exc())) else: try: bakFile = "/tmp/removeUMASK-" + \ os.path.basename(myfile) + ".bak" wfh = open(bakFile, "w") except Exception: self.logdispatch.log( LogPriority.ERROR, "Failed to create backup for umask file") else: startOfUMASKBlock = False endOfUMASKBlock = False for line in rfh: if "# This block added by STONIX sets default umask" in line: startOfUMASKBlock = True if startOfUMASKBlock and not endOfUMASKBlock: self.logdispatch.log(LogPriority.DEBUG, "File: " + str(myfile) + \ "; Removing Line: '" + \ line.strip() + "'") else: wfh.write(line) if startOfUMASKBlock and "# End STONIX default umask block." in line: endOfUMASKBlock = True if startOfUMASKBlock and endOfUMASKBlock: removedBlockSuccessfully = True rfh.close() wfh.close() ##### # Using this method as os.rename (which is used in a file "move") is not # consistent across platforms, and this is. if removedBlockSuccessfully: ### delete myfile os.unlink(myfile) ### copy back to real shutil.copyfile(bakFile, myfile) self.appendDetailedResults("File: " + str(myfile) + \ " - Removed STONIX sets default umask block!") else: self.appendDetailedResults("File: " + str(myfile) + \ " - NO STONIX sets default umask block found in!") ### delete bak os.unlink(bakFile) else: self.appendDetailedResults("File: " + str(myfile) + \ " does not exist.") return success
class NoCoreDumps(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 = 49 self.rulename = "NoCoreDumps" self.formatDetailedResults("initialize") self.mandatory = True self.guidance = ["NSA 2.2.4.2"] self.applicable = { 'type': 'white', 'family': ['linux'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } datatype = 'bool' key = 'NOCOREDUMPS' instructions = "To prevent the disabling of core dumps on your system, set the value of NOCOREDUMPS to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.sethelptext() def report(self): '''Main parent report method that calls the sub report methods report1 and report2 @author: Derek Walker :returns: self.compliant :rtype: bool @change: Breen Malmberg - 1/10/2017 - doc string edit; return var init; minor refactor ''' self.detailedresults = "" self.compliant = True self.ch = CommandHelper(self.logger) osfam = self.environ.getosfamily() try: if osfam == "linux": if not self.reportLinux(): self.compliant = False elif osfam == "darwin": if not self.reportMac(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except Exception: 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 reportMac(self): '''run report actions for mac systems :returns: compliant :rtype: bool @author: Derek Walker @change: Breen Malmberg - 1/10/2017 - added doc string; default return var init; try/except; logging; minor refactor ''' self.logger.log( LogPriority.DEBUG, "System has been detected as Mac OS X, running reportMac()...") compliant = True self.ch.executeCommand("/usr/bin/launchctl limit core") retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "\nFailed to run launchctl command to get current value of core dumps configuration" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: output = self.ch.getOutputString() if output: if not re.search("0", output): compliant = False else: compliant = False return compliant def reportLinux(self): '''Sub report method 1 that searches the /etc/security/limits.conf file for the following line "* hard core 0" :returns: compliant :rtype: bool @author: ??? ''' compliant = True if not self.check_security_limits(): compliant = False if not self.check_sysctl(): compliant = False return compliant def check_security_limits(self): '''check the limits.conf file for the configuration line * hard core 0 :returns: compliant :rtype: bool @author: ??? ''' compliant = True securitylimits = "/etc/security/limits.conf" coresetting = "(^\*)\s+hard\s+core\s+0?" if os.path.exists(securitylimits): if not checkPerms(securitylimits, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions incorrect on " + securitylimits + "\n" compliant = False contents = readFile(securitylimits, self.logger) if contents: found = False for line in contents: if re.search(coresetting, line.strip()): found = True if not found: self.detailedresults += "Correct configuration line * hard core 0 " + \ "not found in /etc/security/limits.conf\n" compliant = False else: self.detailedresults += securitylimits + " file doesn't exist\n" return compliant def check_sysctl(self): '''check the systemd configuration setting fs.suid_dumpable for value of 0 :returns: compliant :rtype: bool @author: ??? ''' compliant = True self.ch.executeCommand("/sbin/sysctl fs.suid_dumpable") retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "Failed to get value of core dumps configuration with sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: output = self.ch.getOutputString() if output.strip() != "fs.suid_dumpable = 0": compliant = False self.detailedresults += "Core dumps are currently enabled\n" if not os.path.exists("/etc/sysctl.conf"): compliant = False self.detailedresults += "/etc/sysctl.conf file doesn't exist\n" else: self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", "/etc/sysctl.conf", "/etc/sysctl.conf.tmp", {"fs.suid_dumpable": "0"}, "present", "openeq") if not self.editor.report(): compliant = False self.detailedresults += "Did not find correct contents inside /etc/sysctl.conf\n" return compliant def fix(self): '''parent fix method which calls os-specific private fix methods @author: Derek Walker :returns: self.rulesuccess :rtype: bool ''' self.iditerator = 0 self.detailedresults = "" self.rulesuccess = True osfam = self.environ.getosfamily() try: if not self.ci.getcurrvalue(): return self.rulesuccess #clear out event history so only the latest fix is recorded eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if osfam == "linux": if not self.fixLinux(): self.rulesuccess = False elif osfam == "darwin": if not self.fixMac(): self.rulesuccess = False 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 def fixLinux(self): '''perform linux-specific configuration changes :returns: success :rtype: bool @author: ??? ''' success = True if not self.fix_security_limits(): success = False if not self.fix_sysctl(): success = False return success def fix_sysctl(self): '''set the systemd configuration setting fs.suid_dumpable to 0 :returns: success :rtype: bool @author: ??? ''' success = True # manually writing key and value to /etc/sysctl.conf sysctl = "/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" editor = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, {"fs.suid_dumpable": "0"}, "present", "openeq") if not editor.report(): if editor.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) editor.setEventID(myid) if not editor.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) elif not editor.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(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False resetsecon(sysctl) # using sysctl -w command self.logger.log(LogPriority.DEBUG, "Configuring /etc/sysctl fs.suid_dumpable directive") self.ch.executeCommand("/sbin/sysctl -w fs.suid_dumpable=0") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set core dumps variable suid_dumpable to 0\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.logger.log(LogPriority.DEBUG, "Re-reading sysctl configuration from files") 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) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -w fs.suid_dumpable=1" event = {"eventtype": "commandstring", "command": command} self.statechglogger.recordchgevent(myid, event) return success def fix_security_limits(self): '''ensure the limits.conf file contains the configuration setting * hard core 0 :returns: succcess :rtype: bool @author: ??? ''' success = True path1 = "/etc/security/limits.conf" lookfor1 = "(^\*)\s+hard\s+core\s+0?" created = False if not os.path.exists(path1): if not createFile(path1, self.logger): success = False self.detailedresults += "Unable to create " + path1 + " file\n" else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": "path1"} self.statechglogger.recordchgevent(myid, event) if os.path.exists(path1): if not checkPerms(path1, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(path1, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to correct permissions on " + path1 + "\n" contents = readFile(path1, self.logger) found = False tempstring = "" if contents: for line in contents: if re.search(lookfor1, line.strip()): found = True else: tempstring += line else: found = False if not found: tempstring += "* hard core 0\n" tempfile = path1 + ".stonixtmp" if not writeFile(tempfile, tempstring, self.logger): success = False self.detailedresults += "Unable to write contents to " + path1 + "\n" else: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": path1} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( path1, tempfile, myid) os.rename(tempfile, path1) setPerms(path1, [0, 0, 0o644], self.logger) resetsecon(path1) return success def fixMac(self): '''run fix actions for Mac OS X systems :returns: success :rtype: bool @author: Derek Walker @change: Breen Malmberg - 1/10/2017 - added doc string; default return var init; try/except; fixed command being used to restart sysctl on mac; logging ''' self.logger.log(LogPriority.DEBUG, "System detected as Mac OS X. Running fixMac()...") success = True self.logger.log(LogPriority.DEBUG, "Configuring launchctl limit core directive") self.ch.executeCommand("/usr/bin/launchctl limit core 0 0") retcode = self.ch.getReturnCode() if retcode != 0: success = False errmsg = self.ch.getErrorString() self.detailedresults += "\nFailed to run launchctl command to configure core dumps" self.logger.log(LogPriority.DEBUG, errmsg) return success
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