class RemoveSoftware(Rule): """This class removes any unnecessary software installed on the system""" def __init__(self, config, environ, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 91 self.rulename = "RemoveSoftware" self.mandatory = True self.formatDetailedResults("initialize") self.sethelptext() self.rootrequired = True self.guidance = ["NSA 2.3.5.6"] self.applicable = {'type': 'white', 'family': ['linux', 'freebsd']} # Configuration item instantiation datatype1 = "bool" key1 = "REMOVESOFTWARE" instructions1 = "To disable this rule set the value of REMOVESOFTWARE TO False." default1 = False self.ci = self.initCi(datatype1, key1, instructions1, default1) datatype2 = "list" key2 = "PACKAGES" instructions2 = "Enter the package(s) that you wish to remove. By " + \ "default we already list packages that we recommend for removal." default2 = [ "squid", "telnet-server", "rsh-server", "rsh", "rsh-client", "talk", "talk-server", "talkd", "libpam-ccreds", "pam_ccreds", "tftp-server", "tftp", "tftpd", "udhcpd", "dhcpd", "dhcp", "dhcp-server", "yast2-dhcp-server", "vsftpd", "httpd", "dovecot", "dovecot-imapd", "dovecot-pop3d", "snmpd", "net-snmpd", "net-snmp", "ipsec-tools", "irda-utils", "slapd", "openldap-servers", "openldap2", "bind9", "bind9.i386", "bind", "dnsutils", "bind-utils", "redhat-access-insights", "insights-client" ] self.pkgci = self.initCi(datatype2, key2, instructions2, default2) def report(self): """ report whether any of the packages listed in the self.pkgci ci are installed return False if any are installed return True if none of them are installed :returns: self.compliant :rtype: bool """ self.detailedresults = "" self.compliant = True self.ph = Pkghelper(self.logger, self.environ) packages = self.pkgci.getcurrvalue() self.remove_packages = [] try: if packages: for pkg in packages: if self.ph.check(pkg): self.remove_packages.append(pkg) self.detailedresults += pkg + " is installed\n" self.compliant = False 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 fix(self): """ remove all software packages listed in the self.pkgci list :returns: self.rulesuccess :rtype: bool """ self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 enabled = self.ci.getcurrvalue() try: if not enabled: self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return self.rulesuccess elif not self.remove_packages: self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) 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) for pkg in self.remove_packages: try: if self.ph.remove(pkg): self.iditerator += 1 self.detailedresults += "\nRemoved package: " + str( pkg) myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "pkghelper", "pkgname": pkg, "startstate": "installed", "endstate": "removed" } self.statechglogger.recordchgevent(myid, event) else: self.rulesuccess = False self.detailedresults += "\nFailed to remove package: " + str( pkg) except Exception: continue 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 undo(self): """ Undo changes made by this rule. Some rules may not be undone. self.rulesuccess will be updated if the rule does not succeed. @author D. Kennel & D. Walker """ # pass if not self.environ.geteuid() == 0: self.detailedresults = "Root access required to revert changes." self.formatDetailedResults("undo", None, self.detailedresults) try: # This must be implemented later once the parameter option or observable # pattern for taking control of self.detailedresults refresh is implemented # see artf30937 : self.detailedresults through application flow for details self.detailedresults = "" undosuccessful = True eventlist = self.statechglogger.findrulechanges(self.rulenumber) if not eventlist: self.formatDetailedResults("undo", None, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return undosuccessful #eventlist.reverse() for entry in eventlist: try: event = self.statechglogger.getchgevent(entry) if event["eventtype"] == "perm": perms = event["startstate"] os.chmod(event["filepath"], perms[2]) os.chown(event["filepath"], perms[0], perms[1]) elif event["eventtype"] == "conf": self.statechglogger.revertfilechanges( event["filepath"], entry) elif event["eventtype"] == "comm" or \ event["eventtype"] == "commandstring": ch = CommandHelper(self.logdispatch) command = event["command"] ch.executeCommand(command) if ch.getReturnCode() != 0: self.detailedresults = "Couldn't run the " + \ "command to undo\n" self.logdispatch.log(LogPriority.DEBUG, self.detailedresults) elif event["eventtype"] == "creation": try: os.remove(event["filepath"]) except OSError as oser: if oser.errno == 21: try: os.rmdir(event["filepath"]) except OSError as oserr: if oserr.errno == 39: self.logdispatch.log( LogPriority.DEBUG, "Cannot remove file path: " + str(event["filepath"]) + " because it is a non-empty directory." ) elif oser.errno == 2: self.logdispatch.log( LogPriority.DEBUG, "Cannot remove file path: " + str(event["filepath"]) + " because it does not exist.") elif event["eventtype"] == "deletion": self.statechglogger.revertfiledelete(event["filepath"]) elif event["eventtype"] == "pkghelper": ph = Pkghelper(self.logdispatch, self.environ) if event["startstate"] == "installed": ph.install(event["pkgname"]) elif event["startstate"] == "removed": ph.remove(event["pkgname"]) else: self.detailedresults = 'Invalid startstate for ' \ + 'eventtype "pkghelper". startstate should ' \ + 'either be "installed" or "removed"\n' self.logdispatch.log(LogPriority.ERROR, self.detailedresults) elif event["eventtype"] == "servicehelper": sh = ServiceHelper(self.environ, self.logdispatch) if event["startstate"] == "enabled": sh.enableservice(event["servicename"]) elif event["startstate"] == "disabled": sh.disableservice(event["servicename"]) else: self.detailedresults = 'Invalid startstate for ' \ + 'eventtype "servicehelper". startstate ' + \ 'should either be "enabled" or "disabled"\n' self.logdispatch.log(LogPriority.ERROR, self.detailedresults) except (IndexError, KeyError): self.detailedresults = "EventID " + entry + " not found" self.logdispatch.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: undosuccessful = False self.detailedresults = traceback.format_exc() self.logdispatch.log( LogPriority.ERROR, [self.rulename + ".undo", self.detailedresults]) self.formatDetailedResults("undo", undosuccessful, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return undosuccessful
class DisableWeakAuthentication(Rule): '''This rule will remove rsh(server and client) if installed, remove pam_rhosts entry from any pam file, ''' def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 30 self.rulename = "DisableWeakAuthentication" self.mandatory = True self.formatDetailedResults("initialize") self.guidance = ["NSA 3.2.3.1"] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'] } # Configuration item instantiation datatype = 'bool' key = 'DISABLEWEAKAUTHENTICATION' instructions = "To prevent the disabling of services using weak " + \ "authentication set DISABLEWEAKAUTHENTICATION to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.rsh = ["rshell", "rsh-client", "rsh-server", "rsh", "SUNWrcmdc"] self.pams = [ "/etc/pam.conf", "/etc/pam_ldap.conf", "/etc/pam.conf-winbind" ] self.incorrects = [] self.iditerator = 0 self.sethelptext() def report(self): '''DisableWeakAuthentication.report() Public method to report on the presence of certain r-command packages and contents in pam files. @author: dwalker :returns: bool - False if the method died during execution ''' self.detailedresults = "" try: self.helper = Pkghelper(self.logger, self.environ) compliant = True for item in self.rsh: if self.helper.check(item): compliant = False self.detailedresults += item + " is still installed\n" break for item in self.pams: found = False if os.path.exists(item): contents = readFile(item, self.logger) if contents: for line in contents: if re.match('^#', line) or \ re.match(r'^\s*$', line): continue elif re.search("pam_rhosts", line): found = True compliant = False self.detailedresults += "pam_rhosts line " + \ "found in " + item + "\n" break if found: self.incorrects.append(item) if not checkPerms(item, [0, 0, 420], self.logger): compliant = False self.detailedresults += "Permissions for " + \ item + " are incorrect\n" if os.path.exists("/etc/pam.d/"): fileItems = glob.glob("/etc/pam.d/*") for item in fileItems: found = False if os.path.islink(item) or os.path.isdir(item): continue contents = readFile(item, self.logger) if not contents: continue for line in contents: if re.match('^#', line) or re.match(r'^\s*$', line): continue elif re.search("pam_rhosts", line): found = True compliant = False self.detailedresults += "pam_rhosts line " + \ "found in " + item + "\n" break if found: self.incorrects.append(item) for item in fileItems: if os.path.islink(item) or os.path.isdir(item): continue if not checkPerms(item, [0, 0, 420], self.logger): compliant = False self.detailedresults += "Permissions for " + \ item + " are incorrect\n" break if self.incorrects: debug = "The following files need to be corrected: " + \ str(self.incorrects) + "\n\n" self.logger.log(LogPriority.DEBUG, debug) 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): '''DisableWeakAuthentication.fix() Public method to fix any issues that were found in the report method. @author: dwalker :returns: bool - False if the method died during execution ''' try: self.detailedresults = "" if not self.ci.getcurrvalue(): return success = True for item in self.rsh: if self.helper.check(item): if not self.helper.remove(item): success = False if self.incorrects: for item in self.incorrects: tempstring = "" contents = readFile(item, self.logger) if not contents: continue for line in contents: if re.match('^#', line) or re.match(r'^\s*$', line): tempstring += line elif re.search("pam_rhosts", line): continue else: tempstring += line if not checkPerms(item, [0, 0, 420], self.logger): if not setPerms(item, [0, 0, 420], self.logger): success = False tmpfile = item + ".tmp" if writeFile(tmpfile, tempstring, self.logger): os.rename(tmpfile, item) os.chown(item, 0, 0) os.chmod(item, 420) resetsecon(item) else: success = False for item in self.pams: if os.path.exists(item): if not checkPerms(item, [0, 0, 420], self.logger): if not setPerms(item, [0, 0, 420], self.logger): success = False if os.path.exists("/etc/pam.d/"): fileItems = glob.glob("/etc/pam.d/*") for item in fileItems: if not checkPerms(item, [0, 0, 420], self.logger): if not setPerms(item, [0, 0, 420], self.logger): success = False return 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 def undo(self): '''There is no undo method for this rule since we don't ever want rsh installed or for the r services to be enabled. Overrides the undo inside the rule.py class ''' try: info = "no undo available" self.logger.log(LogPriority.INFO, info) except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) return False
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
def undo(self): """ Undo changes made by this rule. Some rules may not be undone. self.rulesuccess will be updated if the rule does not succeed. @author D. Kennel & D. Walker """ # pass if not self.environ.geteuid() == 0: self.detailedresults = "Root access required to revert changes." self.formatDetailedResults("undo", None, self.detailedresults) try: # This must be implemented later once the parameter option or observable # pattern for taking control of self.detailedresults refresh is implemented # see artf30937 : self.detailedresults through application flow for details self.detailedresults = "" undosuccessful = True eventlist = self.statechglogger.findrulechanges(self.rulenumber) if not eventlist: self.formatDetailedResults("undo", None, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return undosuccessful #eventlist.reverse() for entry in eventlist: try: event = self.statechglogger.getchgevent(entry) if event["eventtype"] == "perm": perms = event["startstate"] os.chmod(event["filepath"], perms[2]) os.chown(event["filepath"], perms[0], perms[1]) elif event["eventtype"] == "conf": self.statechglogger.revertfilechanges(event["filepath"], entry) elif event["eventtype"] == "comm" or \ event["eventtype"] == "commandstring": ch = CommandHelper(self.logdispatch) command = event["command"] ch.executeCommand(command) if ch.getReturnCode() != 0: self.detailedresults = "Couldn't run the " + \ "command to undo\n" self.logdispatch.log(LogPriority.DEBUG, self.detailedresults) elif event["eventtype"] == "creation": try: os.remove(event["filepath"]) except OSError as oser: if oser.errno == 21: try: os.rmdir(event["filepath"]) except OSError as oserr: if oserr.errno == 39: self.logdispatch.log(LogPriority.DEBUG, "Cannot remove file path: " + str(event["filepath"]) + " because it is a non-empty directory.") elif oser.errno == 2: self.logdispatch.log(LogPriority.DEBUG, "Cannot remove file path: " + str(event["filepath"]) + " because it does not exist.") elif event["eventtype"] == "deletion": self.statechglogger.revertfiledelete(event["filepath"]) elif event["eventtype"] == "pkghelper": ph = Pkghelper(self.logdispatch, self.environ) if event["startstate"] == "installed": ph.install(event["pkgname"]) elif event["startstate"] == "removed": ph.remove(event["pkgname"]) else: self.detailedresults = 'Invalid startstate for ' \ + 'eventtype "pkghelper". startstate should ' \ + 'either be "installed" or "removed"\n' self.logdispatch.log(LogPriority.ERROR, self.detailedresults) elif event["eventtype"] == "servicehelper": sh = ServiceHelper(self.environ, self.logdispatch) if event["startstate"] == "enabled": sh.enableservice(event["servicename"]) elif event["startstate"] == "disabled": sh.disableservice(event["servicename"]) else: self.detailedresults = 'Invalid startstate for ' \ + 'eventtype "servicehelper". startstate ' + \ 'should either be "enabled" or "disabled"\n' self.logdispatch.log(LogPriority.ERROR, self.detailedresults) except(IndexError, KeyError): self.detailedresults = "EventID " + entry + " not found" self.logdispatch.log(LogPriority.DEBUG, self.detailedresults) except(KeyboardInterrupt, SystemExit): raise except Exception: undosuccessful = False self.detailedresults = traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, [self.rulename + ".undo", self.detailedresults]) self.formatDetailedResults("undo", undosuccessful, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return undosuccessful
class DisableUbuntuDataCollection(Rule): def __init__(self, config, environ, logger, statechglogger): ''' :param config: :param environ: :param logger: :param statechglogger: ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.environ = environ self.rulenumber = 311 self.rulename = "DisableUbuntuDataCollection" self.mandatory = True self.rootrequired = True self.formatDetailedResults("initialize") self.guidance = [] self.applicable = {'type': 'white', 'os': {'Ubuntu': ['16.04', '+']}} self.sethelptext() datatype = "bool" key = "DISABLEUBUNTUDATACOLLECTION" instructions = """To prevent the diabling of data collection from this system, set the value of DISABLEUBUNTUDATACOLLECTION to False.""" default = True self.enabledCI = self.initCi(datatype, key, instructions, default) def report(self): '''Check for the existence of any of a number of data-collection and reporting utilities on the system report compliance status as not compliant if any exist report compliance status as compliant if none exist :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.ph = Pkghelper(self.logger, self.environ) self.compliant = True self.pkgslist = ["popularity-contest", "apport", "ubuntu-report"] self.removepkgs = [] try: for pkg in self.pkgslist: if self.ph.check(pkg): self.compliant = False self.detailedresults += "\nData collection utility: " + str( pkg) + " is still installed" self.removepkgs.append(pkg) except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): '''Remove any data-collection and reporting utilities from the system report success status as True if all are removed report success status as False if any remain :returns: self.rulesuccess :rtype: bool @author: Breen Malmberg ''' self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: if self.enabledCI.getcurrvalue(): eventlist = self.statechglogger.findrulechanges( self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) for pkg in self.removepkgs: if not self.ph.remove(pkg): self.detailedresults += "\nUnable to remove package: " + str( pkg) self.rulesuccess = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() + pkg event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) self.logger.log(LogPriority.DEBUG, "Removing package: " + str(pkg)) else: self.logger.log(LogPriority.DEBUG, "Rule was NOT enabled. Nothing was done.") except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class SecureMDNS(Rule): '''The Avahi daemon implements the DNS Service Discovery and Multicast DNS protocols, which provide service and host discovery on a network. It allows a system to automatically identify resources on the network, such as printers or web servers. This capability is also known as mDNSresponder and is a major part of Zeroconf networking. By default, it is enabled. This rule makes a number of configuration changes to the avahi service in order to secure it. @change: 04/16/2014 ekkehard ci and self.setkvdefaultscurrenthost updates ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 135 self.rulename = 'SecureMDNS' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.compliant = False self.rulesuccess = True self.guidance = [ 'NSA(3.7.2)', 'CCE 4136-8', 'CCE 4409-9', 'CCE 4426-3', 'CCE 4193-9', 'CCE 4444-6', 'CCE 4352-1', 'CCE 4433-9', 'CCE 4451-1', 'CCE 4341-4', 'CCE 4358-8', 'CCE-RHEL7-CCE-TBD 2.5.2' ] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # set up command helper object self.ch = CommandHelper(self.logger) # init helper classes self.sh = ServiceHelper(self.environ, self.logger) self.serviceTarget = "" if self.environ.getostype() == "Mac OS X": self.ismac = True self.hasSIP = False self.plb = "/usr/libexec/PlistBuddy" osxversion = str(self.environ.getosver()) versplit = osxversion.split(".") if len(versplit) > 2: minorVersion = int(versplit[1]) releaseVersion = int(versplit[2]) elif len(versplit) == 2: minorVersion = int(versplit[1]) releaseVersion = 0 else: self.logger.log(LogPriority.ERROR, "Unexpected version string length") raise Exception if minorVersion == 10 and releaseVersion < 4: self.service = "/System/Library/LaunchDaemons/com.apple." + \ "discoveryd.plist" self.servicename = "com.apple.networking.discoveryd" self.parameter = "--no-multicast" self.pbr = self.plb + " -c Print " + self.service + \ " | grep 'no-multicast'" self.pbf = self.plb + ' -c "Add :ProgramArguments: string ' + \ self.parameter + '" ' + self.service elif minorVersion > 10: self.hasSIP = True self.service = "/System/Library/LaunchDaemons/" + \ "com.apple.mDNSResponder.plist" self.servicename = "com.apple.mDNSResponder.reloaded" self.parameter = "NoMulticastAdvertisements" self.preferences = "/Library/Preferences/" + \ "com.apple.mDNSResponder.plist" self.pbr = self.plb + " -c Print " + self.preferences + \ " | grep 'NoMulticastAdvertisements'" self.pbf = "defaults write " + self.preferences + " " + \ self.parameter + " -bool YES" else: self.service = "/System/Library/LaunchDaemons/" + \ "com.apple.mDNSResponder.plist" if minorVersion >= 10: self.servicename = "com.apple.mDNSResponder.reloaded" else: self.servicename = "com.apple.mDNSResponder" self.parameter = "-NoMulticastAdvertisements" self.pbr = self.plb + " -c Print " + self.service + \ " | grep 'NoMulticastAdvertisements'" self.pbf = self.plb + ' -c "Add :ProgramArguments: string ' + \ self.parameter + '" ' + self.service else: self.ismac = False # init CIs datatype = 'bool' mdnskey = 'SECUREMDNS' avahikey = 'DISABLEAVAHI' mdnsinstructions = 'To configure the Avahi server daemon ' + \ 'securely set the value of SECUREMDNS to True and the ' + \ 'value of DISABLEAVAHI to False.' avahiinstructions = 'To completely disable the Avahi server ' + \ 'daemon rather than configure it, set the value of ' + \ 'DISABLEAVAHI to True and the value of SECUREMDNS to False.' mdnsdefault = False avahidefault = True self.SecureMDNS = self.initCi(datatype, mdnskey, mdnsinstructions, mdnsdefault) self.DisableAvahi = self.initCi(datatype, avahikey, avahiinstructions, avahidefault) self.configparser = configparser.SafeConfigParser() self.confavahidict = { 'use-ipv6': { 'section': 'server', 'val': 'no' }, 'check-response-ttl': { 'section': 'server', 'val': 'yes' }, 'disallow-other-stacks': { 'section': 'server', 'val': 'yes' }, 'disable-publishing': { 'section': 'publish', 'val': 'yes' }, 'disable-user-service-publishing': { 'section': 'publish', 'val': 'yes' }, 'publish-addresses': { 'section': 'publish', 'val': 'no' }, 'publish-hinfo': { 'section': 'publish', 'val': 'no' }, 'publish-workstation': { 'section': 'publish', 'val': 'no' }, 'publish-domain': { 'section': 'publish', 'val': 'no' } } self.confoptions = { 'server': { 'use-ipv6': 'no', 'check-response-ttl': 'yes', 'disallow-other-stacks': 'yes' }, 'publish': { 'disable-publishing': 'yes', 'disable-user-service-publishing': 'yes', 'publish-addresses': 'no', 'publish-hinfo': 'no', 'publish-workstation': 'no', 'publish-domain': 'no' } } self.iditerator = 0 def report(self): '''The report method examines the current configuration and determines whether or not it is correct. If the config is correct then the self.compliant, self.detailed results and self.currstate properties are updated to reflect the system status. self.rulesuccess will be updated if the rule does not succeed. :returns: bool @author: Breen Malmberg @change: dwalker - added conditional call to reportmac() @change: Breen Malmberg - 12/05/2017 - removed unnecessary argument "serviceTarget" in linux-only call to servicehelper; removed assignment of unused local variable serviceTarget to self.servicename since servicename is not assigned in the linux code logic path (which was resulting in variable referenced before assignment error) ''' try: # defaults compliant = True self.detailedresults = '' self.rulesuccess = True # if system is a mac, run reportmac if self.ismac: compliant = self.reportmac() # if not mac os x, then run this portion else: self.editor = None # set up package helper object only if not mac os x self.pkghelper = Pkghelper(self.logger, self.environ) # if the disableavahi CI is set, we want to make sure it is # completely disabled if self.DisableAvahi.getcurrvalue(): self.package = "avahi-daemon" # if avahi-daemon is still running, it is not disabled if self.sh.auditService('avahi-daemon'): compliant = False self.detailedresults += 'DisableAvahi has been ' + \ 'set to True, but avahi-daemon service is ' + \ 'currently running.\n' self.numdependencies = 0 if self.pkghelper.determineMgr() == 'yum' or \ self.pkghelper.determineMgr() == 'dnf': self.package = "avahi" # The following KVEditor for /etc/sysconfig/network is # used to meet the zeroconf requirement in # CCE-RHEL7-CCE-TBD 2.5.2 path = "/etc/sysconfig/network" self.path = path if os.path.exists(path): tmppath = path + ".tmp" data = {"NOZEROCONF": "yes"} self.editor = KVEditorStonix( self.statechglogger, self.logger, "conf", path, tmppath, data, "present", "closedeq") if not self.editor.report(): self.compliant = False self.detailedresults += path + " does not " + \ "have the correct settings.\n" else: self.compliant = False self.detailedresults += path + " does not exist.\n" self.numdependencies = \ self.parseNumDependencies(self.package) if self.numdependencies <= 3: if self.pkghelper.check(self.package): compliant = False self.detailedresults += 'DisableAvahi is ' + \ 'set to True, but Avahi is currently ' + \ 'installed.\n' else: self.detailedresults += 'Avahi has too many ' + \ 'dependent packages. Will not attempt to ' + \ 'remove it.\n' elif self.pkghelper.determineMgr() == "zypper": self.package = "avahi" elif self.pkghelper.check(self.package): compliant = False self.detailedresults += 'DisableAvahi is ' + \ 'set to True, but Avahi is currently ' + \ 'installed.\n' # otherwise if the securemdns CI is set, we want to make sure # it is securely configured if self.SecureMDNS.getcurrvalue(): # if the config file is found, proceed if os.path.exists('/etc/avahi/avahi-daemon.conf'): kvtype = "tagconf" intent = "present" filepath = '/etc/avahi/avahi-daemon.conf' tmpfilepath = '/etc/avahi/avahi-daemon.conf.stonixtmp' conftype = "closedeq" self.avahiconfeditor = KVEditorStonix( self.statechglogger, self.logger, kvtype, filepath, tmpfilepath, self.confoptions, intent, conftype) self.avahiconfeditor.report() if self.avahiconfeditor.fixables: compliant = False self.detailedresults += "\nThe following configuration options are missing or incorrect in " + str( filepath) + ":\n" + "\n".join( self.avahiconfeditor.fixables) # if config file not found, check if avahi is installed else: # if not installed, we can't configure anything if not self.pkghelper.check('avahi'): self.detailedresults += 'Avahi Daemon not ' + \ 'installed. Cannot configure it.\n' compliant = True self.logger.log(LogPriority.DEBUG, self.detailedresults) # if it is installed, then the config file is missing else: compliant = False self.detailedresults += 'Avahi is installed ' + \ 'but could not find config file in ' + \ 'expected location.\n' self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = compliant except (IOError): self.detailedresults += '\n' + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def reportmac(self): '''check for configuration items needed for mac os x :returns: bool @author: Breen Malmberg @change: dwalker - implemented kveditor defaults ''' try: self.detailedresults = "" # See if parameter is set self.ch.executeCommand(self.pbr) resultOutput = self.ch.getOutput() if len(resultOutput) >= 1: if (resultOutput[0] == ""): commandsuccess = False self.detailedresults += "Parameter: " + str(self.parameter) + \ " for service " + self.servicename + " is not set.\n" else: commandsuccess = True debug = "Parameter: " + str(self.parameter) + \ " for service " + self.servicename + \ " is set correctly." self.logger.log(LogPriority.DEBUG, debug) else: commandsuccess = False self.detailedresults += "Parameter: " + str(self.parameter) + \ " for service " + self.servicename + " is not set.\n" # see if service is running if not re.match("^10.11", self.environ.getosver()): servicesuccess = self.sh.auditService( self.service, serviceTarget=self.servicename) else: servicesuccess = self.sh.auditService( self.service, serviceTarget=self.servicename) if servicesuccess: debug = "Service: " + str(self.service) + ", " + \ self.servicename + " audit successful." self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += "Service: " + str(self.service) + \ ", " + self.servicename + " audit failed.\n" if servicesuccess and commandsuccess: return True else: return False except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False raise return self.compliant def fix(self): '''The fix method will apply the required settings to the system. self.rulesuccess will be updated if the rule does not succeed. @author: Breen Malmberg @change: dwalker - added statechglogger findrulechanges and deleteentry @changed: Breen Malmberg - 12/05/2017 - removed unnecessary servicetarget ''' try: self.rulesuccess = True self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) self.detailedresults = "" # if this system is a mac, run fixmac() if self.ismac: self.rulesuccess = self.fixmac() # if not mac os x, run this portion else: # if DisableAvahi CI is enabled, disable the avahi service # and remove the package if self.DisableAvahi.getcurrvalue(): avahi = self.package avahid = 'avahi-daemon' if self.sh.auditService(avahid): debug = "Disabling " + avahid + " service" self.logger.log(LogPriority.DEBUG, debug) self.sh.disableService(avahid) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "servicehelper", "servicename": avahid, "startstate": "enabled", "endstate": "disabled" } self.statechglogger.recordchgevent(myid, event) if self.environ.getosfamily() == 'linux' and \ self.pkghelper.check(avahi): if self.numdependencies <= 3: self.pkghelper.remove(avahi) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "pkghelper", "pkgname": avahi, "startstate": "installed", "endstate": "removed" } self.statechglogger.recordchgevent(myid, event) else: debug += 'Avahi package has too many dependent ' \ + 'packages. Will not attempt to remove.\n' self.logger.log(LogPriority.DEBUG, debug) if self.pkghelper.determineMgr() == 'yum' or \ self.pkghelper.determineMgr() == 'dnf': path = self.path if not os.path.exists(path): if createFile(path, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": path } self.statechglogger.recordchgevent(myid, event) else: self.rulesuccess = False self.detailedresults += "Failed to create " + \ "file: " + path + ".\n" if self.editor is None: tmppath = path + ".tmp" data = {"NOZEROCONF": "yes"} self.editor = KVEditorStonix( self.statechglogger, self.logger, "conf", path, tmppath, data, "present", "closedeq") if not self.editor.report(): if self.editor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.commit(): self.rulesuccess = False self.detailedresults += "Could not " + \ "commit changes to " + path + ".\n" else: self.rulesuccess = False self.detailedresults += "Could not fix " + \ "file " + path + ".\n" # if SecureMDNS CI is enabled, configure avahi-daemon.conf if self.SecureMDNS.getcurrvalue(): # if config file is present, proceed avahiconf = '/etc/avahi/avahi-daemon.conf' if os.path.exists(avahiconf): if self.avahiconfeditor.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.avahiconfeditor.setEventID(myid) if not self.avahiconfeditor.fix(): self.rulesuccess = False debug = "KVEditor fix for " + avahiconf + \ "failed" self.logger.log(LogPriority.DEBUG, debug) elif not self.avahiconfeditor.commit(): self.rulesuccess = False debug = "KVEditor commit for " + avahiconf + \ "failed" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) setPerms(avahiconf, [0, 0, 0o644], self.logger, self.statechglogger, myid) resetsecon(avahiconf) # if config file is not present and avahi not installed, # then we can't configure it else: if not self.pkghelper.check(avahi): debug = 'Avahi Daemon not installed. ' + \ 'Cannot configure it.' self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += 'Avahi daemon ' + \ 'installed, but could not locate the ' + \ 'configuration file for it.\n' self.rulesuccess = False except IOError: self.detailedresults += '\n' + traceback.format_exc() self.logger.log(LogPriority.DEBUG, self.detailedresults) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixmac(self): '''apply fixes needed for mac os x @author: Breen Malmberg @change: dwalker - implemented kveditor instead of direct editing ''' try: self.detailedresults = "" success = True # See if parameter is set self.ch.executeCommand(self.pbr) resultOutput = self.ch.getOutput() if len(resultOutput) >= 1: if (resultOutput[0] == ""): fixit = True else: fixit = False else: fixit = True # Add parameter if fixit: # Due to weaknesses in using PlistBuddy and defaults to delete # from plists, as well as shortcomings in STONIX's state change # logging, we will record this change as a file deletion. # If the rule's undo is run on OS X, it will restore the # previous version of this file. self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if self.hasSIP: self.statechglogger.recordfiledelete( self.preferences, myid) else: self.statechglogger.recordfiledelete(self.service, myid) self.ch.executeCommand(self.pbf) resultOutput = self.ch.getOutput() errorcode = self.ch.getReturnCode() if errorcode == 0: debug = self.parameter + " was set successfully!" self.logger.log(LogPriority.DEBUG, debug) else: self.detailedresults += self.parameter + \ " was not set successfully!\n" self.statechglogger.deleteentry(myid) success = False else: debug = self.parameter + " was already set!" self.logger.log(LogPriority.DEBUG, debug) # Reload Service if success: success = self.sh.reloadService(self.service, serviceTarget=self.servicename) if success: debug = "Service: " + str(self.service) + ", " + \ self.servicename + " was reloaded successfully." self.logger.log(LogPriority.DEBUG, debug) else: debug = "Service: " + str(self.service) + ", " + \ self.servicename + " reload failed!" self.logger.log(LogPriority.DEBUG, debug) except (KeyboardInterrupt, SystemExit): raise except Exception: success = False raise return success def parseNumDependencies(self, pkgname): '''parse output of yum command to determine number of dependent packages to the given pkgname :param pkgname: :returns: int @author: Breen Malmberg ''' numdeps = 0 flag = 0 try: if self.pkghelper.determineMgr() == 'zypper': command = ['zypper', 'info', '--requires', pkgname] self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if flag: numdeps += 1 if re.search('Requires:', line): flag = 1 elif self.pkghelper.determineMgr() == 'yum': command = ['yum', '--assumeno', 'remove', pkgname] self.ch.wait = False self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('Dependent packages\)', line): sline = line.split('(+') if len(sline) < 2: return numdeps cline = [ int(s) for s in sline[1].split() if s.isdigit() ] numdeps = int(cline[0]) elif self.pkghelper.determineMgr() == 'dnf': command = ['dnf', '--assumeno', 'remove', pkgname] self.ch.wait = False self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('Dependent packages\)', line): sline = line.split('(+') if len(sline) < 2: return numdeps cline = [ int(s) for s in sline[1].split() if s.isdigit() ] numdeps = int(cline[0]) elif self.pkghelper.determineMgr() == 'apt-get': command = ['apt-cache', 'depends', pkgname] self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('Depends:', line): numdeps += 1 else: self.detailedresults += 'Unable to detect package manager\n' return numdeps except (IOError, OSError): self.detailedresults += 'Specified package: ' + str(pkgname) + \ ' not found.\n' return numdeps except Exception: raise return numdeps
class DisableRemoveableStorage(Rule): '''Disable removeable storage. This rule is optional, and disables USB, thunderbolt and firewire storage devices from accessing, or being accessed, by the system. ''' 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 = 29 self.rulename = 'DisableRemoveableStorage' self.mandatory = False self.formatDetailedResults("initialize") self.guidance = ['NSA 2.2.2.2, CIS, NSA(2.2.2.2), cce-4006-3,4173-1'] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] }, 'fisma': 'high' } # configuration item instantiation datatype = "bool" key = "DISABLEREMOVEABLESTORAGE" instructions = "To disable removeable storage devices on this system, set the value of DISABLEREMOVEABLESTORAGE to True" default = False self.storageci = self.initCi(datatype, key, instructions, default) #global variables self.grubfiles = [ "/boot/grub2/grub.cfg", "/boot/grub/grub.cfg", "/boot/grub/grub.conf" ] self.pcmcialist = ['pcmcia-cs', 'kernel-pcmcia-cs', 'pcmciautils'] self.pkgremovedlist = [] self.iditerator = 0 self.created = False self.daemonpath = os.path.abspath( os.path.join(os.path.dirname( sys.argv[0]))) + "/stonix_resources/disablestorage.py" self.sethelptext() self.grubperms = "" self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) def report(self): '''report the current rule-compliance status of this system. update self.rulesuccess if method does not succeed. self.compliant if rule succeeds and reports true. :returns: self.compliant :rtype: bool @author: Breen Malmberg @change: dwalker - implementing kveditor and completely revamped rule logic. added event deletion at the beginning of the fix @change: dwalker 8/13/2014 changed name of rule to DisableRemoveableStorage and rule now supports disabling other ports such thunderbolt and firewire ''' try: # defaults self.detailedresults = "" if self.environ.getostype() == "Mac OS X": compliant = self.reportMac() else: compliant = self.reportLinux() self.compliant = compliant except OSError: self.detailedresults = traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def reportLinux(self): '''sub method for linux portion of compliance reporting @author: dwalker :returns: compliant :rtype: boolean ''' compliant = True lsmodcmd = "" # determine location of lsmod binary if os.path.exists("/sbin/lsmod"): lsmodcmd = "/sbin/lsmod" elif os.path.exists("/usr/bin/lsmod"): lsmodcmd = "/usr/bin/lsmod" usbmods = ["usb_storage", "usb-storage"] # run lsmod command and look for any of the items from # usbmods list in the output. If item exists in output # then that usb module is not disabled. This is for # reporting only. There is no fix using lsmod command. if lsmodcmd: for usb in usbmods: cmd = [lsmodcmd, "|", "grep", usb] self.ch.executeCommand(cmd) if self.ch.getReturnCode() == "0": compliant = False self.detailedresults += "lsmod command shows usb not disabled\n" break # check compliance of grub file(s) if files exist if re.search("Red Hat", self.environ.getostype()) and \ re.search("^6", self.environ.getosver()): self.grubperms = [0, 0, 0o600] elif self.ph.manager is "apt-get": self.grubperms = [0, 0, 0o400] else: self.grubperms = [0, 0, 0o644] for grub in self.grubfiles: if os.path.exists(grub): if self.grubperms: if not checkPerms(grub, self.grubperms, self.logger): compliant = False self.detailedresults += "Permissions " + \ "incorrect on " + grub + " file\n" contents = readFile(grub, self.logger) if contents: for line in contents: if re.search("^kernel", line.strip()) \ or re.search("^linux", line.strip()) \ or re.search("^linux16", line.strip()) \ or re.search("^set default_kernelopts", line.strip()): if not re.search("\s+nousb\s*", line): debug = grub + " file doesn't " + \ "contain nousb kernel option\n" self.detailedresults += grub + " file doesn't " + \ "contain nousb kernel option\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False if not re.search( "\s+usbcore\.authorized_default=0\s*", line): debug = grub + " file doesn't " + \ "contain usbcore.authorized_default=0 " + \ "kernel option\n" self.detailedresults += grub + " file doesn't " + \ "contain usbcore.authorized_default=0 " + \ "kernel option\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False # check for existence of certain usb packages, non-compliant # if any exist for item in self.pcmcialist: if self.ph.check(item): self.detailedresults += item + " is installed " + \ "and shouldn't be\n" compliant = False # check modprobe files inside modprobe.d directory for # contents inside self.blacklist variable removeables = [] found1 = True # self.blacklist dictionary contains the directives # and the value we're looking for (key) and contains # a default value of False for each one. Upon finding # each directive and value pair e.g. blacklist usb_storage # the dictionary is updated with a True value. This keeps # track of the directives we didnt find or that had # incorrect values self.blacklist = { "blacklist usb_storage": False, "install usbcore /bin/true": False, "install usb-storage /bin/true": False, "blacklist uas": False, "blacklist firewire-ohci": False, "blacklist firewire-sbp2": False } #check if /etc/modprobe.d directory exists if os.path.exists("/etc/modprobe.d"): #extract all files inside modprobe.d dirs = glob.glob("/etc/modprobe.d/*") # since file name doesn't matter # i.e. all files are read and treated the same in # modprobe.d, if directives are found in any of # the files inside this directory, where they don't # have to be in the same file, the system is compliant for directory in dirs: if os.path.isdir(directory): continue contents = readFile(directory, self.logger) for item in self.blacklist: for line in contents: if re.search("^" + item, line.strip()): self.blacklist[item] = True # if we don't find all directives in any of the files in # modprobe.d, we will now check /etc/modprobe.conf as # they are all equivalent. We will still keep track of # whether we already found one directive in one of the # files in modprobe.d for item in self.blacklist: if not self.blacklist[item]: found1 = False else: found1 = False # either not all directives inside self.blacklist were found # or /etc/modprobe.d didn't exist. Now we check /etc/modprobe.conf # for any remaining unfound directives. if not found1: if os.path.exists("/etc/modprobe.conf"): contents = readFile("/etc/modprobe.conf") if contents: for item in self.blacklist: for line in contents: if re.search("^" + item, line.strip()): self.blacklist[item] = True for item in self.blacklist: if not self.blacklist[item]: debug = "modprobe.conf nor blacklist " + \ "files contain " + item + "\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False # any directives that were found we remove from self.blacklist # We must add to a variable called removeables first then # iterate through removeables and remove each item self.blacklist for item in self.blacklist: if self.blacklist[item]: removeables.append(item) for item in removeables: del (self.blacklist[item]) # check the contents of the udev file for a certain desired line self.udevfile = "/etc/udev/rules.d/10-local.rules" found2 = False if os.path.exists(self.udevfile): if not checkPerms(self.udevfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions not correct " + \ "on " + self.udevfile + "\n" compliant = False contents = readFile(self.udevfile, self.logger) for line in contents: if re.search( "ACTION\=\=\"add\"\, SUBSYSTEMS\=\=\"usb\"\, RUN\+\=\"/bin/sh \-c \'for host in /sys/bus/usb/devices/usb\*\; do echo 0 \> \$host/authorized\_default\; done\'\"", line.strip()): found2 = True if not found2: self.detailedresults += "Udev rule not found to disable usb at boot\n" debug = "Udev rule not found to disable usb at boot\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False else: self.detailedresults += "Udev file doesn't exist to disable usb at boot\n" debug = "Udev file doesn't exist to disable usb at boot\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False return compliant def reportMac(self): ''' :returns: compliant :rtype: bool ''' self.detailedresults = "" compliant = True self.setvars() if not self.usbprofile: self.detailedresults += "Could not locate the appropriate usb disablement profile for your system.\n" compliant = False self.formatDetailedResults("report", compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return compliant self.usbdict = { "com.apple.systemuiserver": { "harddisk-external": { "val": ["deny", "eject"], "type": "", "accept": "", "result": False } } } self.usbeditor = KVEditorStonix(self.statechglogger, self.logger, "profiles", self.usbprofile, "", self.usbdict, "", "") if not self.usbeditor.report(): if self.usbeditor.badvalues: self.detailedresults += self.usbeditor.badvalues + "\n" self.detailedresults += "USB Disablement profile either not installed or values are incorrect\n" compliant = False return compliant def setvars(self): self.usbprofile = "" baseconfigpath = "/Applications/stonix4mac.app/Contents/Resources/stonix.app/Contents/MacOS/stonix_resources/files/" self.usbprofile = baseconfigpath + "stonix4macDisableUSB.mobileconfig" # the following path and dictionaries are for testing on local vm's # without installing stonix package each time. DO NOT DELETE # basetestpath = "/Users/username/stonix/src/stonix_resources/files/" # self.usbprofile = basetestpath + "stonix4macDisableUSB.mobileconfig" if not os.path.exists(self.usbprofile): self.logger.log( LogPriority.DEBUG, "Could not locate appropriate usb disablement profile\n") self.usbprofile = "" def fix(self): '''attempt to perform necessary operations to bring the system into compliance with this rule. @author Breen Malmberg @change: dwalker - implemented event deletion at the beginning of fix, also implemented a check for the ci value to see if fix should even be run. ''' try: success = True self.detailedresults = "" # clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not self.storageci.getcurrvalue(): self.detailedresults += "Rule not enabled so nothing was done\n" self.logger.log(LogPriority.DEBUG, 'Rule was not enabled, so nothing was done') return if self.environ.getostype() == "Mac OS X": success = self.fixMac() else: success = self.fixLinux() 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 def fixMac(self): '''This method will attempt to disable certain storage ports by moving certain kernel extensions. If the check box is checked we will move the kernel (if present) associated with that storage port/device into a folder designated for those disabled extensions. If the check box is unchecked, we will assume the user doesn't want this disabled and if the kernel is no longer where it should be, we will check the disabled extensions folder to see if it was previously disabled. If it's in that folder, we will move it back. @author: Breen Malmberg :returns: bool @change: dwalker 8/19/2014 ''' success = True if not self.usbprofile: return False if not self.usbeditor.report(): if self.usbeditor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.usbeditor.setEventID(myid) if not self.usbeditor.commit(): success = False self.detailedresults += "Unable to install " + self.usbprofile + " profile\n" self.logdispatch.log(LogPriority.DEBUG, "Kveditor commit failed") else: success = False self.detailedresults += "Unable to install " + self.passprofile + "profile\n" self.logdispatch.log(LogPriority.DEBUG, "Kveditor fix failed") else: success = False self.detailedresults += "Password CI was not enabled.\n" return success def fixLinux(self): '''sub method for linux portion of compliance fixing @author: dwalker :returns: success :rtype: boolean ''' success = True created1, created2 = False, False changed = False tempstring = "" grubfilefound = False for grub in self.grubfiles: if os.path.exists(grub): grubfilefound = True if self.grubperms: if not checkPerms(grub, self.grubperms, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(grub, self.grubperms, self.logger, self.statechglogger, myid): success = False contents = readFile(grub, self.logger) kernellinefound = False if contents: for line in contents: if re.search("^kernel", line.strip()) or re.search("^linux", line.strip()) \ or re.search("^linux16", line.strip()): kernellinefound = True if not re.search("\s+nousb\s*", line): changed = True tempstring += line.strip() + " nousb" if not re.search( "\s+usbcore\.authorized_default=0\s+", line): changed = True tempstring += line.strip( ) + " usbcore.authorized_default=0" tempstring += "\n" elif re.search("^set default_kernelopts", line.strip()): # Fedora 31 has changed it's kernel option line format kernellinefound = True kernelline = line.strip() if not re.search("\s+nousb\s*", kernelline): changed = True kernelline = re.sub("\"$", " nousb\"", kernelline) if not re.search( "\s+usbcore\.authorized_default=0\s+", kernelline): changed = True kernelline = re.sub( "\"$", " usbcore.authorized_default=0\"", kernelline) tempstring += kernelline + "\n" else: tempstring += line if not kernellinefound: changed = False self.detailedresults += "The grub file doesn't contain kernel line\n" + \ "Unable to fully implement fixes in this rule\n" success = False if changed: tmpfile = grub + ".tmp" if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": grub} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( grub, tmpfile, myid) os.rename(tmpfile, grub) if not setPerms(grub, self.grubperms, self.logger): success = False self.detailedresults += "Unable to set permissions on " + \ grub + " file\n" else: success = False if not grubfilefound: self.detailedresults += "No grub configuration file found\n" + \ "Unable to fully fix system for this rule\n" success = False blacklistf = "/etc/modprobe.d/stonix-blacklist.conf" tempstring = "" # Check if self.blacklist still contains values, if it # does, then we didn't find all the blacklist values # in report if self.blacklist: # didn't find one or more directives in the files # inside modprobe.d so we now check an alternate file # we create stonixblacklist file if it doesn't # exist and put remaining unfound blacklist # items there if not os.path.exists(blacklistf): created1 = True createFile(blacklistf, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": blacklistf} self.statechglogger.recordchgevent(myid, event) # file was already present and we need contents already # inside file to remain in newly written file if not created1: contents = readFile(blacklistf, self.logger) for item in contents: tempstring += item for item in self.blacklist: tempstring += item + "\n" tmpfile = blacklistf + ".tmp" if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": blacklistf} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(blacklistf, tmpfile, myid) os.rename(tmpfile, blacklistf) os.chown(blacklistf, 0, 0) os.chmod(blacklistf, 420) resetsecon(blacklistf) if not checkPerms(blacklistf, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(blacklistf, [0, 0, 420], self.logger, self.statechglogger, myid): success = False if self.ph.manager == "apt-get": cmd = ["/usr/sbin/update-initramfs", "-u"] if not self.ch.executeCommand(cmd): success = False self.detailedresults += "Unable to run update-initramfs command\n" for item in self.pcmcialist: if self.ph.check(item): self.ph.remove(item) self.pkgremovedlist.append(item) if not os.path.exists(self.udevfile): if not createFile(self.udevfile, self.logger): self.detailedresults += "Unable to create " + \ self.udevfile + " file\n" success = False else: created2 = True if os.path.exists(self.udevfile): if not checkPerms(self.udevfile, [0, 0, 0o644], self.logger): if created2: if not setPerms(self.udevfile, [0, 0, 0o644], self.logger): success = False self.detailedresults += "Unable to set " + \ "permissions on " + self.udevfile + "\n" else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.udevfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set " + \ "permissions on " + self.udevfile + "\n" found = False contents = readFile(self.udevfile, self.logger) tempstring = "" for line in contents: if re.search( "ACTION==\"add\"\, SUBSYSTEMS==\"usb\"\, RUN+=\"/bin/sh -c \'for host in /sys/bus/usb/devices/usb\*\; do echo 0 > \$host/authorized_default; done\'\"", line.strip()): found = True tempstring += line if not found: tempstring += "ACTION==\"add\", SUBSYSTEMS==\"usb\", RUN+=\"/bin/sh -c \'for host in /sys/bus/usb/devices/usb*; do echo 0 > $host/authorized_default; done\'\"" tmpfile = self.udevfile + ".tmp" if writeFile(tmpfile, tempstring, self.logger): if not created2: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "conf", "filepath": self.udevfile } self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( self.udevfile, tmpfile, myid) os.rename(tmpfile, self.udevfile) os.chown(self.udevfile, 0, 0) os.chmod(self.udevfile, 0o644) resetsecon(self.udevfile) else: success = False self.detailedresults += "Unable to write changes " + \ "to " + self.udevfile + "\n" return success
class RemoveSUIDGames(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 244 self.rulename = "RemoveSUIDGames" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.applicable = {'type': 'white', 'family': 'linux'} # Configuration item instantiation datatype = "bool" key = "REMOVESUIDGAMES" instructions = "To disable this rule, set the value of " + \ "REMOVESUIDGAMES to false." default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ["LANL 15.7"] self.iditerator = 0 self.ph = Pkghelper(self.logger, self.environ) self.gamelist = [ 'atlantik', 'bomber', 'bovo', 'gnuchess', 'kapman', 'kasteroids', 'katomic', 'kbackgammon', 'kbattleship', 'kblackbox', 'kblocks', 'kbounce', 'kbreakout', 'kdiamond', 'kenolaba', 'kfouleggs', 'kfourinline', 'kgoldrunner', 'killbots', 'kiriki', 'kjumpingcube', 'klickety', 'klines', 'kmahjongg', 'kmines', 'knetwalk', 'kolf', 'kollision', 'konquest', 'kpat', 'kpoker', 'kreversi', 'ksame', 'kshisen', 'ksirk', 'ksirkskineditor', 'ksirtet', 'ksmiletris', 'ksnake', 'kspaceduel', 'ksquares', 'ksudoku', 'ktron', 'ktuberling', 'kubrick', 'kwin4', 'kwin4proc', 'lskat', 'lskatproc', 'gnome-games', 'kdegames', 'gnome-taquin' ] def report(self): try: compliant = True self.gamesfound = [] self.gamedirsfound = [] for game in self.gamelist: if self.ph.check(game): self.gamesfound.append(game) compliant = False self.detailedresults += game + " installed on system\n" if os.path.exists("/usr/bin/" + game): self.gamedirsfound.append("/usr/bin/" + game) compliant = False self.detailedresults += "/usr/bin/" + game + "path exists\n" if (os.path.exists("/usr/games")): usrgames = os.listdir("/usr/games") if len(usrgames) > 0: compliant = False self.detailedresults += "/usr/games directory is not empty\n" 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): try: if not self.ci.getcurrvalue(): return success = True debug = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) for game in self.gamesfound: # pkgName = self.ph.getPackageFromFile(game) # if pkgName is not None and str(pkgName) is not "": if self.ph.remove(game): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "pkghelper", "pkgname": game, "startstate": "installed", "endstate": "removed" } self.statechglogger.recordchgevent(myid, event) else: success = False self.detailedresults += "Unable to remove " + game + "\n" for game in self.gamedirsfound: #did not put state change event recording here due to python #not sending valid return code back for success or failure #with os.remove command therefore not knowing if successful #to record event if os.path.exists(game): os.remove(game) if os.path.exists("/usr/games/"): if not self.__cleandir("/usr/games/"): success = False self.detailedresults += "Unable to remove all games from /usr/games\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 def __cleandir(self, directory, depth=0): '''Recursively finds the package name for each file in a directory, and all child directories, and uninstalls the package. Ignores links. This is best-effort; no error checking is done for the uninstall requests. @param directory: Name of the directory to search @return: False if a package name is not found for any file, True otherwise. @author: Eric Ball ''' success = True dirlist = os.listdir(directory) for path in dirlist: path = directory + path if os.path.isfile(path): pkgName = self.ph.getPackageFromFile(path) if pkgName is not None: if self.ph.remove(pkgName): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "pkghelper", "pkgname": pkgName, "startstate": "installed", "endstate": "removed" } self.statechglogger.recordchgevent(myid, event) else: debug = "Could not remove package " + pkgName self.logger.log(LogPriority.DEBUG, debug) success = False else: debug = "Could not find package name for " + path self.logger.log(LogPriority.DEBUG, debug) success = False elif os.path.isdir(path): if depth < 6: success &= self.__cleandir(path, depth + 1) dirlist = os.listdir(directory) if dirlist: # There is a possibility that removing some packages will result in # other packages being installed. We will attempt to remove these # additional packages, but limit this to avoid infinite loops. if depth < 6: success &= self.__cleandir(directory, depth + 1) return success
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 DisableCloudServices(RuleKVEditor): '''This method runs all the report methods for RuleKVEditors defined in the dictionary @author: ekkehard j. koch @change: 07/23/2014 added ubuntu methods and applicability; fixed typos in doc strings; added class doc string; implemented pkghelper and iterate methods - bemalmbe ''' ############################################################################### def __init__(self, config, environ, logdispatcher, statechglogger): RuleKVEditor.__init__(self, config, environ, logdispatcher, statechglogger) self.rulenumber = 159 self.rulename = 'DisableCloudServices' self.formatDetailedResults("initialize") self.mandatory = True self.rootrequired = True self.logger = self.logdispatch self.guidance = [] self.applicable = {'type': 'white', 'os': {'Mac OS X': ['10.15', 'r', '10.15.10'], 'Ubuntu': ['12.04', '+']}} self.ch = CommandHelper(self.logdispatch) # init CIs datatype = 'bool' key = 'DISABLECLOUDSERVICES' instructions = "To prevent cloud services from being disabled, " + \ "set the value of DisableCloudServices to False." default = True self.DisableCloudServices = self.initCi(datatype, key, instructions, default) if self.environ.getosfamily() == 'darwin': self.addKVEditor("iCloudSaveNewDocumentsToDisk", "defaults", "NSGlobalDomain", "", {"NSDocumentSaveNewDocumentsToCloud": ["0", "-bool no"]}, "present", "", "Save new documents to disk not to iCloud.", None, False, {"NSDocumentSaveNewDocumentsToCloud": ["1", "-bool yes"]}) else: self.debianpkglist = ['unity-webapps-common', 'unity-lens-shopping'] self.sethelptext() def report(self): '''choose which report method to run based on OS archetype''' self.detailedresults = "" if self.environ.getosfamily() == 'darwin': RuleKVEditor.report(self, False) elif re.search('Ubuntu', self.environ.getostype()): retval = self.reportUbuntu() return retval ############################################################################### def reportUbuntu(self): '''if debian, check for unity-lens-shopping and unity-webapps-common cloud service packages ''' # defaults self.compliant = True self.detailedresults = "" try: self.ph = Pkghelper(self.logdispatch, self.environ) for package in self.debianpkglist: if self.ph.check(package): self.compliant = False 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 fix(self): '''choose which fix method to run, based on OS archetype''' self.detailedresults = "" if self.DisableCloudServices.getcurrvalue(): if self.environ.getosfamily() == 'darwin': RuleKVEditor.fix(self, False) elif re.search('Ubuntu', self.environ.getostype()): self.fixUbuntu() ############################################################################### def fixUbuntu(self): '''if debian, disable unity-lens-shopping and unity-webapps-common cloud service packages, if they are installed ''' # defaults self.iditerator = 0 try: for package in self.debianpkglist: if self.ph.check(package): self.ph.remove(package) cmd = self.ph.getInstall() + package event = {'eventtype': 'commandstring', 'command': cmd} self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.rulesuccess = False self.detailedresults += "\n" + str(err) + 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 SecurePOPIMAP(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 = 141 self.rulename = 'SecurePOPIMAP' self.mandatory = True self.sethelptext() self.rootrequired = True self.applicable = {'type': 'black', 'family': ['darwin']} self.guidance = ['NSA(3.17)', 'cce-4384-4', '3887-7', '4530-2', '4547-6', '4552-6', '4371-1', '4410-7'] data1 = 'bool' key1 = 'DISABLEPOPIMAP' instructions1 = 'To prevent POP/IMAP services from being disabled entirely, set the value of DisablePOPIMAP to False.' default1 = True self.disableci = self.initCi(data1, key1, instructions1, default1) data2 = 'bool' key2 = 'SECUREPOPIMAP' instructions2 = 'To securely configure POP/IMAP services, set the value of SecurePOPIMAP to True.' default2 = False self.secureci = self.initCi(data2, key2, instructions2, default2) data3 = 'string' key3 = 'RequireProtocols' instructions3 = 'If this system will be operating as a mail server, fill in the required/used protocols below. Please use a space-delimited list. Valid entries are limited to: imap, imaps, pop3, pop3s' default3 = '' self.reqprotocols = self.initCi(data3, key3, instructions3, default3) self.localization() def localization(self): '''determine which type of OS we are running on and set up class variables accordingly :returns: void @author: Breen Malmberg ''' self.initobjs() self.setcommon() if self.pkgh.manager not in ['apt-get', 'yum', 'zypper', 'dnf']: self.logger.log(LogPriority.DEBUG, "Could not identify OS type or OS not supported!") if self.pkgh.manager == 'apt-get': self.setdebian() if self.pkgh.manager == 'yum': self.setredhat() if self.pkgh.manager == 'dnf': self.setredhat() if self.pkgh.manager == 'zypper': self.setsuse() pass def initobjs(self): '''initialize objects needed/used by other methods within this class :returns: void @author: Breen Malmberg ''' try: # if you add class variables here, be sure to also add # checks for them, in the checkinitobjs() method self.pkgh = Pkghelper(self.logger, self.environ) self.svch = ServiceHelper(self.environ, self.logger) self.cmdh = CommandHelper(self.logger) self.debian = False self.suse = False self.redhat = False self.pkgdict = {} self.confpathdict = {} self.servicename = '' self.osdetected = False except Exception: raise def setcommon(self): '''set variables which are common to all platforms :returns: void @author: Breen Malmberg ''' self.detailedresults = "" self.reqprots = "" self.protocollist = ['imap', 'imaps', 'pop3', 'pop3s'] def setdebian(self): '''set debian specific variables :returns: void @author: Breen Malmberg ''' self.debian = True self.osdetected = True self.pkgdict = {'dovecot-core': False, 'dovecot-pop3d': False, 'dovecot-imapd': False} # the following dictionary is of the format: # {configfilepath1: {partialmatch1: fullreplacement1, # partialmatch2: fullreplacement2}, # configfilepath2: {partialmatch1: fullreplacement1, # partialmatch2: fullreplacement2} # } self.confpathdict = {'/etc/dovecot/dovecot.conf': {'protocols =': 'protocols = ' + str(self.reqprots) + '\n', 'disable_plaintext_auth =': 'disable_plaintext_auth = yes\n', 'login_process_per_connection =': 'login_process_per_connection = yes\n', 'mail_drop_priv_before_exec =': 'mail_drop_priv_before_exec = yes\n', 'login_trusted_networks =': '#login_trusted_networks =\n'}} self.servicename = 'dovecot' def setredhat(self): '''set redhat sepcific variables :returns: void @author: Breen Malmberg ''' self.redhat = True self.osdetected = True self.pkgdict = {'dovecot': False} # the following dictionary is of the format: # {configfilepath1: {partialmatch1: fullreplacement1, # partialmatch2: fullreplacement2}, # configfilepath2: {partialmatch1: fullreplacement1, # partialmatch2: fullreplacement2} # } self.confpathdict = {'/etc/dovecot/dovecot.conf': {'protocols =': 'protocols = ' + str(self.reqprots) + '\n', 'disable_plaintext_auth =': 'disable_plaintext_auth = yes\n', 'login_process_per_connection =': 'login_process_per_connection = yes\n', 'mail_drop_priv_before_exec =': 'mail_drop_priv_before_exec = yes\n', 'login_trusted_networks =': '#login_trusted_networks =\n'}} self.servicename = 'dovecot' def setsuse(self): '''set suse specific variables :returns: void @author: Breen Malmberg ''' self.suse = True self.osdetected = True self.pkgdict = {'dovecot': False} # the following dictionary is of the format: # {configfilepath1: {partialmatch1: fullreplacement1, # partialmatch2: fullreplacement2}, # configfilepath2: {partialmatch1: fullreplacement1, # partialmatch2: fullreplacement2} # } self.confpathdict = {'/etc/dovecot/dovecot.conf': {'protocols =': 'protocols = ' + str(self.reqprots) + '\n', 'disable_plaintext_auth =': 'disable_plaintext_auth = yes\n', 'login_process_per_connection =': 'login_process_per_connection = yes\n', 'mail_drop_priv_before_exec =': 'mail_drop_priv_before_exec = yes\n', 'login_trusted_networks =': '#login_trusted_networks =\n'}} self.servicename = 'dovecot' def getFileContents(self, filepath): ''' :param filepath: ''' filecontents = [] try: if not isinstance(filepath, str): self.logger.log(LogPriority.DEBUG, "Parameter filepath must be of type: str") if not filepath: self.logger.log(LogPriority.DEBUG, "Parameter filepath must not be blank") if not os.path.exists(filepath): self.logger.log(LogPriority.DEBUG, "Specified filepath not found") else: f = open(filepath, 'r') filecontents = f.readlines() f.close() if not filecontents: self.logger.log(LogPriority.DEBUG, "Specified file had no contents") except Exception: raise return filecontents def searchContents(self, regex, contents): ''' :param regex: :param contents: ''' retval = False try: if not isinstance(regex, str): self.logger.log(LogPriority.DEBUG, "Parameter regex must be of type: str") retval = False return retval if not isinstance(contents, list): self.logger.log(LogPriority.DEBUG, "Parameter contents must be of type: list") retval = False return retval if not regex: self.logger.log(LogPriority.DEBUG, "regex must not be blank") retval = False return retval if not contents: self.logger.log(LogPriority.DEBUG, "contents must not be blank") retval = False return retval for line in contents: if re.search(regex, line): retval = True except Exception: raise return retval def fixContents(self, fixdict, filepath, contents): ''' :param fixdict: :param filepath: :param contents: ''' retval = True if not isinstance(fixdict, dict): self.logger.log(LogPriority.DEBUG, "Parameter fixdict must be of type: dict") retval = False return retval if not isinstance(filepath, str): self.logger.log(LogPriority.DEBUG, "Parameter filepath must be of type: str") retval = False return retval if not isinstance(contents, list): self.logger.log(LogPriority.DEBUG, "Parameter contents must be of type: list") retval = False return retval if not fixdict: self.logger.log(LogPriority.DEBUG, "Parameter fixdict must not be empty") retval = False return retval if not filepath: self.logger.log(LogPriority.DEBUG, "Parameter filepath must not be blank") retval = False return retval if not contents: self.logger.log(LogPriority.DEBUG, "Parameter contents must not be empty") retval = False return retval contentdict = {} for item in fixdict: contentdict[item] = False tempfilepath = filepath + '.stonixtmp' for line in contents: for partialmatch in fixdict: if re.search(partialmatch, line): contents = [c.replace(line, fixdict[partialmatch]) for c in contents] contentdict[partialmatch] = True for item in contentdict: if not contentdict[item]: contents.append('\n' + fixdict[item]) tf = open(tempfilepath, 'w') tf.writelines(contents) tf.close() event = {'eventtype': 'conf', 'filepath': filepath} myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(filepath, tempfilepath, myid) os.rename(tempfilepath, filepath) def report(self): '''return true if all check actions report compliant return false if one or more check actions reports not compliant :returns: self.compliant :rtype: bool @author: Breen Malmberg ''' self.compliant = True self.detailedresults = '' self.logger.log(LogPriority.DEBUG, "Entering SecurePOPIMAP.report()...") try: self.reqprots = self.reqprotocols.getcurrvalue() if self.suse: self.setsuse() if self.debian: self.setdebian() if self.redhat: self.setredhat() self.logger.log(LogPriority.DEBUG, "Checking init objects...") if not self.checkinitobjs(): self.logger.log(LogPriority.DEBUG, 'One or more class properties were not initialized or set correctly.') self.rulesuccess = False self.compliant = False self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant self.logger.log(LogPriority.DEBUG, "Finished checking init objects. all were OK") # disable stuff if self.disableci.getcurrvalue(): self.logger.log(LogPriority.DEBUG, "The disable POP/IMAP option is set. Checking if POP/IMAP is disabled...") self.logger.log(LogPriority.DEBUG, "Checking for running service...") if not self.checksvc(): self.compliant = False self.detailedresults += "\nRule is not compliant because: The " + str(self.servicename) + " service is either running or enabled." self.logger.log(LogPriority.DEBUG, "Service check finished.") self.logger.log(LogPriority.DEBUG, "Checking packages...") if not self.checkpkgs(False): self.compliant = False self.detailedresults += "\nRule is not compliant because one or more of the packages is still installed." self.logger.log(LogPriority.DEBUG, "Package check finished.") # secure stuff elif self.secureci.getcurrvalue(): self.logger.log(LogPriority.DEBUG, "The secure POP/IMAP option is set. Checking if POP/IMAP is secured...") if not self.reqprots: self.detailedresults += '\nRequired protocols were not specified. Cannot securely configure POP/IMAP without them.' self.compliant = False slist = self.reqprots.split() if slist: for prot in slist: if prot.strip() != '' and prot.strip() not in self.protocollist: self.detailedresults += "\nRule is not compliant because: The specified protocol: " + str(prot) + " is not a valid protocol." self.compliant = False else: self.detailedresults += '\nUnable to read protocol list. Please use a space-delimited list when specifying your required protocols.' self.logger.log(LogPriority.DEBUG, "The secure POP/IMAP option is set. Checking if POP/IMAP is secured...") if self.checkpkgs(True): self.logger.log(LogPriority.DEBUG, "Required packages are installed.") self.logger.log(LogPriority.DEBUG, " Checking file configuration...") if not self.checkconfig(): self.compliant = False self.logger.log(LogPriority.DEBUG, "File configuration check finished.") else: self.logger.log(LogPriority.DEBUG, "One or more required packages are not installed.") self.compliant = False self.detailedresults += "\nRule is not compliant because one or more of the required packages are not installed." except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.rulesuccess = False self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def checkinitobjs(self): '''validate each class property and object to be used in this class before it is used return True if each object is properly assigned/initialized return False if not :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True try: if not self.pkgh: retval = False self.logger.log(LogPriority.DEBUG, "The package helper object was not initialized correctly") if not self.svch: retval = False self.logger.log(LogPriority.DEBUG, "The service helper object was not initialized correctly") if not self.cmdh: retval = False self.logger.log(LogPriority.DEBUG, "The command helper object was not initialized correctly") if not self.osdetected: retval = False self.logger.log(LogPriority.DEBUG, "Unable to determine OS type or package manager") if not self.servicename: retval = False self.logger.log(LogPriority.DEBUG, "servicename variable was not set") if not self.pkgdict: retval = False self.logger.log(LogPriority.DEBUG, "pgkdict variable was not set") if not self.confpathdict: retval = False self.logger.log(LogPriority.DEBUG, "confpathdict variable was not set") except AttributeError: retval = False self.logger.log(LogPriority.DEBUG, "One or more of the class variables are undefined") return retval return retval def checksvc(self): '''check to see if the dovecot service is enabled or running return False if it is either running or enabled if the service is neither running nor enabled, return True :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True enabled = False running = False try: if self.svch.auditService(self.servicename, _="_"): enabled = True self.detailedresults += '\nThe ' + str(self.servicename) + ' service is still enabled' if enabled: self.detailedresults += "\nThere are service(s) which need to be disabled" if self.svch.isRunning(self.servicename, _="_"): running = False self.detailedresults += '\nThe ' + str(self.servicename) + ' service is still running' if running: self.detailedresults += "\nThere are service(s) which need to be stopped" if enabled | running: retval = False except Exception: raise return retval def checkpkgs(self, desired): '''check compliance of packages portion of rule if desired, check if all required packages are installed: True if yes, False if no if not desired, check if any packages are installed: True if no, False if yes :param desired: :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True try: if not desired: for pkg in self.pkgdict: if self.pkgh.check(pkg): self.pkgdict[pkg] = True retval = False else: for pkg in self.pkgdict: if not self.pkgh.check(pkg): self.pkgdict[pkg] = False retval = False except Exception: raise return retval def checkconfig(self): '''verify configuration state of file(s) :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True contents = [] try: for path in self.confpathdict: contents = self.getFileContents(path) if contents: for confitem in self.confpathdict[path]: if not self.searchContents(str(self.confpathdict[path][confitem]), contents): retval = False self.detailedresults += "\nRequired configuration option: " + str(self.confpathdict[path][confitem]) + " was not found in file: " + str(path) else: self.logger.log(LogPriority.DEBUG, "Unable to check contents of file: " + str(path)) if retval: self.detailedresults += "\nAll required configuration options have been found in file: " + str(path) if retval: self.detailedresults += "\nAll required configuration options have been found in all required configuration files" except Exception: raise return retval def fix(self): '''run each fix action and get the success results of each one return True if all fix actions succeeded return False if not :returns: fixsuccess :rtype: bool @author: Breen Malmberg ''' fixsuccess = True self.detailedresults = '' self.iditerator = 0 try: if self.disableci.getcurrvalue(): self.detailedresults += '\nYou have selected the DisablePOPIMAP option. It will now be disabled/removed from this system.' if not self.turnoffsvc(): fixsuccess = False if not self.removePackages(): fixsuccess = False elif self.secureci.getcurrvalue(): self.detailedresults += '\nYou have selected the SecurePOPIMAP option. It will now be installed (if it is not already installed) and then securely configured.' if not self.installPackages(): fixsuccess = False if not self.configurefiles(): fixsuccess = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += traceback.format_exc() self.rulesuccess = False self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", fixsuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return fixsuccess def turnoffsvc(self): '''disable dovecot service if it is enabled return True if dovecot service was successfully disabled or is not enabled return False if otherwise :returns: retval :rtype: bool @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "Attempting to disable service: " + str(self.servicename)) retval = True try: self.svch.disableService(self.servicename, _="_") if self.svch.auditService(self.servicename, _="_"): retval = False self.logger.log(LogPriority.DEBUG, "Service is still enabled after executing disableservice!") if self.svch.isRunning(self.servicename, _="_"): retval = False self.logger.log(LogPriority.DEBUG, "Service is still running after executing disableservice!") if retval: self.logger.log(LogPriority.DEBUG, "Successfully disabled service: " + str(self.servicename)) else: self.logger.log(LogPriority.DEBUG, "Failed to disable service: " + str(self.servicename)) except Exception: raise return retval def removePackages(self): '''Remove all packages in self.pkgdict, which are currently installed return True if all installed packages were successfully removed return False if not :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True try: for pkg in self.pkgdict: if self.pkgdict[pkg]: if not self.pkgh.remove(pkg): retval = False self.detailedresults += '\nFailed to remove package: ' + str(pkg) else: self.pkgdict[pkg] = False if not retval: self.logger.log(LogPriority.DEBUG, "Failed to remove packages") else: self.logger.log(LogPriority.DEBUG, "Successfully removed all packages") except Exception: raise return retval def configurefiles(self): '''set the correct configuration options within the dovecot configuration file(s) :returns: void @author: Breen Malmberg ''' try: for path in self.confpathdict: contents = self.getFileContents(path) self.fixContents(self.confpathdict[path], path, contents) except Exception: raise def installPackages(self): '''install all necessary dovecot packages :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True self.logger.log(LogPriority.DEBUG, "Attempting to install all necessary packages...") try: for pkg in self.pkgdict: if not self.pkgdict[pkg]: if not self.pkgh.install(pkg): retval = False self.detailedresults += '\nFailed to install package ' + str(pkg) self.logger.log(LogPriority.DEBUG, "Failed to install package: " + str(pkg)) if not retval: self.logger.log(LogPriority.DEBUG, "Failed to install one or more required packages. Please check your connection to the network and ensure that your package repositories are correctly configured.") else: self.logger.log(LogPriority.DEBUG, "Successfully installed all required packages") except Exception: raise return retval