class InstallBanners(RuleKVEditor): '''Install and configure warning banners, to be displayed at startup.''' def __init__(self, config, environ, logger, statechglogger): """ Constructor """ RuleKVEditor.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 51 self.rulename = 'InstallBanners' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.compliant = False self.guidance = [ 'CIS', 'NSA 2.3.7.2', 'CCE 4188-9', 'CCE 4431-3', 'CCE 3717-6', 'CCE 4554-2', 'CCE 4603-7', 'CCE 4760-5', 'CCE 4301-8', 'CCE 4698-7', 'CCE 4222-6', 'CCE 4103-8', 'CCE 4870-2', 'CCE 4896-7' ] self.iditerator = 0 self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # init CIs datatype = 'bool' key = 'INSTALLBANNERS' instructions = "To prevent the installation of warning banners, " + \ "set the value of InstallBanners to False.\n\n!DEBIAN USERS! Due to " + \ "a bug in gdm3 (gnome 3) which has not been patched on debian, we are forced to " + \ "change the user's display manager to something else in order to be compliant " + \ "with the login banner requirement. As a result, the login banner changes will " + \ "only take effect after a system reboot." default = True self.ci = self.initCi(datatype, key, instructions, default) # Initial setup and deterministic resolution of variables constlist = [ WARNINGBANNER, GDMWARNINGBANNER, GDM3WARNINGBANNER, ALTWARNINGBANNER, OSXSHORTWARNINGBANNER ] if not self.checkConsts(constlist): self.applicable = {'type': 'white', 'family': []} return self.initobjs() self.setcommon() self.islinux() self.ismac() def initobjs(self): ''' ''' try: self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.linux = False self.mac = False self.gnome2 = False self.gnome3 = False self.kde = False self.lightdm = False self.badline = False except Exception: raise def setgnome2(self): '''set up all variables for use with gnome2-based systems @author: Breen Malmberg ''' self.gnome2 = True self.gnome2bannertext = GDMWARNINGBANNER def forcelightdm(self): '''force debian systems using gdm3 to change to the lightdm display manager in order to comply with login/display banner requirements :returns: retval :rtype: bool @author: Breen Malmberg ''' retval = True self.logger.log(LogPriority.DEBUG, "Forcing display manager to be lightdm...") if self.gnome2: cmd = '/usr/sbin/dpkg-reconfigure --frontend=noninteractive gdm' elif self.gnome3: cmd = '/usr/sbin/dpkg-reconfigure --frontend=noninteractive gdm3' else: cmd = '/usr/sbin/dpkg-reconfigure --frontend=noninteractive lightdm' cmd2 = "/usr/sbin/dpkg --configure lightdm" self.lightdm = True path = '/etc/X11/default-display-manager' opt = '/usr/sbin/lightdm\n' mode = 0o644 uid = 0 gid = 0 try: if not self.setFileContents(path, opt, [uid, gid, mode]): retval = False if not self.ph.check('lightdm'): self.logger.log( LogPriority.DEBUG, "Package: lightdm not installed yet. Installing lightdm..." ) if not self.ph.install('lightdm'): retval = False self.detailedresults += "\nFailed to install lightdm" if not self.ch.executeCommand(cmd): retval = False self.detailedresults += "\nFailed to set lightdm as default display manager" self.ch.executeCommand(cmd2) if retval: self.detailedresults += "\nThe display manager has been changed to lightdm. These changes will only take effect once the system has been restarted." else: self.detailedresults += "\nFailed to change default display manager to lightdm. Login banner may not display." except Exception: raise return retval def setgnome3(self): '''set up all variables for use with gnome3-based systems @author: Breen Malmberg ''' self.gnome3 = True self.gnome3bannertext = GDM3WARNINGBANNER def setkde(self): '''set up all variables for use with kde-based systems @author: Breen Malmberg ''' self.kde = True self.kdebannertext = ALTWARNINGBANNER self.kdelocs = [ '/etc/kde/kdm/kdmrc', '/etc/kde3/kdm/kdmrc', '/etc/kde4/kdm/kdmrc', '/usr/share/config/kdm/kdmrc', '/usr/share/kde4/config/kdm/kdmrc' ] self.kdefile = '/usr/share/kde4/config/kdm/kdmrc' for loc in self.kdelocs: if os.path.exists(loc): self.kdefile = str(loc) tmpfile = self.kdefile + '.stonixtmp' key1 = 'GreetString' val1 = '"' + str(self.kdebannertext) + '"' self.greetbanner = [key1, val1] key2 = 'UserList' val2 = 'false' key3 = 'UseTheme' val3 = 'false' key4 = 'PreselectUser' val4 = 'None' key5 = 'LogoArea' val5 = 'None' key6 = 'GreeterPos' val6 = '45,45' key7 = 'AntiAliasing' val7 = 'false' key8 = 'SortUsers' val8 = 'false' key9 = 'GreetFont' val9 = 'Serif,20,-1,5,50,0,0,0,0,0' key10 = 'FailFont' val10 = 'Sans Serif,10,-1,5,75,0,0,0,0,0' bkey1 = 'PreselectUser' bval1 = 'None' self.kdedict = { "X-*-Greeter": { key2: val2, key3: val3, key4: val4, key5: val5, key6: val6, key7: val7, key8: val8, key9: val9, key10: val10 }, "X-:*-Greeter": { bkey1: bval1 } } self.kdeditor = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.kdefile, tmpfile, self.kdedict, "present", "closedeq") def setlightdm(self): '''set up all variables for use with lightdm-based systems @author: Breen Malmberg ''' self.lightdm = True self.ldmbannertext = ALTWARNINGBANNER key1 = '/usr/share/lightdm/lightdm.conf.d/50-ubuntu.conf' val1 = [ '[SeatDefaults]', 'allow-guest=false', 'greeter-hide-users=true', 'greeter-show-manual-login=true', 'autologin-user='******'/etc/lightdm/lightdm.conf.d/stonixlightdm.conf' val3 = [ '[SeatDefaults]', 'greeter-setup-script=/bin/sh -c "until /usr/bin/zenity ' + '--width=600 --height=400 --title=WARNING --text-info ' + '--filename=' + str(self.loginbannerfile) + '; do :; done"' ] self.lightdmdict = {key1: val1, key2: val2, key3: val3} def setlinuxcommon(self): '''set up all variables for use with linux-based systems @author: Breen Malmberg ''' if not self.sshdfile: self.sshdfile = '/etc/ssh/sshd_config' self.bannerfiles = [ "/etc/banners/in.ftpd", "/etc/banners/in.rlogind", "/etc/banners/in.rshd", "/etc/banners/in.telnetd", "/etc/banner" ] def setcommon(self): '''set up all variables for use with all systems @author: Breen Malmberg ''' self.loginbannerfile = "/etc/issue" self.sshbannerfile = "/etc/banner" self.sshdfile = "" self.sshdlocs = [ "/etc/sshd_config", "/etc/ssh/sshd_config", "/private/etc/ssh/sshd_config", "/private/etc/sshd_config" ] for loc in self.sshdlocs: if os.path.exists(loc): self.sshdfile = str(loc) self.sshbannerdict = {"Banner": self.sshbannerfile} def setmac(self): '''set up all variables for use with darwin-based systems @author: Breen Malmberg ''' self.mac = True self.bannertext = WARNINGBANNER if not self.sshdfile: self.sshdfile = '/private/etc/sshd_config' self.ftpwelcomelocs = ["/etc/ftpwelcome", "/private/etc/ftpwelcome"] self.ftpwelcomefile = '/private/etc/ftpwelcome' for loc in self.ftpwelcomelocs: if os.path.exists(loc): self.ftpwelcomefile = str(loc) self.policybanner = "/Library/Security/PolicyBanner.txt" self.addKVEditor( "FileServerBannerText", "defaults", "/Library/Preferences/com.apple.AppleFileServer", "", { "loginGreeting": [re.escape(WARNINGBANNER), "'\"" + WARNINGBANNER + "\"'"] }, "present", "", "To prevent the installation of a warning banner," + " set the value of InstallBanners to False", self.ci) self.addKVEditor( "loginwindowBannerText", "defaults", "/Library/Preferences/com.apple.loginwindow", "", { "LoginwindowText": [ re.escape(OSXSHORTWARNINGBANNER), '"' + OSXSHORTWARNINGBANNER + '"' ] }, "present", "", "To prevent the installation of a warning banner," + " set the value of InstallBanners to False", self.ci) def ismac(self): '''determine whether the current system is macintosh, or darwin-based @author: Breen Malmberg ''' try: if self.environ.getosfamily() == 'darwin': self.setmac() self.logger.log( LogPriority.DEBUG, "System is Mac OS. Configuring Mac banners...") except Exception: raise def islinux(self): '''determine whether the current system is linux-based, and set all distro-specific variables @author: Breen Malmberg ''' try: if self.environ.getosfamily() == 'linux': self.linux = True self.setlinuxcommon() if os.path.exists("/usr/sbin/gdm3"): self.setgnome3() elif os.path.exists("/usr/sbin/gdm"): self.ch.executeCommand("/usr/sbin/gdm --version") gnomeversionstring = self.ch.getOutputString() if re.search("GDM\s+3\.", gnomeversionstring, re.IGNORECASE): self.setgnome3() else: self.setgnome2() if os.path.exists("/usr/sbin/lightdm"): self.setlightdm() if os.path.exists("/usr/bin/startkde"): self.setkde() except Exception: raise def getFileContents(self, filepath, returntype='list'): '''Retrieve and return file contents (in list format) of a given file path :param filepath: string full path to file to read :param returntype: string valid values: 'list', 'string' (Default value = 'list') :returns: filecontents :rtype: list @author: Breen Malmberg ''' self.logger.log(LogPriority.DEBUG, "Retrieving contents of file " + filepath) try: if returntype == 'list': filecontents = [] elif returntype == 'string': filecontents = '' else: filecontents = '' self.detailedresults += "\nReturntype parameter must be " + \ "either 'list' or 'string!'" if os.path.exists(filepath): f = open(filepath, 'r') if returntype == 'list': filecontents = f.readlines() elif returntype == 'string': filecontents = f.read() f.close() else: self.detailedresults += '\nCould not find specified file: ' + \ str(filepath) + '. Returning empty value...' except Exception: raise return filecontents def setFileContents(self, filepath, contents, perms=[0, 0, 0o644]): '''write (or append) specified contents to specified file :param filepath: string full path to file :param contents: list/string object to write to file (can be either list or string) :param mode: string indicates the IO method to use to open the file. Valid values are 'w' for write, and 'a' for append :param perms: integer-list of size 3. first index/position in list indicates octal permissions flag; second indicates uid; third indicates gid (Default value = [0) :param 0: :param 0644]: :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True tmpPath = filepath + ".stonixtmp" self.logger.log(LogPriority.DEBUG, "Writing changes to file " + filepath) try: if os.path.exists(filepath): f = open(tmpPath, "w") if isinstance(contents, list): f.writelines(contents) else: f.write(contents) f.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': filepath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(filepath, tmpPath, myid) os.rename(tmpPath, filepath) os.chmod(filepath, perms[2]) os.chown(filepath, perms[0], perms[1]) else: # get the parent directory of filepath basepath = ("/").join(filepath.split("/")[:-1]) # create parent directory if it doesn't exist if not os.path.exists(basepath): os.makedirs(basepath, 0o755) # then create the file f = open(filepath, "w") if isinstance(contents, list): f.writelines(contents) else: f.write(contents) f.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'creation', 'filepath': filepath} self.statechglogger.recordchgevent(myid, event) os.chmod(filepath, perms[2]) os.chown(filepath, perms[0], perms[1]) if not fixInflation(filepath, self.logger, perms[2], [perms[0], perms[1]]): retval = False except Exception: retval = False raise return retval def replaceFileContents(self, filepath, contentdict): '''replace key from contentdict with value from contentdict, in filepath :param filepath: string full path to file :param contentdict: dictionary of strings :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True appends = [] self.logger.log(LogPriority.DEBUG, "Replacing any existing, incorrect configurations...") try: contentlines = self.getFileContents(filepath) for line in contentlines: for key in contentdict: if any((re.search("\*", key), re.search("\*", line))): if re.search("^" + re.escape(key), re.escape(line)): contentlines = [ c.replace(line, key + contentdict[key]) for c in contentlines ] else: if re.search("^" + key, line): contentlines = [ c.replace(line, key + contentdict[key]) for c in contentlines ] for key in contentdict: if key.strip() not in contentlines: appends.append(key + contentdict[key]) self.appendFileContents(filepath, contentlines, appends) except Exception: self.rulesuccess = False raise return retval def appendFileContents(self, filepath, contents, appends): ''' :param filepath: :param contents: :param appends: ''' self.logger.log(LogPriority.DEBUG, "Appending missing configuration lines...") for a in appends: contents.append(a + "\n") self.setFileContents(filepath, contents) def reportFileContents(self, filepath, searchparams): '''verify that the give key:value pairs in contentdict exist in filepath :param filepath: string full path to file :param searchparams: list or string to search in file contents :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True foundDict = {} findresult = 999 foundstring = False try: if not os.path.exists(filepath): retval = False self.detailedresults += "\nRequired configuration file " + filepath + " does not exist" return retval if isinstance(searchparams, str): contents = self.getFileContents(filepath, "string") findresult = contents.find(searchparams) if findresult != -1: foundstring = True if not foundstring: retval = False self.logger.log( LogPriority.DEBUG, "str.find() return code = " + str(findresult)) self.detailedresults += "\nSearch parameter:\n" + searchparams + "\nis missing from " + filepath elif isinstance(searchparams, list): for p in searchparams: foundDict[p] = False contentlines = self.getFileContents(filepath) for line in contentlines: for p in searchparams: if line.find(p) != -1: foundDict[p] = True for i in foundDict: if not foundDict[i]: retval = False self.detailedresults += "\nSearch parameter:\n" + i + "\nis missing from " + filepath except Exception: raise return retval def checkCommand(self, cmd, val="", regex=True): '''check the output of a given command to see if it matches given value :param cmd: :param val: (Default value = "") :param regex: (Default value = True) :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True try: self.ch.executeCommand(cmd) retcode = self.ch.getReturnCode() # gconftool-2 returns either 0 or -1 on error if re.search("gconftool-2", cmd): if retcode == -1: retval = False errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) self.detailedresults += "\nCommand:\n" + cmd + "\nFailed" else: # all other programs return 0 on success if retcode != 0: retval = False errstr = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errstr) if val: outputstr = self.ch.getOutputString() if not regex: # outputstr = outputstr.strip() # val = val.strip() if outputstr.find(val) == -1: retval = False self.detailedresults += "\nDesired output:\n" self.detailedresults += val self.detailedresults += "\n\nActual output:\n" self.detailedresults += outputstr else: if not re.search(val, outputstr, re.IGNORECASE): retval = False self.logger.log( LogPriority.DEBUG, "Could not find search regex:\n" + val + "\nin command output") except Exception: raise return retval def report(self): '''The report method examines the current configuration and determines whether or not it is correct. If the config is correct then the self.compliant, self.detailed results and self.currstate properties are updated to reflect the system status. self.rulesuccess will be updated if the rule does not succeed. :returns: self.compliant :rtype: boolean @author Breen Malmberg ''' self.compliant = True self.detailedresults = "" try: if self.linux: if not self.reportlinux(): self.compliant = False elif self.mac: if not self.reportmac(): self.compliant = False else: self.compliant = False self.detailedresults += '\nCould not identify operating system, or operating system not supported.' except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def getgnome3version(self): '''get the gnome3 version as a float :returns: g3ver :rtype: float @author: Breen Malmberg ''' g3ver = 0.0 cmd = '/usr/sbin/gdm3 --version' backupcmd = '/usr/bin/gnome-session --version' if not any((self.checkCommand(cmd), self.checkCommand(backupcmd))): return g3ver else: outlines = self.ch.getOutput() for line in outlines: sline = line.split() if len(sline) > 1: splitver = sline[1].split('.') combinedver = splitver[0] + '.' + splitver[1] g3ver = float(combinedver) else: try: g3ver = float(sline[0]) except Exception: return g3ver return g3ver def reportlinux(self): '''run report functionality for linux-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: if all((self.gnome3, re.search('debian', self.environ.getostype(), re.IGNORECASE))): if self.getgnome3version() < 3.15: self.gnome3 = False self.forcelightdm() self.setlightdm() if not self.reportlinuxcommon(): compliant = False if self.gnome2: if not self.reportgnome2(): compliant = False elif self.gnome3: if not self.reportgnome3(): compliant = False if self.lightdm: if not self.reportlightdm(): compliant = False if self.kde: if not self.reportkde(): compliant = False except Exception: raise return compliant def reportlinuxcommon(self): '''run report functionality which is common to linux platforms :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: for f in self.bannerfiles: if not self.reportFileContents(f, WARNINGBANNER): compliant = False if not self.reportcommon(): compliant = False except Exception: raise return compliant def reportcommon(self): '''run report functionality which is common to all platforms :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True if not os.path.exists(self.sshdfile): self.detailedresults += "Required configuration file " + self.ssdhfile + \ " does not exist.\n" return False contents = readFile(self.sshdfile, self.logger) self.commoneditor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sshdfile, self.sshdfile + ".tmp", self.sshbannerdict, "present", "space") if not self.commoneditor.report(): compliant = False self.detailedresults += self.sshdfile + " doesn't contain correct contents\n" return compliant def reportgnome2(self): '''run report functionality for gnome2-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: gconf = "/usr/bin/gconftool-2" confDict = { "/apps/gdm/simple-greeter/disable_user_list": "true", "/apps/gdm/simple-greeter/banner_message_enable": "true", "/apps/gdm/simple-greeter/banner_message_text": ALTWARNINGBANNER } # gdm 2 db cannot reliably store/preserve/interpret newline characters gconfcommands = [] for item in confDict: gconfcommands.append(gconf + " -g " + item) for gcmd in gconfcommands: if not self.checkCommand(gcmd, confDict[gcmd.split()[2]], False): compliant = False except Exception: raise return compliant def reportgnome3(self): '''run report functionality for gnome3-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: if not self.checkprofilecfg(): compliant = False if not self.checkkeyfilecfg(): compliant = False except Exception: raise return compliant def checkprofilecfg(self): ''' ''' compliant = True profilefile = "/etc/dconf/profile/gdm" profileoptslist = ["user-db:user", "system-db:gdm"] if not self.reportFileContents(profilefile, profileoptslist): compliant = False return compliant def checkkeyfilecfg(self): ''' ''' compliant = True keyfile = "/etc/dconf/db/gdm.d/01-banner-message" keyfileoptslist = [ "[org/gnome/login-screen]", "banner-message-enable=true", "banner-message-text='" + GDM3WARNINGBANNER + "'", "disable-user-list=true" ] if not self.reportFileContents(keyfile, keyfileoptslist): compliant = False return compliant def reportlightdm(self): '''run report functionality for lightdm-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' compliant = True try: for item in self.lightdmdict: if not self.reportFileContents(item, self.lightdmdict[item]): compliant = False self.detailedresults += '\nRequired configuration text not found in: ' + str( item) except Exception: raise return compliant def reportkde(self): '''run report functionality for kde-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True try: if not os.path.exists(self.kdefile): retval = False self.detailedresults += 'Required configuration file: ' + str( self.kdefile) + ' not found' else: if not self.kdeditor.report(): retval = False if self.kdeditor.fixables: self.detailedresults += '\nThe following required options are missing from ' + \ str(self.kdefile) + ':\n' + '\n'.join(str(f) for f in self.kdeditor.fixables) \ + '\n' except Exception: raise return retval def reportmac(self): '''run report functionality for macintosh, or darwin-based systems :returns: retval :rtype: boolean @author: Breen Malmberg ''' retval = True try: if not RuleKVEditor.report(self, True): self.detailedresults += '\nEither file server banner text ' + \ 'or login window banner text is incorrect, or both' retval = False if os.path.exists(self.ftpwelcomefile): if not self.reportFileContents(self.ftpwelcomefile, self.bannertext): retval = False self.detailedresults += '\nIncorrect configuration ' + \ 'text in: ' + str(self.ftpwelcomefile) else: retval = False self.detailedresults += '\nRequired configuration file: ' + \ str(self.ftpwelcomefile) + ' not found' if os.path.exists(self.policybanner): if not self.reportFileContents(self.policybanner, self.bannertext): retval = False self.detailedresults += '\nIncorrect configuration ' + \ 'text in: ' + str(self.policybanner) else: retval = False self.detailedresults += '\nRequired configuration file: ' + \ str(self.policybanner) + ' not found' if not self.reportcommon(): retval = False except Exception: raise return retval def fix(self): '''Install warning banners, set the warning text and configure the file permissions for the warning banner files. :returns: success :rtype: boolean @author Breen Malmberg ''' self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) try: if self.ci.getcurrvalue(): if not self.fixcommon(): self.rulesuccess = False if self.linux: if not self.fixlinux(): self.rulesuccess = False elif self.mac: if not self.fixmac(): self.rulesuccess = False else: self.rulesuccess = False self.detailedresults += '\nCould not identify ' + \ 'operating system, or operating system not supported.' else: self.detailedresults += '\nThe configuration item for ' + \ 'this rule was not enabled so nothing was done.' except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixlinux(self): '''run fix functionality for linux-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: if not self.fixlinuxcommon(): success = False if self.gnome2: if not self.fixgnome2(): success = False elif self.gnome3: if not self.fixgnome3(): success = False if self.lightdm: if not self.fixlightdm(): success = False if self.kde: if not self.fixkde(): success = False except Exception: raise return success def fixlinuxcommon(self): '''run fix functionality which is common to linux platforms :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: for f in self.bannerfiles: if not self.setFileContents(f, WARNINGBANNER): success = False except Exception: raise return success def fixcommon(self): '''run fix functionlity which is common to all platforms :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True if self.commoneditor.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.commoneditor.setEventID(myid) if not self.commoneditor.fix(): debug = self.sshdfile + " editor fix is returning false\n" self.logger.log(LogPriority.DEBUG, debug) success = False elif not self.commoneditor.commit(): debug = self.sshdfile + " editor commit is returning false\n" self.logger.log(LogPriority.DEBUG, debug) success = False if not success: self.detailedresults += "Unable to correct contents of " + \ self.sshdfile + "\n" return success def fixgnome2(self): '''run fix functionality for gnome2-based systems (implementation based on: https://access.redhat.com/solutions/32480) :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True gconf = "/usr/bin/gconftool-2" confDict = { "/apps/gdm/simple-greeter/disable_user_list": "boolean true", "/apps/gdm/simple-greeter/banner_message_enable": "boolean true", "/apps/gdm/simple-greeter/banner_message_text": 'string "$(cat /etc/issue)"' } gconfcommands = [] for item in confDict: gconfcommands.append( gconf + " -s " + item + " --config-source=xml:readwrite:/etc/gconf/gconf.xml.system --direct --type " + confDict[item]) try: if not self.setFileContents("/etc/issue", GDMWARNINGBANNER): success = False self.detailedresults += "\nFailed to properly configure the banner text in file /etc/issue" # write configuration options to gnome system-wide database for gcmd in gconfcommands: if not self.checkCommand(gcmd): success = False except Exception: raise return success def fixgnome3(self): '''run fix functionality for gnome3-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: if not self.setFileContents("/etc/issue", GDM3WARNINGBANNER): success = False self.detailedresults += "\nFailed to properly configure the banner text in file /etc/issue" self.logger.log(LogPriority.DEBUG, "Applying gdm profile configurations...") # configure the gdm profile greeterdefaults = "" gdefaultslocs = [ "/usr/share/gdm/greeter-dconf-defaults", "/usr/share/gdm/greeter.dconf-defaults" ] for loc in gdefaultslocs: if os.path.exists(loc): greeterdefaults = loc profilebasedir = "/etc/dconf/profile" profilepath = profilebasedir + "/gdm" profileconflist = [ "user-db:user\n", "system-db:gdm\n", "file-db:" + greeterdefaults + "\n" ] if not os.path.exists(profilebasedir): self.logger.log( LogPriority.DEBUG, "gdm profile base directory does not exist. Creating it..." ) os.makedirs(profilebasedir, 0o755) if not self.setFileContents(profilepath, profileconflist): success = False self.logger.log(LogPriority.DEBUG, "Applying gdm keyfile configurations...") # configure the gdm keyfile gdmkeyfilebase = "/etc/dconf/db/gdm.d" gdmkeyfile = gdmkeyfilebase + "/01-banner-message" gdmkeyfileconflist = [ "[org/gnome/login-screen]\n", "banner-message-enable=true\n", "banner-message-text='" + GDM3WARNINGBANNER + "'\n", "disable-user-list=true\n" ] if not os.path.exists(gdmkeyfilebase): self.logger.log( LogPriority.DEBUG, "gdm keyfile base directory does not exist. Creating it..." ) os.makedirs(gdmkeyfilebase, 0o755) if not self.setFileContents(gdmkeyfile, gdmkeyfileconflist): success = False self.logger.log( LogPriority.DEBUG, "Updating dconf with new configuration settings...") # update dconf dconfupdate = "/usr/bin/dconf update" if not self.checkCommand(dconfupdate): success = False if success: self.detailedresults += "\nSuccessfully applied new banner configuration settings. On some systems, a reboot may be required to view updated settings" except Exception: raise return success def fixlightdm(self): '''run fix functionality for lightdm-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True contentlines = [] try: if not os.path.exists('/etc/lightdm/lightdm.conf.d'): os.makedirs('/etc/lightdm/lightdm.conf.d/', 0o755) for f in self.lightdmdict: contentlines = [] if isinstance(self.lightdmdict[f], list): for opt in self.lightdmdict[f]: contentlines.append(opt + '\n') if not self.setFileContents(f, contentlines): success = False else: if not self.setFileContents(f, self.lightdmdict[f]): success = False except Exception: raise return success def fixkde(self): '''run fix functionality for kde-based systems :returns: success :rtype: boolean @author: Breen Malmberg @change: Breen Malmberg - 5/25/2016 - moved the commit calls to ensure they are only called if the fix calls completed successfully ''' success = True try: if not self.kdefile: self.logger.log( LogPriority.DEBUG, "Unable to identify kde configuration file. " + "Can not continue with fix") self.detailedresults += "Unable to identify kde " + \ "configuration file. Can not continue with fix." success = False return success fileCreated = False if not os.path.exists(self.kdefile): if createFile(self.kdefile, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.kdefile} self.statechglogger.recordchgevent(myid, event) fileCreated = True debug = "kdmrc file successfully created at " + \ self.kdefile self.logger.log(LogPriority.DEBUG, debug) setPerms(self.kdefile, [0, 0, 0o644], self.logger) self.reportkde() else: self.detailedresults += "\nCould not create kdmrc file " + \ "at " + self.kdefile return False if not self.kdeditor.fix(): success = False self.detailedresults += "KVEditor fix failed. Fix not applied" else: if not fileCreated: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.kdeditor.setEventID(myid) if not self.kdeditor.commit(): success = False self.detailedresults += 'kdeditor commit failed. ' + \ 'Fix not applied' key1 = 'GreetString' val1 = '"' + self.kdebannertext + '"' data = {"X-*-Greeter": {key1: val1}} tmpfile = self.kdefile + ".stonixtmp2" greeteditor = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.kdefile, tmpfile, data, "present", "closedeq") if not greeteditor.report(): if greeteditor.fix(): if not greeteditor.commit(): if not fileCreated: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.kdeditor.setEventID(myid) success = False self.detailedresults += "\nGreetString commit failed" else: success = False self.detailedresults += "\nGreetString fix failed" except Exception: raise return success def fixmac(self): '''run fix functionality for macintosh, or darwin-based systems :returns: success :rtype: boolean @author: Breen Malmberg ''' success = True try: if not RuleKVEditor.fix(self, True): success = False if not self.setFileContents(self.ftpwelcomefile, self.bannertext): success = False self.detailedresults += '\nUnable to set warning banner text in ' + str( self.ftpwelcomefile) if not self.setFileContents(self.policybanner, self.bannertext): success = False self.detailedresults += '\nUnable to set warning banner text in ' + str( self.policybanner) except Exception: raise return success
class DisableCamera(Rule): def __init__(self, config, environ, logger, statechglogger): ''' :param config: :param environ: :param logger: :param statechglogger: ''' Rule.__init__(self, config, environ, logger, statechglogger) self.rulenumber = 150 self.rulename = "DisableCamera" self.formatDetailedResults("initialize") self.mandatory = False self.rulesuccess = True self.sethelptext() self.rootrequired = True self.guidance = ["CIS 1.2.6"] self.applicable = {'type': 'white', 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.logger = logger self.iditerator = 0 datatype = 'bool' key = 'DISABLECAMERA' instructions = "To disable the built-in iSight camera, set the value of DISABLECAMERA to True." default = False self.ci = self.initCi(datatype, key, instructions, default) self.setvars() def setvars(self): self.camprofile = "" baseconfigpath = "/Applications/stonix4mac.app/Contents/" + \ "Resources/stonix.app/Contents/MacOS/" + \ "stonix_resources/files/" self.camprofile = baseconfigpath + "stonix4macCameraDisablement.mobileconfig" # basetestpath = "/Users/username/stonix/src/stonix_resources/files/" # self.camprofile = basetestpath + "stonix4macCameraDisablement.mobileconfig" if not os.path.exists(self.camprofile): self.logger.log(LogPriority.DEBUG, "Could not locate appropriate camera disablement profile\n") self.camprofile = "" def report(self): '''check for the existence of the AppleCameraInterface driver in the output of kexstat. Report non-compliant if found. Report compliant if not found. :returns: self.compliant :rtype: bool @author: Breen Malmberg @change: dwalker - ??? - ??? @change: Breen Malmberg - 1/19/2017 - minor doc string edit; minor refactor @change: dwalker 10/3/2017 updated to check for a profile value ''' try: self.detailedresults = "" self.compliant = True if not self.camprofile: self.detailedresults += "Could not locate the appropriate camera disablement profile for your system.\n" self.compliant = False self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant if os.path.exists(self.camprofile): cameradict = {"com.apple.applicationaccess": {"allowCamera": {"val": "0", "type": "bool", "accept": "", "result": False}}} self.cameditor = KVEditorStonix(self.statechglogger, self.logger, "profiles", self.camprofile, "", cameradict, "", "") if not self.cameditor.report(): self.detailedresults += "iSight camera is not disabled\n" self.compliant = False else: self.detailedresults += self.camprofile + " doesn't exist\n" self.compliant = False self.detailedresults += "Due to a Mac OS issue, having multiple camera profiles " + \ "installed with conflicting values may allow any camera, " + \ "internal or external to still function. If a rule is coming " + \ "back compliant but your camera is still working, check your " + \ "installed profiles and remove any other restriction based profiles.\n" 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): '''run kextunload on the AppleCameraInterface driver to unload it and disable the iSight camera. return True if the command succeeds. return False if the command fails. :returns: success :rtype: bool @author: Breen Malmberg @change: dwalker - ??? - ??? @change: Breen Malmberg - 1/19/2017 - minor doc string edit; minor refactor @change: dwalker 10/3/2017 updated to check for a profile value ''' try: success = True self.detailedresults = "" # only run the fix actions if the CI has been enabled if not self.ci.getcurrvalue(): self.detailedresults += "Configuration item was not enabled\n" self.rulesuccess = False self.formatDetailedResults("report", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not self.cameditor.report(): if self.cameditor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.cameditor.setEventID(myid) if not self.cameditor.commit(): success = False self.detailedresults += "Unable to install profile\n" self.logdispatch.log(LogPriority.DEBUG, "Kveditor commit failed") else: success = False self.detailedresults += "Unable to install profile\n" self.logdispatch.log(LogPriority.DEBUG, "Kveditor fix failed") else: self.detailedresults += "Camera disablement profile was already installed.\n" self.rulesuccess = success except (KeyboardInterrupt, SystemExit): raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + str(traceback.format_exc()) self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
def fix_sysctl(self): '''set the systemd configuration setting fs.suid_dumpable to 0 :returns: success :rtype: bool @author: ??? ''' success = True # manually writing key and value to /etc/sysctl.conf sysctl = "/etc/sysctl.conf" created = False if not os.path.exists(sysctl): if createFile(sysctl, self.logger): created = True setPerms(sysctl, [0, 0, 0o644], self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sysctl} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to create " + sysctl + "\n" self.logger.log(LogPriority.DEBUG, debug) if os.path.exists(sysctl): if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sysctl, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False tmpfile = sysctl + ".tmp" editor = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, {"fs.suid_dumpable": "0"}, "present", "openeq") if not editor.report(): if editor.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) editor.setEventID(myid) if not editor.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) elif not editor.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not setPerms(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False resetsecon(sysctl) # using sysctl -w command self.logger.log(LogPriority.DEBUG, "Configuring /etc/sysctl fs.suid_dumpable directive") self.ch.executeCommand("/sbin/sysctl -w fs.suid_dumpable=0") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set core dumps variable suid_dumpable to 0\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.logger.log(LogPriority.DEBUG, "Re-reading sysctl configuration from files") self.ch.executeCommand("/sbin/sysctl -q -e -p") retcode2 = self.ch.getReturnCode() if retcode2 != 0: success = False self.detailedresults += "Failed to load new sysctl configuration from config file\n" errmsg2 = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg2) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -w fs.suid_dumpable=1" event = {"eventtype": "commandstring", "command": command} self.statechglogger.recordchgevent(myid, event) return success
def 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
class BootSecurity(Rule): """The Boot Security rule configures the system to run a job at system boot time that handles turning off potential vulnerability points such as: wifi, bluetooth, microphones, and cameras. """ def __init__(self, config, environ, logger, statechglogger): """ private method to initialize the module :param config: configuration object instance :param environ: environment object instance :param logger: logdispatcher object instance :param statechglogger: statechglogger object instance """ Rule.__init__(self, config, environ, logger, statechglogger) self.rulenumber = 18 self.rulename = 'BootSecurity' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = [] self.applicable = { 'type': 'white', 'family': ['linux'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.servicehelper = ServiceHelper(environ, logger) self.ch = CommandHelper(self.logdispatch) if os.path.exists('/bin/systemctl'): self.type = 'systemd' elif os.path.exists('/sbin/launchd'): self.type = 'mac' else: self.type = 'rclocal' self.rclocalpath = '/etc/rc.local' if os.path.islink(self.rclocalpath): paths = ['/etc/rc.d/rc.local', '/etc/init.d/rc.local'] for rcpath in paths: if os.path.isfile(rcpath): self.rclocalpath = rcpath self.logdispatch.log(LogPriority.DEBUG, 'Using rc.local file ' + self.rclocalpath) datatype = 'bool' key = 'BOOTSECURITY' instructions = """To disable this rule set the value of BOOTSECURITY to False.""" default = True self.bootci = self.initCi(datatype, key, instructions, default) datatype2 = 'bool' key2 = 'ENABLEFIPS' instructions2 = """!WARNING! DO NOT ENABLE THIS OPTION IF YOUR SYSTEM IS ARLEADY FDE ENCRYPTED! To enable full fips compliance on this system, at boot, set the value of ENABLEFIPS to True.""" default2 = False self.fips_ci = self.initCi(datatype2, key2, instructions2, default2) self.set_paths() def set_paths(self): """ """ self.systemd_boot_service_file = "/etc/systemd/system/stonixBootSecurity.service" self.rc_boot_script = "/usr/bin/stonix_resources/stonixBootSecurityLinux.py" self.systemd_service_name = "stonixBootSecurity.service" self.stonix_launchd_plist = "/Library/LaunchDaemons/gov.lanl.stonix.bootsecurity.plist" self.stonix_launchd_name = "gov.lanl.stonix.bootsecurity" self.grub_file = "/etc/default/grub" self.grub_config_file = "" grub_configs = [ "/boot/grub2/grub.cfg", "/boot/efi/EFI/redhat/grub.cfg" ] for c in grub_configs: if os.path.isfile(c): self.grub_config_file = c break self.grub_updater_cmd = "" grub_updater_locs = [ "/usr/sbin/grub2-mkconfig", "/sbin/grub2-mkconfig", "/usr/sbin/update-grub", "/sbin/update-grub" ] for l in grub_updater_locs: if os.path.isfile(l): self.grub_updater_cmd = l break if self.grub_updater_cmd in [ "/usr/sbin/grub2-mkconfig", "/sbin/grub2-mkconfig" ]: self.grub_updater_cmd += " -o " + self.grub_config_file def auditsystemd(self): """ check whether the stonixbootsecurity.service service module is loaded :return: boolean; True if the stonixbootsecurity.service service module is loaded, False if not """ compliant = True self.stonix_boot_service_contents = """[Unit] Description=Stonix Boot Security After=network.target [Service] ExecStart=/usr/bin/stonix_resources/stonixBootSecurityLinux.py [Install] WantedBy=multi-user.target """ try: # check if service file exists if not os.path.isfile(self.systemd_boot_service_file): compliant = False self.detailedresults += "\nstonix boot service unit does not exist" else: # check contents of service file f = open(self.systemd_boot_service_file, "r") contents = f.read() f.close() if contents != self.stonix_boot_service_contents: compliant = False self.detailedresults += "\nstonix boot service unit contents incorrect" # check if service is enabled if not self.servicehelper.auditService(self.systemd_service_name): compliant = False self.detailedresults += "\nstonix boot service is not enabled" except: raise return compliant def auditrclocal(self): """ check whether the rclocal configuration file contains the correct stonixBootSecurity line entry :return: compliant - boolean; True if compliant, False if not """ compliant = True try: tmppath = self.rc_boot_script + ".stonixtmp" data = {"python3": self.rc_boot_script} self.rc_boot_security_editor = KVEditorStonix( self.statechglogger, self.logdispatch, "conf", self.rc_boot_script, tmppath, data, "present", "space") if not self.rc_boot_security_editor.report(): self.detailedresults += "\nThe following config line is missing or incorrect from " + str( self.rc_boot_script) + "\n" + "\n".join( self.rc_boot_security_editor.fixables) compliant = False except: raise return compliant def auditmac(self): """ check whether the stonixbootsecurity launchd job exists :return: """ compliant = True self.logdispatch.log(LogPriority.DEBUG, "Looking for macOS launchd job") self.stonix_plist_contents = """<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>gov.lanl.stonix.bootsecurity</string> <key>Program</key> <string>/Applications/stonix4mac.app/Contents/Resources/stonix.app/Contents/MacOS/stonix_resources/stonixBootSecurityMac</string> <key>RunAtLoad</key> <true/> </dict> </plist>""" try: if not os.path.exists(self.stonix_launchd_plist): compliant = False self.detailedresults += "\nCould not locate stonix boot security launchd job" except: raise return compliant def report_boot_fips(self): """ :return: """ found_fips = False compliant = True try: if not self.fips_ci.getcurrvalue(): self.logdispatch.log( LogPriority.DEBUG, "fips compliance check disabled. Skipping fips compliance check." ) return compliant else: self.logdispatch.log( LogPriority.DEBUG, "fips compliance check enabled. Checking for fips compliance..." ) # check grub template file for fips if self.grub_file: f = open(self.grub_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search('^GRUB_CMDLINE_LINUX_DEFAULT=', line, re.I): if re.search("fips=1", line, re.I): found_fips = True # check permanent grub config file for fips if self.grub_config_file: f = open(self.grub_config_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("fips=1", line, re.I): found_fips = True if self.is_luks_encrypted(): if found_fips: # fips=1 will break boot config if luks encrypted compliant = False self.detailedresults += "\nfips=1 config option found in boot config line. This will break system boot while the system is luks encrypted. Will remove this line and fips compatibility for luks will be configured instead." else: self.detailedresults += "\nNo problems detected with boot config" else: if not found_fips: compliant = False self.detailedresults += "\nfips=1 config option not found in boot config line. As this system is not luks encrypted, this line will be added to the boot config." else: self.detailedresults += "\nfips enabled in boot config" except: raise return compliant def report(self): """ check whether the current system complies with the boot security settings to disable wifi, bluetooth and microphone at boot time :return: self.compliant - boolean; True if system is compliant, False if not """ self.detailedresults = "" self.compliant = True try: if self.type == 'mac': self.logdispatch.log(LogPriority.DEBUG, 'Checking for Mac plist') if not self.auditmac(): self.compliant = False self.detailedresults += '\nPlist for stonixBootSecurity Launch Daemon not found.' elif self.type == 'systemd': self.logdispatch.log(LogPriority.DEBUG, 'Checking for systemd service') if not self.auditsystemd(): self.compliant = False self.detailedresults += '\nService for stonixBootSecurity not active.' elif self.type == 'rclocal': self.logdispatch.log(LogPriority.DEBUG, 'Checking rc.local') if not self.auditrclocal(): self.compliant = False self.detailedresults += '\nstonixBootSecurity-Linux not scheduled in rc.local.' else: self.compliant = False self.detailedresults += "\nThis platform is not supported by STONIX" if self.compliant: self.detailedresults += '\nstonixBootSecurity correctly scheduled for execution at boot.' if not self.report_boot_fips(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except: self.compliant = False self.detailedresults += traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def setsystemd(self): """ create a systemd service unit which will run an installed script to disable wifi, bluetooth and microphone at boot time """ self.logdispatch.log(LogPriority.DEBUG, "Creating stonix boot security service unit") try: # create the new service unit f = open(self.systemd_boot_service_file, "w") f.write(self.stonix_boot_service_contents) f.close() myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": self.systemd_boot_service_file } self.statechglogger.recordchgevent(myid, event) os.chown(self.systemd_boot_service_file, 0, 0) os.chmod(self.systemd_boot_service_file, 0o644) # make the service manager aware of the new service unit reloadcmd = '/bin/systemctl daemon-reload' try: self.ch.executeCommand(reloadcmd) except: pass # ensure that the new service is enabled self.servicehelper.enableService('stonixBootSecurity') except: raise def setrclocal(self): """ install and run a boot security script which will disable wifi, bluetooth and microphone at boot time """ success = True try: if not self.rc_boot_security_editor.fix(): self.logdispatch.log(LogPriority.DEBUG, "KVEditor failed to fix") success = False elif not self.rc_boot_security_editor.commit(): self.logdispatch.log(LogPriority.DEBUG, "KVEditor failed to commit changes") success = False except: raise return success def setmac(self): """ install a boot security plist on mac, which will run an oascript to disable microphone on mac at boot time """ success = True try: whandle = open(self.stonix_launchd_plist, 'w') whandle.write(self.stonix_plist_contents) whandle.close() os.chown(self.stonix_launchd_plist, 0, 0) os.chmod(self.stonix_launchd_plist, 0o644) except: raise return success def fix_boot_fips(self): """ :return: """ success = True fixed_fips = False tmpfile = self.grub_file + ".stonixtmp" try: if not self.fips_ci.getcurrvalue(): self.logdispatch.log( LogPriority.DEBUG, "fips compliance option disabled. Skipping fips compliance fix..." ) return success else: self.logdispatch.log( LogPriority.DEBUG, "fips compliance option enabled. Proceeding with fix compliance fix..." ) if self.environ.getosname() == "RHEL": self.logdispatch.log( LogPriority.DEBUG, "System detected as RHEL. Running RHEL specific fixes...") if not self.fix_rhel7_boot_fips(): success = False return success else: self.logdispatch.log( LogPriority.DEBUG, "System is not RHEL-based. Running generic fixes...") f = open(self.grub_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("^GRUB_CMDLINE_LINUX_DEFAULT=", line, re.I): contentlines = [ c.replace(line, line.strip()[:-1] + ' fips=1"\n') for c in contentlines ] fixed_fips = True if not fixed_fips: contentlines.append( 'GRUB_CMDLINE_LINUX_DEFAULT="splash quiet audit=1 fips=1"\n' ) tf = open(tmpfile, "w") tf.writelines(contentlines) tf.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.grub_file} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(tmpfile, self.grub_file, myid) os.rename(tmpfile, self.grub_file) self.ch.executeCommand(self.grub_updater_cmd) except: raise return success def remove_fips_line(self): """ the fips=1 configuration option at the end of the linuxefi boot line in grub config (for efi-based systems) causes rhel to revert to an emergency dracut mode instead of booting normally, when the system is encrypted with luks this method will ensure that line is removed and the grub configuration is updated. :return: """ success = True self.logdispatch.log( LogPriority.DEBUG, "Attempting to remove fips=1 option from grub boot config...") tmpfile = self.grub_file + ".stonixtmp" f = open(self.grub_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("^GRUB_CMDLINE_LINUX_DEFAULT=", line, re.I): self.logdispatch.log(LogPriority.DEBUG, "fips=1 found in boot config file") line2 = line.replace("fips=1", "") self.logdispatch.log( LogPriority.DEBUG, "removing fips=1 from " + str(self.grub_file)) contentlines = [c.replace(line, line2) for c in contentlines] tf = open(tmpfile, "w") tf.writelines(contentlines) tf.close() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.grub_file} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(tmpfile, self.grub_file, myid) os.rename(tmpfile, self.grub_file) self.logdispatch.log(LogPriority.DEBUG, "regenerating efi boot config file...") self.ch.executeCommand(self.grub_updater_cmd) retcode = self.ch.getReturnCode() if retcode != 0: self.logdispatch.log(LogPriority.WARNING, "Failed to update efi boot config file!") f = open(self.grub_config_file, "r") contentlines = f.readlines() f.close() for line in contentlines: if re.search("fips=1", line, re.I): success = False if not success: self.logdispatch.log( LogPriority.WARNING, "fips=1 option still found in efi boot configuration!") else: self.logdispatch.log( LogPriority.DEBUG, "fips option successfully removed from efi boot configuration") return success def is_luks_encrypted(self): """ check all drive devices to see if any are luks encrypted :return: luks_encrypted :rtype: bool """ luks_encrypted = False command = "/sbin/blkid" devices = [] if not os.path.isfile(command): self.logdispatch.log( LogPriority.WARNING, "Unable to check devices for luks encryption due to missing utility 'blkid'" ) return luks_encrypted self.logdispatch.log(LogPriority.DEBUG, "Checking if any devices are luks encrypted...") try: self.ch.executeCommand(command) output = self.ch.getOutput() for line in output: if re.search('TYPE="crypto_LUKS"', line, re.I): luks_encrypted = True try: devices.append(str(line.split()[0])) except (IndexError, KeyError): continue except: raise for d in devices: if re.search(":", d): devices = [d.replace(":", "") for d in devices] if luks_encrypted: self.logdispatch.log( LogPriority.DEBUG, "The following devices are luks encrypted:\n" + "\n".join(devices)) return luks_encrypted def configure_luks_compatibility(self): """ configure rhel 7 systems, which are LUKS encrypted, to be compatible with fips https://access.redhat.com/solutions/137833 :return: """ prelink_pkg = "prelink" prelink = "/usr/sbin/prelink" dracut_aes_pkg = "dracut-fips-aesni" dracut_fips_pkg = "dracut-fips" prelink_conf_file = "/etc/sysconfig/prelink" dracut = "/usr/bin/dracut" grep = "/usr/bin/grep" mv = "/usr/bin/mv" prelink_installed = False aes_supported = False try: if self.is_luks_encrypted(): if not self.remove_fips_line(): self.detailedresults += "\nFailed to remove fips=1 from efi boot configuration file. Please run: 'sudo grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg' manually!" # check for cpu aes compatibility self.ch.executeCommand(grep + " -w aes /proc/cpuinfo") outputstring = self.ch.getOutputString() if re.search("aes", outputstring, re.I): aes_supported = True # check if prelink package is installed if self.ph.check(prelink_pkg): prelink_installed = True # install dracut fips package self.ph.install(dracut_fips_pkg) # install dracut aes package if cpu supports it if aes_supported: self.ph.install(dracut_aes_pkg) # disable prelinking if installed if prelink_installed: f = open(prelink_conf_file, "w") f.write("PRELINKING=no") f.close() os.chmod(prelink_conf_file, 0o644) os.chown(prelink_conf_file, 0, 0) self.ch.executeCommand(prelink + " -uav") # backup existing initramfs self.ch.executeCommand( mv + " -v /boot/initramfs-$(uname -r).img{,.bak}") # rebuild initramfs (this command may take some time) self.ch.executeCommand(dracut) except: raise def fix_rhel7_boot_fips(self): """ enable fips compliance on redhat 7 systems https://access.redhat.com/solutions/137833 :return: success :rtype: bool """ success = True self.ph = Pkghelper(self.logdispatch, self.environ) grubby = "/usr/sbin/grubby" findmnt = "/usr/bin/findmnt" try: # configure the system to be compatible with luks and fips self.configure_luks_compatibility() # add fips=1 to kernel boot line (requires sytem restart to take effect) self.ch.executeCommand(grubby + " --update-kernel=$(" + grubby + " --default-kernel) --args=fips=1") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to enable fips compliance in kernel boot line" # update boot partition info uuid = "" self.ch.executeCommand(findmnt + " -no uuid /boot") retcode = self.ch.getReturnCode() if retcode == 0: uuid = self.ch.getOutputString() else: success = False self.detailedresults += "\nFailed to update boot partition info" if uuid: self.ch.executeCommand( "[[ -n $uuid ]] && " + grubby + " --update-kernel=$(" + grubby + " --default-kernel) --args=boot=UUID=${uuid}") except: raise return success def fix(self): """ install system job which will run and disable bluetooth, microphone and wifi at boot """ self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: if self.bootci.getcurrvalue(): if self.type == 'mac': self.logdispatch.log(LogPriority.DEBUG, 'Creating Mac plist') self.setmac() elif self.type == 'systemd': self.logdispatch.log(LogPriority.DEBUG, 'Creating systemd service') self.setsystemd() elif self.type == 'rclocal': self.logdispatch.log(LogPriority.DEBUG, 'Creating rc.local entry') self.setrclocal() else: self.detailedresults = 'ERROR: Fix could not determine where boot job should be scheduled!' self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.rulesuccess = False if self.fips_ci.getcurrvalue(): if not self.fix_boot_fips(): self.rulesuccess = False else: self.logdispatch.log(LogPriority.DEBUG, "Rule not enabled. Nothing was done.") except (KeyboardInterrupt, SystemExit): raise except: self.rulesuccess = False self.detailedresults += traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class ConfigureMACPolicy(Rule): """The ConfigureMACPolicy class configures either selinux or apparmor depending on the os platform. @change: Derek Walker - created two config items, one for enable/disable, and another for whether the user wants to use permissive or enforcing """ def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 107 self.rulename = 'ConfigureMACPolicy' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.guidance = ['NSA(2.1.1.6)(2.4.2)', 'CCE-3977-6', 'CCE-3999-0', 'CCE-3624-4', 'CIS 1.7'] self.applicable = {'type': 'white', 'family': ['linux']} datatype = "bool" key = "CONFIGUREMAC" instructions = "To prevent the configuration of a mandatory " + \ "access control policy, set the value of CONFIGUREMAC to " + \ "False. Note: The 'mandatory access control' is either SELinux " + \ "or AppArmor, depending on what is available to your current system." default = True self.ConfigureMAC = self.initCi(datatype, key, instructions, default) datatype2 = "string" key2 = "MODE" default2 = "permissive" instructions2 = "Valid modes for SELinux are: permissive or " + \ "enforcing\nValid modes for AppArmor are: complain or enforce" self.modeci = self.initCi(datatype2, key2, instructions2, default2) def report(self): """ :return: """ self.selinux = False self.apparmor = False self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.sh = ServiceHelper(self.environ, self.logger) selinux_packages = ["selinux", "libselinux", "selinux-basics"] apparmor_packages = ["apparmor"] self.mac_package = "" # discover whether this system can use - or is using - selinux # or if it should use apparmor (selinux takes precedence) for p in selinux_packages: if self.ph.check(p): self.selinux = True self.mac_package = p break if not self.selinux: for p in apparmor_packages: if self.ph.check(p): self.apparmor = True self.mac_package = p break if not bool(self.selinux or self.apparmor): for p in selinux_packages: if self.ph.checkAvailable(p): self.selinux = True self.mac_package = p break if not self.selinux: for p in apparmor_packages: if self.ph.checkAvailable(p): self.apparmor = True self.mac_package = p break self.compliant = True self.detailedresults = "" self.mode = str(self.modeci.getcurrvalue()) try: if self.selinux: if not self.reportSelinux(): self.compliant = False elif self.apparmor: if not self.reportApparmor(): self.compliant = False else: self.detailedresults += "\nCould not find either selinux or apparmor installed and nother appears to be available to this system!" self.compliant = False except (KeyboardInterrupt, SystemExit): raise except: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def set_selinux_conf_path(self): """ :return: """ # discover correct location of selinux config file self.selinux_config_file = "/etc/sysconfig/selinux" selinux_config_files = ["/etc/selinux/config", "/etc/sysconfig/selinux"] for p in selinux_config_files: if os.path.exists(p): self.selinux_config_file = p break def reportSelinux(self): """ :return: """ compliant = True conf_option_dict = {"selinux\s+status:\s+enabled": "status", "current\s+mode:\s+" + self.mode: "mode"} # check if selinux is installed if not self.ph.check(self.mac_package): compliant = False self.detailedresults += "\nSELinux is not installed" # check sestatus for current configuration of selinux self.ch.executeCommand("/usr/sbin/sestatus") output = self.ch.getOutput() for co in conf_option_dict: rco = re.compile(co, re.I) if not list(filter(rco.match, output)): compliant = False self.detailedresults += "\nSELinux " + conf_option_dict[co] + " is not configured properly" self.set_selinux_conf_path() # check selinux config file for correct configuration so setting is # persistent after reboot selinux_tmp_file = self.selinux_config_file + ".stonixtmp" selinux_config_dict = {"SELINUX": self.mode} self.selinux_config_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.selinux_config_file, selinux_tmp_file, selinux_config_dict, "present", "closedeq") self.selinux_config_editor.report() if self.selinux_config_editor.fixables: compliant = False self.detailedresults += "\nFollowing option(s) not configured correctly in " + self.selinux_config_file + " :\n" + "\n".join(self.selinux_config_editor.fixables) return compliant def reportApparmor(self): """ :return: """ compliant = True aa_enabled = "Yes" # check if apparmor is installed if not self.ph.check(self.mac_package): compliant = False self.detailedresults += "\nApparmor is not installed" # check if apparmor is enabled self.ch.executeCommand("/usr/bin/aa-enabled") output = self.ch.getOutputString() if not re.search(aa_enabled, output): compliant = False self.detailedresults += "\nApparmor is not enabled" # check if boot configuration for apparmor is correct f = open("/etc/default/grub", "r") contents = f.readlines() f.close() for line in contents: if re.search("GRUB_CMDLINE_LINUX_DEFAULT=", line): if not re.search("apparmor=1", line): compliant = False self.detailedresults += "\nApparmor not enabled in boot config" elif not re.search("security=apparmor", line): compliant = False self.detailedresults += "\nApparmor not enabled in boot config" break return compliant def fix(self): """ :return: """ self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 try: if self.selinux: if not self.fixSelinux(): self.rulesuccess = False elif self.apparmor: if not self.fixApparmor(): self.rulesuccess = False else: self.rulesuccess = False self.detailedresults += "\nNeither SELinux nor Apparmor appears to be available to this system!" except (KeyboardInterrupt, SystemExit): raise except: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixSelinux(self): """ :return: """ success = True self.iditerator = 0 activate_utility = "/usr/sbin/selinux-activate" # install selinux package if it is not installed if not self.ph.check(self.mac_package): if not self.ph.install(self.mac_package): success = False self.detailedresults += "\nFailed to install selinux package" else: # if we just installed selinux, then we need to discover the correct conf path self.set_selinux_conf_path() self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype":"pkghelper", "pkgname":self.mac_package} self.statechglogger.recordchgevent(myid, event) if os.path.exists(activate_utility): self.ch.executeCommand(activate_utility) output = self.ch.getOutputString() if re.search("need to reboot", output, re.I): self.detailedresults += "\nSElinux has been configured, but you will need to reboot before selinux can become active. This rule will not report compliant until this is done." # set enforcement mode for selinux self.ch.executeCommand("/usr/sbin/setenforce " + self.mode) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to set selinux mode to: " + self.mode self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.selinux_config_editor.setEventID(myid) # configure the selinux config file for persistence through reboot if not self.selinux_config_editor.fix(): success = False self.detailedresults += "\nFailed to fix " + self.selinux_config_file elif not self.selinux_config_editor.commit(): success = False self.detailedresults += "\nFailed to fix " + self.selinux_config_file return success def fixApparmor(self): """ :return: """ success = True profiles_dir = "/etc/apparmor.d/" valid_modes = ["complain", "enforce"] grub_file = "/etc/default/grub" tmp_grub_file = grub_file + ".stonixtmp" # install apparmor package if not installed if not self.ph.check(self.mac_package): if not self.ph.install(self.mac_package): success = False self.detailedresults += "\nFailed to install apparmor package" else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype":"pkghelper", "pkgname":self.mac_package} self.statechglogger.recordchgevent(myid, event) if not self.ph.check("apparmor-profiles"): self.ph.install("apparmor-profiles") if not self.ph.check("apparmor-utils"): self.ph.install("apparmor-utils") # set apparmor enforcement mode if self.mode.lower() in ["complain", "permissive"]: self.ch.executeCommand("/usr/sbin/aa-complain " + profiles_dir + "*") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to set apparmor profiles to complain mode" elif self.mode.lower() in ["enforce", "enforcing"]: if os.path.exists(profiles_dir): self.ch.executeCommand("/usr/sbin/aa-enforce " + profiles_dir + "*") retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "\nFailed to set apparmor profiles to enforce mode" else: self.logger.log(LogPriority.DEBUG, "apparmor profiles directory does not exist") success = False self.detailedresults += "\nFailed to set apparmor mode to: " + self.mode else: success = False self.detailedresults += "\nPlease specify one of the following options in the MODE field:\n" + "\n".join(valid_modes) # correct apparmor boot config # (can't use kveditor because it can't handle appending to a config line) if os.path.exists(grub_file): f = open(grub_file, "r") contents = f.readlines() f.close() for n, i in enumerate(contents): if re.search("GRUB_CMDLINE_LINUX_DEFAULT=", i): contents[n] = i.strip()[:-1]+' apparmor=1 security=apparmor"\n' tf = open(tmp_grub_file, "w") tf.writelines(contents) tf.close() self.iditerator += 1 event = {"eventtype":"conf", "filepath":grub_file} myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(grub_file, tmp_grub_file, myid) os.rename(tmp_grub_file, grub_file) # run update-grub to apply the new grub config self.ch.executeCommand("/usr/sbin/update-grub") return success
class ShellTimeout(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 120 self.rulename = "ShellTimeout" self.formatDetailedResults("initialize") self.mandatory = False self.sethelptext() datatype = "bool" key = "SHELLTIMEOUT" instructions = "To disable this rule set the value of " + \ "SHELLTIMEOUT to False." default = False self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ["NSA 2.3.5.5", "CCE 3689-7", "CCE 3707-7"] self.applicable = {"type": "white", "family": ["linux"]} self.iditerator = 0 self.created = False def report(self): try: self.path1 = "/etc/profile.d/tmout.sh" self.data1 = {"TMOUT": "900"} self.data2 = {"readonly": "TMOUT", "export": "TMOUT"} self.path2 = "/etc/profile.d/autologout.csh" self.cshData = "set -r autologout 15" compliant = True self.detailedresults = "" if os.path.exists(self.path1): # Shell scripts in profile.d do not require +x, so they can # be either 0755 (0755) or 0644 (0644) if not checkPerms(self.path1, [0, 0, 0o755], self.logger) and \ not checkPerms(self.path1, [0, 0, 0o644], self.logger): compliant = False self.detailedresults += self.path1 + \ " permissions incorrect\n" self.tmppath1 = self.path1 + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path1, self.tmppath1, self.data1, "present", "closedeq") kveReport1 = self.editor1.report() self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path1, self.tmppath1, self.data2, "present", "space") kveReport2 = self.editor2.report() if not kveReport1 or not kveReport2: compliant = False self.detailedresults += self.path1 + \ " does not contain the correct values\n" else: compliant = False self.detailedresults += self.path1 + " does not exist\n" if os.path.exists(self.path2): if not checkPerms(self.path2, [0, 0, 0o755], self.logger) and \ not checkPerms(self.path2, [0, 0, 0o644], self.logger): compliant = False self.detailedresults += self.path2 + \ " permissions incorrect\n" cshText = open(self.path2, "r").read() if not re.search(self.cshData, cshText): compliant = False self.detailedresults += self.path2 + \ " does not contain the correct values\n" else: compliant = False self.detailedresults += self.path2 + " does not exist\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(): self.detailedresults += "CI not enabled\n" else: success = True self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not os.path.exists(self.path1): createFile(self.path1, self.logger) self.created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path1} self.statechglogger.recordchgevent(myid, event) self.tmppath = self.path1 + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path1, self.tmppath, self.data1, "present", "closedeq") self.editor1.report() self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path1, self.tmppath, self.data2, "present", "space") self.editor2.report() if self.editor1.fixables or self.editor2.fixables: if self.editor1.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if self.editor1.commit(): debug = self.path1 + "'s contents have been " + \ "corrected\n" self.logger.log(LogPriority.DEBUG, debug) resetsecon(self.path1) else: debug = "kveditor commit not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.path1 + \ " properties could not be set\n" else: debug = "kveditor fix not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.path1 + \ " properties could not be set\n" if self.editor2.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if self.editor2.commit(): debug = self.path1 + "'s contents have been " + \ "corrected\n" self.logger.log(LogPriority.DEBUG, debug) resetsecon(self.path1) else: debug = "kveditor commit not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.path1 + \ " properties could not be set\n" else: debug = "kveditor fix not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.path1 + \ " properties could not be set\n" if not checkPerms(self.path1, [0, 0, 0o755], self.logger) and \ not checkPerms(self.path1, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path1, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Could not set permissions " + \ "for " + self.path1 + "\n" if not os.path.exists(self.path2): createFile(self.path2, self.logger) self.created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path2} self.statechglogger.recordchgevent(myid, event) writeFile(self.path2, self.cshData, self.logger) if not checkPerms(self.path2, [0, 0, 0o755], self.logger) and \ not checkPerms(self.path2, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path2, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Could not set permissions " + \ "for " + self.path2 + "\n" self.rulesuccess = success except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class DisableIPV6(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 123 self.rulename = "DisableIPV6" self.formatDetailedResults("initialize") self.guidance = ["NSA 2.5.3.1"] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # configuration item instantiation datatype = 'bool' key = 'DISABLEIPV6' instructions = "To disable this rule set the value of DISABLEIPV6 " + \ "to False." default = False self.ci = self.initCi(datatype, key, instructions, default) self.iditerator = 0 self.created = False self.created2 = False # self.editor1: sysctl file editor # self.editor2: network file editor # self.editor3: sshd file editor self.editor1, self.editor2, self.editor3 = "", "", "" self.sh = ServiceHelper(self.environ, self.logger) self.sethelptext() def report(self): try: self.detailedresults = "" if self.environ.getosfamily() == "linux": self.ph = Pkghelper(self.logger, self.environ) self.compliant = self.reportLinux() elif self.environ.getosfamily() == "freebsd": self.compliant = self.reportFree() elif self.environ.getostype() == "Mac OS X": self.compliant = self.reportMac() except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): try: if not self.ci.getcurrvalue(): return self.detailedresults = "" # clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getosfamily() == "linux": self.rulesuccess = self.fixLinux() elif self.environ.getosfamily() == "freebsd": self.rulesuccess = self.fixFree() elif self.environ.getosfamily() == "darwin": self.rulesuccess = self.fixMac() except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def reportMac(self): self.ifaces = [] compliant = True self.cmdhelper = CommandHelper(self.logger) cmd = ["/usr/sbin/networksetup", "-listallnetworkservices"] if not self.cmdhelper.executeCommand(cmd): self.detailedresults += "Unable to run " + \ "networksetup -listallnetworkservices command\n" return False output = self.cmdhelper.getOutput() for item in output: item = item.strip() cmd = ["/usr/sbin/networksetup", "-getinfo", item] if not self.cmdhelper.executeCommand(cmd): self.detailedresults += "Unable to run " + \ "networksetup -getinfo command\n" return False output2 = self.cmdhelper.getOutput() for item2 in output2: if re.search("^IPv6:", item2): check = item2.split(":") if check[1].strip() != "Off": self.detailedresults += "IPV6 is not turned off " + \ "for " + item + " interface\n" self.ifaces.append(item) compliant = False return compliant def fixMac(self): if self.ifaces: for item in self.ifaces: cmd = ["/usr/sbin/networksetup", "setv6off", item] if not self.cmdhelper.executeCommand(cmd): self.rulesuccess = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { 'eventtype': 'comm', 'command': ['/usr/sbin/networksetup', 'setv6automatic', item] } self.statechglogger.recordchgevent(myid, event) return self.rulesuccess def reportFree(self): compliant = True self.editor1, self.editor2 = "", "" directives1 = { "ipv6_network_interfaces": "none", "ipv6_activate_all_interfaces": "NO", "ip6addrctl_enable": "NO", "ip6addrctl_policy": "NO" } directives2 = { "net.ipv6.conf.all.disable_ipv6": "1", "net.ipv6.conf.default.disable_ipv6": "1", "net.ipv6.conf.lo.disable_ipv6": "1" } path1 = "/etc/rc.conf" path2 = "/etc/sysctl.conf" tmpfile1 = "/etc/rc.conf.tmp" tmpfile2 = "/etc/sysctl.conf.tmp" # try and create /etc/rc.conf if doesn't exist if not os.path.exists(path1): if not createFile(path1, self.logger): compliant = False self.detailedresults += "Unable to create the file: " + \ path1 + ", so this file will not be configured, " + \ "resulting in failed compliance\n" if os.path.exists(path1): if not checkPerms(path1, [0, 0, 420], self.logger): compliant = False self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", path1, tmpfile1, directives1, "present", "closedeq") if not self.editor1.report(): compliant = False # try and create /etc/sysctl.conf if doesn't exist if not os.path.exists(path2): if not createFile(path2, self.logger): compliant = False self.detailedresults += "Unable to create the file: " + \ path2 + " so this file will not be configured " + \ "resulting in failed compliance\n" if os.path.exists(path2): if not checkPerms(path2, [0, 0, 384], self.logger): compliant = False self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", path2, tmpfile2, directives2, "present", "closedeq") if not self.editor2.report(): compliant = False else: compliant = False cmdhelper = CommandHelper(self.logger) cmd = ["/sbin/ifconfig", "-a"] if not cmdhelper.executeCommand(cmd): return False output = cmdhelper.getOutput() for line in output: line = line.strip() if re.search("^nd6", line): if not re.search("(.)*IFDISABLED(.)*", line): compliant = False return compliant def fixFree(self): # debug messages are used for developers, self.detailedresults # are used for the users information path1 = "/etc/rc.conf" path2 = "/etc/sysctl.conf" success = True debug = "" if os.path.exists(path1): if not checkPerms(path1, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 420], self.logger, self.statechglogger, myid): success = False if self.editor1: if self.editor1.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): debug += "Kveditor unable to correct file: " + \ path1 + "\n" self.detailedresults += "Unable to correct " + path1 + \ "\n" success = False elif not self.editor1.commit(): self.detailedresults += "Unable to correct " + path1 + \ "\n" debug += "commit for kveditor1 was not successful\n" success = False else: debug += "Editor2 was never created so path didn't exist \ and/or wasn't able to be created\n" success = False else: self.detailedresults += path1 + " doesn't exist!\n" debug += path1 + " doesn't exist, unble to fix file\n" success = False if os.path.exists(path2): # check permissions on /etc/sysctl.conf if not checkPerms(path2, [0, 0, 384], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) # set permissions if wrong if not setPerms(self.path, [0, 0, 384, self.logger], self.statechglogger, myid): success = False # check if editor is present if self.editor2: if self.editor2.fixables(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): debug += "Kveditor unable to correct file: " + \ path2 + "\n" self.detailedresults += "Unable to correct " + path2 + \ "\n" success = False elif not self.editor2.commit(): self.detailedresults += "Unable to correct " + path2 + \ "\n" debug += "commit for kveditor2 was not successful\n" success = False else: debug += "Editor2 was never created so path didn't exist \ and/or wasn't able to be created\n" success = False else: self.detailedresults += path2 + " doesn't exist!\n" debug += path2 + " doesn't exist, unble to fix file\n" success = False # restart the network ch = CommandHelper(self.logger) cmd = ["/etc/rc.d/netif", "restart"] if not ch.executeCommand(cmd): self.detaileresults += "Unable to restart network\n" success = False return success def reportLinux(self): self.ch = CommandHelper(self.logger) compliant = True netwrkfile = "" ifacefile = "" self.modprobefiles = [] #self.modprobeOK = False self.modprobeOK = True sysctl = "/etc/sysctl.conf" self.interface = {"IPV6INIT": "no", "NETWORKING_IPV6": "no"} self.sysctls = { "net.ipv6.conf.all.disable_ipv6": "1", "net.ipv6.conf.default.disable_ipv6": "1" } self.modprobes = { "options": ["ipv6 disable=1"], "install": ["ipv6 /bin/true"] } # stig portion, check netconfig file for correct contents if self.ph.manager == "apt-get": nfspkg = "nfs-common" else: nfspkg = "nfs-utils.x86_64" if self.ph.check(nfspkg): if os.path.exists("/etc/netconfig"): item1 = "udp6 tpi_clts v inet6 udp - -" item2 = "tcp6 tpi_cots_ord v inet6 tcp - -" contents = readFile("/etc/netconfig", self.logger) for line in contents: line = re.sub("\s+", " ", line.strip()) if re.search(item1, line) or re.search(item2, line): self.detailedresults += "/etc/netconfig file contains " + \ "lines we don't want present\n" compliant = False # "ifconfig" has been deprecated on Debian9 and some otherd distros # so use "ip addr" instead # Here we check if the system is giving out ipv6 ip addresses if os.path.exists("/sbin/ifconfig"): cmd = ["/sbin/ifconfig"] else: cmd = ["/sbin/ip", "addr"] if not self.ch.executeCommand(cmd): compliant = False else: output = self.ch.getOutput() for line in output: if re.search("^inet6", line.strip()): self.detailedresults += "inet6 exists in the " + \ "ifconfig output\n" compliant = False break # check for ipv6 address in hostname file if os.path.exists("/etc/hosts"): contents = readFile("/etc/hosts", self.logger) for line in contents: if re.search("^#", line) or re.match("^\s*$", line): continue if re.search(":", line): compliant = False # check compliancy of /etc/sysctl.conf file if not os.path.exists(sysctl): self.detailedresults += "File " + sysctl + " does not exist\n" compliant = False else: tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): self.detailedresults += "/etc/sysctl file doesn't contain \ the correct contents\n" compliant = False if not checkPerms(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions for " + sysctl + \ "are incorrect\n" compliant = False # in addition to checking /etc/sysctl.conf contents we need to # also check sysctl compliancy using the sysctl command for key in self.sysctls: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() output = self.ch.getOutputString() errmsg = output + self.ch.getErrorString() if retcode != 0: if re.search("unknown key", errmsg): continue else: self.detailedresults += "Failed to get value " + key + " using sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: actualoutput = output.strip() expectedoutput = key + " = " + self.sysctls[key] if actualoutput != expectedoutput: compliant = False self.detailedresults += "\nsysctl output has " + \ "incorrect value: expected " + \ expectedoutput + ", found " + \ actualoutput + "\n" # check files inside modprobe.d directory for correct contents if os.path.exists("/etc/modprobe.d/"): modprobefiles = glob.glob("/etc/modprobe.d/*") for modfile in modprobefiles: tmpfile = "" modprobekveditor = KVEditorStonix(self.statechglogger, self.logger, "conf", modfile, tmpfile, self.modprobes, "present", "space") if modprobekveditor.report(): self.modprobeOK = True break if not self.modprobeOK: self.detailedresults += "Didn't find desired contents inside files " + \ "within /etc/modprobe.d/" compliant = False else: # system isn't using loadable kernel modules, not an issue self.modprobeOK = True # Check for existence of interface and network files to be configured if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" if not os.path.exists(ifacefile): ifacefile = "" netwrkfile = "/etc/sysconfig/network" if not os.path.exists(netwrkfile): netwrkfile = "" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" if not os.path.exists(ifacefile): ifacefile = "" # go through interface directory and check each interface file # for correct contents if ifacefile: dirs = glob.glob(ifacefile + '*') for loc in dirs: contents = [] if re.search('^' + ifacefile + 'ifcfg', loc): if not checkPerms(loc, [0, 0, 0o644], self.logger): compliant = False contents = readFile(loc, self.logger) if contents: for key in self.interface: found = False for line in contents: if re.search("^#", line) or re.match( "^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip( ) == self.interface[key]: found = True continue else: found = False break else: compliant = False self.detailedresults += loc + \ " file in bad format\n" if not found: self.detailedresults += "contents of " + \ loc + " file is wrong\n" compliant = False break else: continue else: compliant = False # check network file for correct contents if netwrkfile: if os.path.exists(netwrkfile): if not checkPerms(netwrkfile, [0, 0, 0o644], self.logger): compliant = False tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface, "present", "closedeq") if not self.editor2.report(): self.detailedresults += netwrkfile + " doesn't contain \ the correct contents\n" compliant = False else: self.detailedresults += netwrkfile + " doesn't exist\n" compliant = False # This subpart is only for apt-get based systems # sshd needs the inet directive for ipv6 disablement if self.ph.manager == "apt-get": data = {"AddressFamily": "inet"} kvtype = "conf" path = "/etc/ssh/sshd_config" tmpPath = path + ".tmp" intent = "present" configtype = "space" self.editor3 = KVEditorStonix(self.statechglogger, self.logger, kvtype, path, tmpPath, data, intent, configtype) if not self.editor3.report(): self.detailedresults += "/etc/ssh/ssdh_config doesn't \ contain the correct contents\n" compliant = False return compliant def fixLinux(self): ''' @change: dkennel removed extraneous arg from setperms call on 864 ''' universal = "#The following lines were added by stonix\n" debug = "" success = True ifacefile = "" netwrkfile = "" sysctl = "/etc/sysctl.conf" blacklistfile = "/etc/modprobe.d/stonix-blacklist.conf" # STIG portion, correct netconfig file if self.ph.manager == "apt-get": nfspkg = "nfs-common" else: nfspkg = "nfs-utils.x86_64" # if package not installed, no need to configure it if self.ph.check(nfspkg): if os.path.exists("/etc/netconfig"): filestring = "" # we want to make sure the following two lines don't # appear in the netconfig file item1 = "udp6 tpi_clts v inet6 udp - -" item2 = "tcp6 tpi_cots_ord v inet6 tcp - -" contents = readFile("/etc/netconfig", self.logger) for line in contents: templine = re.sub("\s+", " ", line.strip()) # if we find the lines, skip them thus leaving them out of # of the rewrite if re.search(item1, templine) or re.search( item2, templine): continue else: filestring += line tmpfile = "/etc/netconfig.tmp" if not writeFile(tmpfile, filestring, self.logger): success = False else: # record event, rename file, set perms self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": "/etc/netconfig"} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( "/etc/netconfig", tmpfile, myid) os.rename(tmpfile, "/etc/netconfig") os.chown("/etc/netconfig", 0, 0) os.chmod("/etc/netconfig", 420) resetsecon("/etc/netconfig") # remove any ipv6 addresses from /etc/hosts file if os.path.exists("/etc/hosts"): contents = readFile("/etc/hosts", self.logger) tempstring = "" tmpfile = "/etc/hosts.tmp" for line in contents: if re.search("^#", line) or re.match("^\s*$", line): tempstring += line continue elif re.search(":", line): tempstring += "#" + line else: tempstring += line if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": "/etc/hosts"} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange("/etc/hosts", tmpfile, myid) os.rename(tmpfile, "/etc/hosts") os.chown("/etc/hosts", 0, 0) os.chmod("/etc/hosts", 420) resetsecon("/etc/hosts") else: success = False debug = "Unable to write to file /etc/hosts\n" self.logger.log(LogPriority.DEBUG, debug) # fix sysctl / tuning kernel parameters # manually write key value pairs to /etc/sysctl.conf created = False if not os.path.exists(sysctl): if createFile(sysctl, self.logger): created = True setPerms(sysctl, [0, 0, 0o644], self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sysctl} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to create " + sysctl + "\n" self.logger.log(LogPriority.DEBUG, debug) if os.path.exists(sysctl): if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sysctl, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): if self.editor1.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor1.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not setPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False resetsecon(sysctl) # here we also check the output of the sysctl command for each key # to cover all bases for key in self.sysctls: if self.ch.executeCommand("/sbin/sysctl " + key): output = self.ch.getOutputString().strip() errmsg = output + self.ch.getErrorString() if re.search("unknown key", errmsg): continue if not re.search(self.sysctls[key] + "$", output): undovalue = output[-1] self.ch.executeCommand("/sbin/sysctl -q -e -w " + key + "=" + self.sysctls[key]) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set " + key + " = " + self.sysctls[ key] + "\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -q -e -w " + key + "=" + undovalue event = { "eventtype": "commandstring", "command": command } self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Unable to get value for " + key + "\n" success = False # at the end do a print and ignore any key errors to ensure # the new values are read into the kernel self.ch.executeCommand("/sbin/sysctl -q -e -p") retcode2 = self.ch.getReturnCode() if retcode2 != 0: success = False self.detailedresults += "Failed to load new sysctl configuration from config file\n" errmsg2 = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg2) # We never found the correct contents in any of the modprobe.d files # so we're going to created the stonix-blacklist file # this file is used in other rules if not self.modprobeOK: created = False tmpfile = blacklistfile + ".tmp" modprobekveditor = KVEditorStonix(self.statechglogger, self.logger, "conf", blacklistfile, tmpfile, self.modprobes, "notpresent", "space") if not os.path.exists(blacklistfile): # create the file and record the event as file creation if createFile(blacklistfile, self.logger): created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": blacklistfile } self.statechglogger.recordchgevent(myid, event) if os.path.exists(blacklistfile): if not modprobekveditor.report(): if not modprobekveditor.fix(): success = False self.detailedresults += "Unable to correct contents in " + \ blacklistfile + "\n" else: # if the file was created, then we already recorded an event # for that, so this step would get skipped if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) modprobekveditor.setEventID(myid) if not modprobekveditor.commit(): success = False self.detailedresults += "Unable to correct contents in " + \ blacklistfile + "\n" # fix ifcfg (interface) files if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" netwrkfile = "/etc/sysconfig/network" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" if ifacefile: if os.path.exists(ifacefile): dirs = glob.glob(ifacefile + "*") if dirs: for loc in dirs: interface = {"IPV6INIT": "no", "NETWORKING_IPV6": "no"} interface2 = { "IPV6INIT": "no", "NETWORKING_IPV6": "no" } found = False tempstring = "" if re.search('^' + ifacefile + 'ifcfg', loc): filename = loc tmpfile = filename + ".tmp" contents = readFile(filename, self.logger) if not checkPerms(filename, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(filename, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + \ filename + "\n" self.logger.log(LogPriority.DEBUG, debug) success = False for key in interface: found = False for line in contents: if re.search("^#", line) or \ re.match("^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip( ) == interface[key]: if found: continue found = True else: contents.remove(line) if found: del interface2[key] for line in contents: tempstring += line tempstring += universal for key in interface2: tempstring += key + "=" + interface2[key] + \ "\n" if not writeFile(tmpfile, tempstring, self.logger): success = False debug = "Unable to write to file " + loc + "\n" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': filename} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( filename, tmpfile, myid) os.rename(tmpfile, filename) os.chown(filename, 0, 0) os.chmod(filename, 420) resetsecon(filename) elif not os.path.exists(ifacefile) and ifacefile != "": # will not attempt to create the interface files debug = "interface directory which holds interface \ files, doesn't exist, stonix will not attempt to make this \ directory or the files contained therein" success = False self.logger.log(LogPriority.DEBUG, debug) # fix network file if it exists if netwrkfile: if not os.path.exists(netwrkfile): if not createFile(netwrkfile, self.logger): debug = "Unable to create " + netwrkfile + "file\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: if not checkPerms(netwrkfile, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(netwrkfile, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + \ netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) success = False tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface, "present", "closedeq") if not self.editor2.report(): self.detailedresults += netwrkfile + " doesn't contain \ the correct contents\n" if self.editor2: if self.editor2.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for " + netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor2.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for " + netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(netwrkfile, 0, 0) os.chmod(netwrkfile, 420) resetsecon(netwrkfile) # fix sshd_config file for apt-get systems if ssh is installed if self.ph.manager == "apt-get": if not os.path.exists("/etc/ssh/sshd_config"): msg = "/etc/ssh/ssd_config doesn\'t exist. This could mean ssh \ is not installed or the file has been inadvertantly deleted. Due to the \ complexity of this file stonix will not attempt to create this file" self.logger.log(LogPriority.DEBUG, msg) success = False else: if not checkPerms("/etc/ssh/sshd_config", [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms("/etc/ssh/sshd_config", [0, 0, 420], self.logger, self.statechglogger, myid): success = False debug = "Unable to set permissions on " + \ "/etc/ssh/sshd_config\n" self.logger.log(LogPriority.DEBUG, debug) if self.editor3: if self.editor3.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor3.setEventID(myid) if not self.editor3.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/ssh/sshd_config file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor3.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/ssh/sshd_config file\n" self.logger.log(LogPriority.DEBUG, debug) os.chown("/etc/ssh/sshd_config", 0, 0) os.chmod("/etc/ssh/sshd_config", 420) resetsecon("/etc/ssh/sshd_config") return success
class 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 SecureIPV6(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 124 self.rulename = "SecureIPV6" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() datatype = "bool" key = "SECUREIPV6" instructions = '''To disable this rule set the value of SECUREIPV6 to \ False.''' default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ["NSA 2.5.3.2", "CCE 4269-7", "CCE 4291-1", "CCE 4313-3", "CCE 4198-8", "CCE 3842-2", "CCE 4221-8", "CCE 4137-6", "CCE 4159-0", "CCE 3895-0", "CCE 4287-9", "CCE 4058-4", "CCE 4128-5"] self.applicable = {'type': 'white', 'family': ['linux'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.iditerator = 0 # self.editor1: sysctl file editor # self.editor2: network file editor self.editor1, self.editor2 = "", "" self.ch = CommandHelper(self.logger) def report(self): try: self.detailedresults = "" if self.environ.getosfamily() == "linux": self.compliant = self.reportLinux() if self.environ.getosfamily() == "freebsd": self.compliant = self.reportFree() if self.environ.getosfamily() == "darwin": self.compliant = self.reportMac() elif self.environ.getosfamily() == "solaris": self.compliant = self.reportSol() except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant ############################################################################### def reportMac(self): '''check the values of the directives, specified in self.directives check that self.path (/private/etc/sysctl.conf) exists check that the permissions and ownership on file sysctl.conf are 0o600 and 0,0 :returns: compliant :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - added doc string; try/except; fixed perms for file sysctl.conf (should be 0o600; was 420) ''' compliant = True self.editor = "" self.path = "/private/etc/sysctl.conf" self.tmpPath = self.path + ".tmp" sysctl = "/usr/sbin/sysctl" self.directives = {"net.inet6.ip6.forwarding": "0", "net.inet6.ip6.maxifprefixes": "1", "net.inet6.ip6.maxifdefrouters": "1", "net.inet6.ip6.maxfrags": "0", "net.inet6.ip6.maxfragpackets": "0", "net.inet6.ip6.neighborgcthresh": "1024", "net.inet6.ip6.use_deprecated": "0", "net.inet6.ip6.hdrnestlimit": "0", "net.inet6.ip6.only_allow_rfc4193_prefixes": "1", "net.inet6.ip6.dad_count": "0", "net.inet6.icmp6.nodeinfo": "0", "net.inet6.icmp6.rediraccept": "1", "net.inet6.ip6.maxdynroutes": "0"} self.fixables = {} try: self.cmdhelper = CommandHelper(self.logger) for directive in self.directives: cmd = [sysctl, "-n", directive] if self.cmdhelper.executeCommand(cmd): output = self.cmdhelper.getOutputString().strip() if output != self.directives[directive]: self.detailedresults += "The value for " + directive + \ " is not " + self.directives[directive] + ", it's " + \ output + "\n" compliant = False self.fixables[directive] = self.directives[directive] else: error = self.cmdhelper.getErrorString() self.detailedresults += "There was an error running the " + \ "the command " + cmd + "\n" self.logger.log(LogPriority.DEBUG, error) self.fixables[directive] = self.directives[directive] compliant = False if not os.path.exists(self.path): compliant = False else: self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, self.directives, "present", "closedeq") if not self.editor.report(): compliant = False self.detailedresults += "Didn't find the correct contents " + \ "inside " + self.path + "\n" if not checkPerms(self.path, [0, 0, 0o600], self.logger): compliant = False except Exception: raise return compliant ############################################################################### def reportLinux(self): netwrkfile = "" ifacefile = "" sysctl = "/etc/sysctl.conf" compliant = True self.interface1 = {"IPV6_AUTOCONF": "no"} self.interface2 = {"IPV6_PRIVACY": "rfc3041"} self.sysctls = {"net.ipv6.conf.default.router_solicitations": "0", "net.ipv6.conf.default.accept_ra_rtr_pref": "0", "net.ipv6.conf.default.accept_ra_pinfo": "0", "net.ipv6.conf.default.accept_ra_defrtr": "0", "net.ipv6.conf.default.autoconf": "0", "net.ipv6.conf.default.dad_transmits": "0", "net.ipv6.conf.default.max_addresses": "1", "net.ipv6.conf.default.accept_ra": "0", "net.ipv6.conf.default.accept_redirects": "0"} self.ph = Pkghelper(self.logger, self.environ) # check compliancy of /etc/sysctl.conf file if not os.path.exists(sysctl): compliant = False self.detailedresults += sysctl + " file doesn't exist\n" else: tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): self.detailedresults += "/etc/sysctl file doesn't contain \ the correct contents\n" compliant = False if not checkPerms(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions for " + sysctl + \ "are incorrect\n" compliant = False # in addition to checking /etc/sysctl.conf contents we need to # also check sysctl compliancy using the sysctl command for key in self.sysctls: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() output = self.ch.getOutputString() errmsg = output + self.ch.getErrorString() if retcode != 0: if re.search("unknown key", errmsg): continue else: self.detailedresults += "Failed to get value of " + key + " key with sysctl command\n" self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: if output.strip() != key + " = " + self.sysctls[key]: compliant = False self.detailedresults += "sysctl output has incorrect value: " + \ output + "\n" # set the appropriate files based on the system if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" if not os.path.exists(ifacefile): ifacefile = "" netwrkfile = "/etc/sysconfig/network" if not os.path.exists(netwrkfile): netwrkfile = "" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network" if not os.path.exists(ifacefile): ifacefile = "" # Check contents of network file if netwrkfile: if os.path.exists(netwrkfile): if not checkPerms(netwrkfile, [0, 0, 0o644], self.logger): compliant = False tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface1, "present", "closedeq") if not self.editor2.report(): self.detailedresults += netwrkfile + " doesn't contain \ the correct contents\n" compliant = False else: self.detailedresults += netwrkfile + " doesn't exist\n" compliant = False if ifacefile: dirs = glob.glob(ifacefile + "*") for loc in dirs: contents = [] if re.search("^" + ifacefile + "ifcfg", loc): if not checkPerms(loc, [0, 0, 0o644], self.logger): compliant = False contents = readFile(loc, self.logger) if contents: for key in self.interface2: found = False iterator = 0 for line in contents: if re.search("^#", line) or re.match("^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip() == self.interface2[key]: found = True continue else: found = False break else: compliant = False self.detailedresults += loc + \ " file in bad format\n" if not found: self.detailedresults += "contents of " + \ loc + " file is wrong\n" compliant = False break else: continue else: compliant = False return compliant ############################################################################### def fix(self): try: if not self.ci.getcurrvalue(): return self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getosfamily() == "linux": self.rulesuccess = self.fixLinux() elif self.environ.getosfamily() == "freebsd": self.rulesuccess = self.fixFree() elif self.environ.getosfamily() == "darwin": self.rulesuccess = self.fixMac() elif self.environ.getosfamily() == "solaris": self.detailedresults = "Solaris systems require a manual fix" self.logger.log(LogPriority.INFO, self.detailedresults) except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess ############################################################################### def fixMac(self): '''use the sysctl command to write directives create the sysctl.conf file if needed set permissions and ownership of sysctl.conf file to 0o600 and 0,0 :returns: success :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - added doc string; try/except; fixed perms for file sysctl.conf (should be 0o600; was 420) ''' success = True created = False try: if self.fixables: sysctl = "/usr/sbin/sysctl" for directive in self.fixables: cmd = [sysctl, "-w", directive + "=" + self.fixables[directive]] if not self.cmdhelper.executeCommand(cmd): error = self.cmdhelper.getErrorString() self.detailedresults += "There was an error running " + \ "the command " + cmd + "\n" self.logger.log(LogPriority.DEBUG, error) success = False if not os.path.exists(self.path): if createFile(self.path, self.logger): created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) else: return False if not self.editor: self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, self.directives, "present", "closedeq") if not self.editor.report(): if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "KVEditor commit to " + \ self.path + " was not successful\n" else: success = False self.detailedresults += "KVEditor fix of " + self.path + \ " was not successful\n" else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "KVEditor commit to " + \ self.path + " was not successful\n" else: success = False self.detailedresults += "KVEditor fix of " + self.path + \ " was not successful\n" if not checkPerms(self.path, [0, 0, 0o600], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o600], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions" + \ " on " + self.path + "\n" success = False else: if not setPerms(self.path, [0, 0, 0o600], self.logger): self.detailedresults += "Could not set permissions" + \ " on " + self.path + "\n" success = False except Exception: raise return success ############################################################################### def fixLinux(self): universal = "#The following lines were added by stonix\n" debug = "" success = True ifacefile = "" netwrkfile = "" sysctl = "/etc/sysctl.conf" interface = {"IPV6_AUTOCONF": "no"} interface2 = {"IPV6_PRIVACY": "rfc3041"} # "IPV6_DEFAULTGW": self.gateway, # "IPV6ADDR":self.ipaddr} if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" netwrkfile = "/etc/sysconfig/network" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" created = False # fix sysctl / tuning kernel parameters # manually write key value pairs to /etc/sysctl.conf if not os.path.exists(sysctl): if createFile(sysctl, self.logger): created = True setPerms(sysctl, [0, 0, 0o644], self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sysctl} self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Could not create file " + sysctl + \ "\n" success = False if os.path.exists(sysctl): if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sysctl, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): if self.editor1.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event to record the file write if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor1.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) # permissions on file are incorrect if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not setPerms(sysctl, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ sysctl + "\n" success = False resetsecon(sysctl) # here we also check the output of the sysctl command for each key # to cover all bases for key in self.sysctls: if self.ch.executeCommand("/sbin/sysctl " + key): output = self.ch.getOutputString().strip() errmsg = output + self.ch.getErrorString() if re.search("unknown key", errmsg): continue if not re.search(self.sysctls[key] + "$", output): undovalue = output[-1] self.ch.executeCommand("/sbin/sysctl -q -e -w " + key + "=" + self.sysctls[key]) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set " + key + " = " + self.sysctls[key] + "\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -q -e -w " + key + "=" + undovalue event = {"eventtype": "commandstring", "command": command} self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Unable to get value for " + key + "\n" success = False # at the end do a print and ignore any key errors to ensure # the new values are read into the kernel self.ch.executeCommand("/sbin/sysctl -q -e -p") retcode2 = self.ch.getReturnCode() if retcode2 != 0: success = False self.detailedresults += "Failed to load new sysctl configuration from config file\n" errmsg2 = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg2) # correct the network file if it exists if netwrkfile: created = False if not os.path.exists(netwrkfile): if not createFile(netwrkfile, self.logger): success = False debug = "Unable to create " + netwrkfile + " file\n" self.logger.log(LogPriority.DEBUG, debug) else: created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": netwrkfile} self.statechglogger.recordchgevent(myid, event) tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface1, "present", "closedeq") self.editor2.report() if os.path.exists(netwrkfile): if not checkPerms(netwrkfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(netwrkfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False if self.editor2: if self.editor2.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): success = False elif not self.editor2.commit(): success = False os.chown(netwrkfile, 0, 0) os.chmod(netwrkfile, 0o644) resetsecon(netwrkfile) if ifacefile: if os.path.exists(ifacefile): dirs = glob.glob(ifacefile + "*") if dirs: for loc in dirs: interface2 = {"IPV6_PRIVACY": "rfc3041"} # "IPV6_DEFAULTGW": self.gateway, # "IPV6ADDR":self.ipaddr} interface3 = {"IPV6_PRIVACY": "rfc3041"} # "IPV6_DEFAULTGW": self.gateway, # "IPV6ADDR":self.ipaddr} found = False tempstring = "" if re.search('^' + ifacefile + 'ifcfg', loc): filename = loc tmpfile = filename + ".tmp" contents = readFile(filename, self.logger) if not checkPerms(filename, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(filename, [0, 0, 0o644], self.logger, self.statechglogger, myid): return False for key in interface2: found = False for line in contents: if re.search("^#", line) or \ re.match("^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip() == \ interface2[key]: if found: continue found = True else: contents.remove(line) if found: del interface3[key] for line in contents: tempstring += line tempstring += universal for key in interface3: tempstring += key + "=" + interface3[key] + \ "\n" if not writeFile(tmpfile, tempstring, self.logger): return False self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': filename} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(filename, tmpfile, myid) os.rename(tmpfile, filename) os.chown(filename, 0, 0) os.chmod(filename, 0o644) resetsecon(filename) elif not os.path.exists(ifacefile) and ifacefile != "": # will not attempt to create the interface files self.detailedresults += "Interface directory which holds interface \ files, doesn't exist. Stonix will not attempt to make this \ directory or the files contained therein." success = False return success
def fixLinux(self): ''' @change: dkennel removed extraneous arg from setperms call on 864 ''' universal = "#The following lines were added by stonix\n" debug = "" success = True ifacefile = "" netwrkfile = "" sysctl = "/etc/sysctl.conf" blacklistfile = "/etc/modprobe.d/stonix-blacklist.conf" # STIG portion, correct netconfig file if self.ph.manager == "apt-get": nfspkg = "nfs-common" else: nfspkg = "nfs-utils.x86_64" # if package not installed, no need to configure it if self.ph.check(nfspkg): if os.path.exists("/etc/netconfig"): filestring = "" # we want to make sure the following two lines don't # appear in the netconfig file item1 = "udp6 tpi_clts v inet6 udp - -" item2 = "tcp6 tpi_cots_ord v inet6 tcp - -" contents = readFile("/etc/netconfig", self.logger) for line in contents: templine = re.sub("\s+", " ", line.strip()) # if we find the lines, skip them thus leaving them out of # of the rewrite if re.search(item1, templine) or re.search( item2, templine): continue else: filestring += line tmpfile = "/etc/netconfig.tmp" if not writeFile(tmpfile, filestring, self.logger): success = False else: # record event, rename file, set perms self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": "/etc/netconfig"} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( "/etc/netconfig", tmpfile, myid) os.rename(tmpfile, "/etc/netconfig") os.chown("/etc/netconfig", 0, 0) os.chmod("/etc/netconfig", 420) resetsecon("/etc/netconfig") # remove any ipv6 addresses from /etc/hosts file if os.path.exists("/etc/hosts"): contents = readFile("/etc/hosts", self.logger) tempstring = "" tmpfile = "/etc/hosts.tmp" for line in contents: if re.search("^#", line) or re.match("^\s*$", line): tempstring += line continue elif re.search(":", line): tempstring += "#" + line else: tempstring += line if writeFile(tmpfile, tempstring, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": "/etc/hosts"} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange("/etc/hosts", tmpfile, myid) os.rename(tmpfile, "/etc/hosts") os.chown("/etc/hosts", 0, 0) os.chmod("/etc/hosts", 420) resetsecon("/etc/hosts") else: success = False debug = "Unable to write to file /etc/hosts\n" self.logger.log(LogPriority.DEBUG, debug) # fix sysctl / tuning kernel parameters # manually write key value pairs to /etc/sysctl.conf created = False if not os.path.exists(sysctl): if createFile(sysctl, self.logger): created = True setPerms(sysctl, [0, 0, 0o644], self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sysctl} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to create " + sysctl + "\n" self.logger.log(LogPriority.DEBUG, debug) if os.path.exists(sysctl): if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sysctl, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False tmpfile = sysctl + ".tmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, self.sysctls, "present", "openeq") if not self.editor1.report(): if self.editor1.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor1.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/sysctl.conf file\n" self.logger.log(LogPriority.DEBUG, debug) if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not setPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False resetsecon(sysctl) # here we also check the output of the sysctl command for each key # to cover all bases for key in self.sysctls: if self.ch.executeCommand("/sbin/sysctl " + key): output = self.ch.getOutputString().strip() errmsg = output + self.ch.getErrorString() if re.search("unknown key", errmsg): continue if not re.search(self.sysctls[key] + "$", output): undovalue = output[-1] self.ch.executeCommand("/sbin/sysctl -q -e -w " + key + "=" + self.sysctls[key]) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set " + key + " = " + self.sysctls[ key] + "\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -q -e -w " + key + "=" + undovalue event = { "eventtype": "commandstring", "command": command } self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Unable to get value for " + key + "\n" success = False # at the end do a print and ignore any key errors to ensure # the new values are read into the kernel self.ch.executeCommand("/sbin/sysctl -q -e -p") retcode2 = self.ch.getReturnCode() if retcode2 != 0: success = False self.detailedresults += "Failed to load new sysctl configuration from config file\n" errmsg2 = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg2) # We never found the correct contents in any of the modprobe.d files # so we're going to created the stonix-blacklist file # this file is used in other rules if not self.modprobeOK: created = False tmpfile = blacklistfile + ".tmp" modprobekveditor = KVEditorStonix(self.statechglogger, self.logger, "conf", blacklistfile, tmpfile, self.modprobes, "notpresent", "space") if not os.path.exists(blacklistfile): # create the file and record the event as file creation if createFile(blacklistfile, self.logger): created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = { "eventtype": "creation", "filepath": blacklistfile } self.statechglogger.recordchgevent(myid, event) if os.path.exists(blacklistfile): if not modprobekveditor.report(): if not modprobekveditor.fix(): success = False self.detailedresults += "Unable to correct contents in " + \ blacklistfile + "\n" else: # if the file was created, then we already recorded an event # for that, so this step would get skipped if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) modprobekveditor.setEventID(myid) if not modprobekveditor.commit(): success = False self.detailedresults += "Unable to correct contents in " + \ blacklistfile + "\n" # fix ifcfg (interface) files if self.ph.manager == "yum": ifacefile = "/etc/sysconfig/network-scripts/" netwrkfile = "/etc/sysconfig/network" elif self.ph.manager == "zypper": ifacefile = "/etc/sysconfig/network/" if ifacefile: if os.path.exists(ifacefile): dirs = glob.glob(ifacefile + "*") if dirs: for loc in dirs: interface = {"IPV6INIT": "no", "NETWORKING_IPV6": "no"} interface2 = { "IPV6INIT": "no", "NETWORKING_IPV6": "no" } found = False tempstring = "" if re.search('^' + ifacefile + 'ifcfg', loc): filename = loc tmpfile = filename + ".tmp" contents = readFile(filename, self.logger) if not checkPerms(filename, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(filename, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + \ filename + "\n" self.logger.log(LogPriority.DEBUG, debug) success = False for key in interface: found = False for line in contents: if re.search("^#", line) or \ re.match("^\s*$", line): continue if re.search("^" + key, line): if re.search("=", line): temp = line.split("=") if temp[1].strip( ) == interface[key]: if found: continue found = True else: contents.remove(line) if found: del interface2[key] for line in contents: tempstring += line tempstring += universal for key in interface2: tempstring += key + "=" + interface2[key] + \ "\n" if not writeFile(tmpfile, tempstring, self.logger): success = False debug = "Unable to write to file " + loc + "\n" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': filename} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( filename, tmpfile, myid) os.rename(tmpfile, filename) os.chown(filename, 0, 0) os.chmod(filename, 420) resetsecon(filename) elif not os.path.exists(ifacefile) and ifacefile != "": # will not attempt to create the interface files debug = "interface directory which holds interface \ files, doesn't exist, stonix will not attempt to make this \ directory or the files contained therein" success = False self.logger.log(LogPriority.DEBUG, debug) # fix network file if it exists if netwrkfile: if not os.path.exists(netwrkfile): if not createFile(netwrkfile, self.logger): debug = "Unable to create " + netwrkfile + "file\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: if not checkPerms(netwrkfile, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(netwrkfile, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + \ netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) success = False tmpfile = netwrkfile + ".tmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", netwrkfile, tmpfile, self.interface, "present", "closedeq") if not self.editor2.report(): self.detailedresults += netwrkfile + " doesn't contain \ the correct contents\n" if self.editor2: if self.editor2.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for " + netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor2.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for " + netwrkfile + "\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(netwrkfile, 0, 0) os.chmod(netwrkfile, 420) resetsecon(netwrkfile) # fix sshd_config file for apt-get systems if ssh is installed if self.ph.manager == "apt-get": if not os.path.exists("/etc/ssh/sshd_config"): msg = "/etc/ssh/ssd_config doesn\'t exist. This could mean ssh \ is not installed or the file has been inadvertantly deleted. Due to the \ complexity of this file stonix will not attempt to create this file" self.logger.log(LogPriority.DEBUG, msg) success = False else: if not checkPerms("/etc/ssh/sshd_config", [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms("/etc/ssh/sshd_config", [0, 0, 420], self.logger, self.statechglogger, myid): success = False debug = "Unable to set permissions on " + \ "/etc/ssh/sshd_config\n" self.logger.log(LogPriority.DEBUG, debug) if self.editor3: if self.editor3.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor3.setEventID(myid) if not self.editor3.fix(): success = False debug = "Unable to complete kveditor fix method" + \ "for /etc/ssh/sshd_config file\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor3.commit(): success = False debug = "Unable to complete kveditor commit " + \ "method for /etc/ssh/sshd_config file\n" self.logger.log(LogPriority.DEBUG, debug) os.chown("/etc/ssh/sshd_config", 0, 0) os.chmod("/etc/ssh/sshd_config", 420) resetsecon("/etc/ssh/sshd_config") return success
def fixSSHFile(self, sshfile, directives): """ apply configuration options to config files :param: sshfile - filepath string :param: directives - dictionary of desired directives :return: compliant :rtype: bool """ success = True debug = "" directives = dict(directives) tpath = sshfile + ".tmp" created = False if not os.path.exists(sshfile): createFile(sshfile, self.logger) created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sshfile} self.statechglogger.recordchgevent(myid, event) if os.path.exists(sshfile): if re.search("Ubuntu", self.environ.getostype()): if sshfile == "/etc/ssh/sshd_config": del (directives["GSSAPIAuthentication"]) del (directives["KerberosAuthentication"]) elif sshfile == "/etc/ssh/ssh_config": del (directives["GSSAPIAuthentication"]) elif self.environ.getostype() == "Mac OS X" and self.mac_piv_auth_CI.getcurrvalue(): if sshfile == "/private/etc/ssh/sshd_config": directives["ChallengeResponseAuthentication"] = "no" directives["PasswordAuthentication"] = "no" editor = KVEditorStonix(self.statechglogger, self.logger, "conf", sshfile, tpath, directives, "present", "space") editor.report() if re.search("Ubuntu", self.environ.getostype()): if sshfile == "/etc/ssh/sshd_config": directives = {"GSSAPIAuthentication": "", "KerberosAuthentication": ""} elif sshfile == "/etc/ssh/ssh_config": directives = {"GSSAPIAuthentication": ""} editor.setIntent("notpresent") editor.setData(directives) editor.report() if not checkPerms(sshfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sshfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False else: if not setPerms(sshfile, [0, 0, 0o644], self.logger): success = False if editor.fixables or editor.removeables: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) editor.setEventID(myid) if editor.fix(): if editor.commit(): os.chown(sshfile, 0, 0) os.chmod(sshfile, 0o644) resetsecon(sshfile) else: self.detailedresults += "Unable to correct contents " + \ "in " + sshfile + "\n" debug = "kveditor1 commit did not run successfully" self.logger.log(LogPriority.DEBUG, debug) success = False else: self.detailedresults += "Unable to correct contents " + \ "in " + sshfile + "\n" debug = "kveditor1 fix did not run successfully" self.logger.log(LogPriority.DEBUG, debug) success = False return success
class CheckRootPath(Rule): """ The CheckRootPath rule checks the root user's PATH environment variable, ensuring that it is set to the vendor default and that there are no user or world-writable files or directories in any of the path directories. """ def __init__(self, config, environ, logger, statechglogger): """ private method to initialize the module :param config: configuration object instance :param environ: environment object instance :param logger: logdispatcher object instance :param statechglogger: statechglogger object instance """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 44 self.rulename = 'CheckRootPath' self.formatDetailedResults("initialize") self.compliant = False self.mandatory = True self.sethelptext() self.rootrequired = True self.guidance = ['NSA RHEL 2.3.4.1, 2.3.4.1.1, 2.3.4.1.2', "CCE-RHEL7-CCE-TBD 2.4.1.1.7"] self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} # Configuration item instantiation datatype = "bool" key = "CHECKROOTPATH" instructions = "To disable this rule, set the value of " + \ "CHECKROOTPATH to false." default = True self.ci = self.initCi(datatype, key, instructions, default) if self.isapplicable(): myos = self.environ.getostype().lower() self.myos = myos if re.search("os x", myos): defaultPath = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" elif re.search("opensuse", myos): defaultPath = "/sbin:/usr/sbin:/usr/local/sbin:/root/bin:" + \ "/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games" elif re.search("fedora|centos|red hat", myos): defaultPath = "/usr/local/sbin:/usr/local/bin:/sbin:/bin:" + \ "/usr/sbin:/usr/bin:/root/bin" else: defaultPath = "/usr/local/sbin:/usr/local/bin:/sbin:/bin:" + \ "/usr/sbin:/usr/bin" self.defaultPath = defaultPath def report(self): """The report method examines the current configuration and determines whether or not it is correct. If the config is correct then the self.compliant, self.detailedresults and self.currstate properties are updated to reflect the system status. self.rulesuccess will be updated if the rule does not succeed. :return: self.compliant - True if compliant; False if not :rtype: bool """ try: compliant = True self.detailedresults = "" self.vendorDefault = True wwList = [] defaultPath = self.defaultPath path = os.environ['PATH'] if not re.search(defaultPath, path): compliant = False self.vendorDefault = False self.detailedresults += "root's PATH variable is not set " + \ "to the vendor default\n" exPaths = path.split(":") self.logger.log(LogPriority.DEBUG, "PATH entries: " + str(exPaths)) for exPath in exPaths: if not os.path.exists(exPath): continue pathEntries = os.listdir(exPath) for entry in pathEntries: absPath = exPath + "/" + entry if not os.path.exists(absPath): continue entryStat = os.stat(absPath) userMode = oct(entryStat.st_mode)[-1] if userMode == "7" or userMode == "6" or userMode == "2": compliant = False wwList.append(absPath) self.detailedresults += "World-writeable entry " + \ "found at: " + absPath + "\n" self.compliant = compliant except (OSError): 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 fix(self): """set root's default PATH environment variable to vendor default :return: self.rulesucces - True if fix succeeds; False if not :rtype: bool """ try: self.detailedresults = "" if not self.ci.getcurrvalue(): return success = True if not self.vendorDefault: os.environ['PATH'] = self.defaultPath if re.search("darwin", self.myos): root = "/var/root/" else: root = "/root/" checkFiles = [root + ".profile", root + ".bashrc"] for checkFile in checkFiles: if not os.path.exists(checkFile): open(checkFile, "w") tmppath = checkFile + ".tmp" data = {"PATH": self.defaultPath} self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", checkFile, tmppath, data, "present", "closedeq") if not self.editor.report(): if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "Failed to commit " + \ "changes to " + checkFile + "\n" else: success = False self.detailedresults += "Error fixing file " + \ checkFile + "\n" self.rulesuccess = success except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class SecureNFS(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.rulenumber = 39 self.rulename = "SecureNFS" self.formatDetailedResults("initialize") self.sethelptext() self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.guidance = [ "NSA(3.13.4)", "cce-4241-6", "cce-4465-1", "cce-4559-1", "cce-4015-4", "cce-3667-3", "cce-4310-9", "cce-4438-8", "cce-3579-0" ] # Configuration item instantiation datatype = 'bool' key = 'SECURENFS' instructions = "To disable this rule set the value of SECURENFS to False." default = True self.ci = self.initCi(datatype, key, instructions, default) def report(self): """ Run report actions for SecureNFS :return: self.compliant :rtype: bool """ self.detailedresults = "" self.compliant = True nfsexports = True try: if self.environ.getosfamily() == "linux": self.ph = Pkghelper(self.logger, self.environ) # secure NFS service self.sh = ServiceHelper(self.environ, self.logger) if self.environ.getostype() == "Mac OS X": nfsfile = "/etc/nfs.conf" data1 = { "nfs.lockd.port": "", "nfs.lockd.tcp": "1", "nfs.lockd.udp": "1" } if not self.sh.auditService('/System/Library/LaunchDaemons/' + 'com.apple.nfsd.plist', serviceTarget='com.apple.nfsd'): self.compliant = True self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant elif self.ph.manager in ("yum", "zypper", "dnf"): nfsfile = "/etc/sysconfig/nfs" data1 = { "LOCKD_TCPPORT": "32803", "LOCKD_UDPPORT": "32769", "MOUNTD_PORT": "892", "RQUOTAD_PORT": "875", "STATD_PORT": "662", "STATD_OUTGOING_PORT": "2020" } if self.ph.manager == "zypper": nfspackage = "nfs-kernel-server" elif self.ph.manager == "yum" or self.ph.manager == "dnf": nfspackage = "nfs-utils" elif self.ph.manager == "apt-get": nfsfile = "/etc/services" data1 = { "rpc.lockd": ["32803/tcp", "32769/udp"], "rpc.mountd": ["892/tcp", "892/udp"], "rpc.quotad": ["875/tcp", "875/udp"], "rpc.statd": ["662/tcp", "662/udp"], "rpc.statd-bc": ["2020/tcp", "2020/udp"] } nfspackage = "nfs-kernel-server" if self.environ.getostype() != "Mac OS X": if self.ph.manager in ("apt-get", "zypper", "yum", "dnf"): if not self.ph.check(nfspackage): self.compliant = True self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant if os.path.exists(nfsfile): nfstemp = nfsfile + ".stonixtmp" eqtype = "" eqtypestr = "" if self.environ.getostype() == "Mac OS X": eqtype = "openeq" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", nfsfile, nfstemp, data1, "present", eqtype) elif self.ph.manager in ("yum", "zypper", "dnf"): eqtype = "closedeq" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", nfsfile, nfstemp, data1, "present", eqtype) elif self.ph.manager == "apt-get": eqtype = "space" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", nfsfile, nfstemp, data1, "present", eqtype) if eqtype == "openeq": eqtypestr = " = " elif eqtype == "closedeq": eqtypestr = "=" elif eqtype == "space": eqtypestr = " " if not self.editor1.report(): if self.editor1.fixables: missingconfiglines = [] for item in self.editor1.fixables: if isinstance(data1[item], list): for li in data1[item]: missingconfiglines.append( str(item) + eqtypestr + str(li)) else: missingconfiglines.append( str(item) + eqtypestr + str(data1[item])) self.detailedresults += "\nThe following configuration lines are missing from " + str( nfsfile) + ":\n" + "\n".join(missingconfiglines) else: self.detailedresults += "\nOne or more configuration lines are missing from " + str( nfsfile) self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = False if not checkPerms(nfsfile, [0, 0, 420], self.logger): self.detailedresults += "\nPermissions aren't correct on " \ + nfsfile self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = False else: self.detailedresults += "\n" + nfsfile + " does not exist" self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = False # secure NFS exports export = "/etc/exports" if os.path.exists(export): if not self.checkNFSexports(): nfsexports = False extemp = export + ".stonixtmp" data2 = { "all_squash": "", "no_root_squash": "", "insecure_locks": "" } self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", export, extemp, data2, "notpresent", "space") if not self.editor2.report(): incorrectconfiglines = [] if self.editor2.fixables: for item in self.editor2.fixables: # no fixables being generated for items not compliant with data2 and the "notpresent" directive incorrectconfiglines.append(str(item)) self.detailedresults += "\nThe following configuration options are insecure, in file " + str( export) + ":\n" + "\n".join(incorrectconfiglines) self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = False if not checkPerms(export, [0, 0, 420], self.logger): self.detailedresults += "\n" + export + " file doesn't " + \ "have the correct permissions" self.logger.log(LogPriority.DEBUG, self.detailedresults) self.compliant = False if not nfsexports: self.compliant = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def checkNFSexports(self): """ check the NFS export lines in the exports configuration file :return: retval :rtype: bool """ retval = True filename = "/etc/exports" directory = "/etc/exports.d" fileslist = [] try: if os.path.exists(filename): f = open(filename, "r") contentlines = f.readlines() f.close() if not self.checkNFScontents(contentlines, filename): retval = False if os.path.exists(directory): fileslist = [ f for f in listdir(directory) if isfile(join(directory, f)) ] if fileslist: for filename in fileslist: f = open(filename, "r") contentlines = f.readlines() f.close() if not self.checkNFScontents(contentlines, str(f)): retval = False else: self.logger.log(LogPriority.DEBUG, "No NFS exports to check") except Exception: raise return retval def checkNFScontents(self, contentlines, filename=""): """ check given list of contentlines for required nfs export formatting :param contentlines: :param filename: (Default value = "") :return: retval :rtype: bool """ retval = True if not filename: filename = "(file name not specified)" if not isinstance(filename, str): filename = "(file name not specified)" if not isinstance(contentlines, list): self.logger.log(LogPriority.DEBUG, "Parameter contentlines must be of type: list!") if not contentlines: self.logger.log(LogPriority.DEBUG, "Parameter contentlines was empty!") try: if contentlines: for line in contentlines: if re.search('^#', line): continue elif re.search('^\/', line): # search for overly broad exports broadexports = [ '^\/\w+.*\s*\d{2,3}\.\d{2,3}\.0\.0\/16\b', '^\/\w+.*\s*\d{1,3}\.0\.0\.0\/8\b', '^\/\w+.*\s*.*\*.*' ] for be in broadexports: if re.search(be, line): retval = False self.detailedresults += "The nfs export line:\n" + str( line ) + "\nin " + str( filename ) + " contains an export that is overly broad." else: if re.search('^([^ !$`&*()+]|(\\[ !$`&*()+]))+\s*', line): sline = line.split() if len(sline) < 2: retval = False self.detailedresults += "\nThe export line:\n" + str( '\"' + line.strip() + '\"') + " in " + str( filename ) + " lacks a host specification." if not retval: self.detailedresults += "\n\nThere is no automatic fix action for host exports. Please ensure that each NFS export in " + str( filename ) + " has a host specification.\nSee man exports for help.\n" except Exception: raise return retval def fix(self): """ Run fix actions for SecureNFS :return: self.rulesuccess :rtype: bool @author: dwalker @change: Breen Malmberg - 4/26/2016 - changed location of defaults variables in method; added detailedresults message if fix run while CI disabled; added formatdetailedresults update if fix called when CI disabled; changed return value to always be self.rulesuccess; updated self.rulesuccess based on success variable as well @change: Breen Malmberg - 7/11/2017 - added another service check on mac os x; no files will be created on mac if the service is not enabled """ self.logdispatch.log(LogPriority.DEBUG, "Entering SecureNFS.fix()...") success = True changed1, changed2 = False, False installed = False self.detailedresults = "" try: if not self.ci.getcurrvalue(): self.detailedresults += "\nThe CI for this rule was not enabled. Nothing has been done." success = True self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.DEBUG, "Exiting SecureNFS.fix()...") return success # Clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getostype() == "Mac OS X": nfsservice = "nfsd" nfsfile = "/etc/nfs.conf" data1 = { "nfs.lockd.port": "", "nfs.lockd.tcp": "1", "nfs.lockd.udp": "1" } elif self.ph.manager in ("yum", "zypper", "dnf"): nfsfile = "/etc/sysconfig/nfs" data1 = { "LOCKD_TCPPORT": "32803", "LOCKD_UDPPORT": "32769", "MOUNTD_PORT": "892", "RQUOTAD_PORT": "875", "STATD_PORT": "662", "STATD_OUTGOING_PORT": "2020" } nfsservice = "nfs" if self.ph.manager == "zypper": nfspackage = "nfs-kernel-server" elif self.ph.manager == "yum" or self.ph.manager == "dnf": nfspackage = "nfs-utils" elif self.ph.manager == "apt-get": nfsservice = "nfs-kernel-server" nfspackage = "nfs-kernel-server" nfsfile = "/etc/services" data1 = { "rpc.lockd": ["32803/tcp", "32769/udp"], "rpc.mountd": ["892/tcp", "892/udp"], "rpc.quotad": ["875/tcp", "875/udp"], "rpc.statd": ["662/tcp", "662/udp"], "rpc.statd-bc": ["2020/tcp", "2020/udp"] } if self.environ.getostype() != "Mac OS X": if self.ph.manager in ("apt-get", "zypper"): if not self.ph.check(nfspackage): success = True self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return success if not os.path.exists(nfsfile): if createFile(nfsfile, self.logger): nfstemp = nfsfile + ".stonixtmp" if self.environ.getostype() == "Mac OS X": if not self.sh.auditService( '/System/Library/LaunchDaemons/com.apple.nfsd.plist', serviceTarget='com.apple.nfsd'): success = True self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return success self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", nfsfile, nfstemp, data1, "present", "openeq") elif self.ph.manager in ("yum", "zypper", "dnf"): self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", nfsfile, nfstemp, data1, "present", "closedeq") elif self.ph.manager == "apt-get": self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "conf", nfsfile, nfstemp, data1, "present", "space") if not self.editor1.report(): if not self.editor1.fix(): success = False debug = "fix for editor1 failed" self.logger.log(LogPriority.DEBUG, debug) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.commit(): success = False debug = "commit for editor1 failed" self.logger.log(LogPriority.DEBUG, debug) else: changed1 = True if not checkPerms(nfsfile, [0, 0, 420], self.logger): if not setPerms(nfsfile, [0, 0, 420], self.logger, self.statechglogger): success = False debug = "Unable to set permissions on " + nfsfile self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": nfsfile} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to create " + nfsfile + " file" self.logger.log(LogPriority.DEBUG, debug) else: if self.editor1.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.fix(): success = False debug = "editor1 fix failed" self.logger.log(LogPriority.DEBUG, debug) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if not self.editor1.commit(): success = False debug = "editor1 commit failed" self.logger.log(LogPriority.DEBUG, debug) else: changed1 = True if not checkPerms(nfsfile, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(nfsfile, [0, 0, 420], self.logger, self.statechglogger, myid): debug = "Unable to set permissions on " + nfsfile self.logger.log(LogPriority.DEBUG, debug) export = "/etc/exports" if not os.path.exists(export): # mac os x will automatically enable the nfs # service and related ports if the file /etc/exports # is created if self.environ.getostype() == "Mac OS X": if not self.sh.auditService( '/System/Library/LaunchDaemons/com.apple.nfsd.plist', serviceTarget='com.apple.nfsd'): success = True self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return success if createFile(export, self.logger): extemp = export + ".stonixtmp" data2 = { "all_squash": "", "no_root_squash": "", "insecure_locks": "" } self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", export, extemp, data2, "notpresent", "space") if not self.editor2.report(): if not self.editor2.fix(): success = False debug = "fix for editor2 failed" self.logger.log(LogPriority.DEBUG, debug) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.commit(): success = False debug = "commit for editor2 failed" self.logger.log(LogPriority.DEBUG, debug) else: changed2 = True if not checkPerms(export, [0, 0, 420], self.logger): if not setPerms(export, [0, 0, 420], self.logger, self.statechglogger): success = False debug = "Unable to set permissions on " + export self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": export} self.statechglogger.recordchgevent(myid, event) else: if installed: extemp = export + ".stonixtmp" data2 = { "all_squash": "", "no_root_squash": "", "insecure_locks": "" } self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", export, extemp, data2, "notpresent", "space") self.editor2.report() if self.editor2.removeables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if not self.editor2.fix(): success = False debug = "editor2 fix failed" self.logger.log(LogPriority.DEBUG, debug) else: if not self.editor2.commit(): success = False debug = "editor2 commit failed" self.logger.log(LogPriority.DEBUG, debug) else: changed2 = True if not checkPerms(export, [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(export, [0, 0, 420], self.logger, self.statechglogger, myid): success = False debug = "Unable to set permissions on " + export self.logger.log(LogPriority.DEBUG, debug) if changed1 or changed2: ## CHANGE (Breen Malmberg) 1/23/2017 # The self.sh.reloadservice() call, for SHlaunchd, will start # the service even if it is not already running. # We don't want to start THIS service if it is not # already running/enabled! # We also don't want to change this functionality at the # SHlaunchd class-level, because there may be other # instances in which we want it to do a stop and start # (aka a full reload), so this decision should be made at # the rule-level. ## if self.sh.isRunning(nfsservice, serviceTarget=nfsservice): self.sh.reloadService(nfsservice, serviceTarget=nfsservice) self.logdispatch.log(LogPriority.DEBUG, "Exiting SecureNFS.fix()...") 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", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return success
class RestrictMounting(Rule): '''Class help text''' def __init__(self, config, enviro, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 112 self.rulename = "RestrictMounting" self.formatDetailedResults("initialize") self.mandatory = False self.sethelptext() # Configuration item instantiation datatype1 = "bool" key1 = "RESTRICTCONSOLEACCESS" instructions1 = "To restrict console device access, set " + \ "RESTRICTCONSOLEACCESS to True." default1 = False self.consoleCi = self.initCi(datatype1, key1, instructions1, default1) datatype2 = "bool" key2 = "DISABLEAUTOFS" instructions2 = "To disable dynamic NFS mounting through the " + \ "autofs service, set DISABLEAUTOFS to True." default2 = False self.autofsCi = self.initCi(datatype2, key2, instructions2, default2) datatype3 = "bool" key3 = "DISABLEGNOMEAUTOMOUNT" instructions3 = "To disable the GNOME desktop environment from " + \ "automounting devices and removable media, set " + \ "DISABLEGNOMEAUTOMOUNT to True." default3 = False self.gnomeCi = self.initCi(datatype3, key3, instructions3, default3) self.guidance = ["NSA 2.2.2.1", "NSA 2.2.2.3", "NSA 2.2.2.4", "CCE 3685-5", "CCE 4072-5", "CCE 4231-7", "CCE-RHEL7-CCE-TBD 2.2.2.6"] self.applicable = {"type": "white", "family": ["linux"]} self.iditerator = 0 self.gsettings = "/usr/bin/gsettings" self.gconftool = "/usr/bin/gconftool-2" self.dbuslaunch = "/usr/bin/dbus-launch" def report(self): ''' ''' self.automountMedia = True self.automountDrives = True self.sec_console_perms1 = "/etc/security/console.perms.d/50-default.perms" self.sec_console_perms2 = "/etc/security/console.perms" self.console_perms_temppath = self.sec_console_perms2 + ".stonixtmp" self.data = {"<console>": "tty[0-9][0-9]* vc/[0-9][0-9]* :0\.[0-9] :0", "<xconsole>": "0\.[0-9] :0"} self.autofspkg = "autofs" self.autofssvc = "autofs" self.ph = Pkghelper(self.logdispatch, self.environ) self.sh = ServiceHelper(self.environ, self.logdispatch) self.ch = CommandHelper(self.logdispatch) self.compliant = True self.detailedresults = "" try: if os.path.exists(self.sec_console_perms1): current_config = readFile(self.sec_console_perms1, self.logger) for line in current_config: if re.search("^<[x]?console>", line, re.M): self.compliant = False self.detailedresults += self.sec_console_perms1 + " contains unrestricted device access\n" break if os.path.exists(self.sec_console_perms2): self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sec_console_perms2, self.console_perms_temppath, self.data, "present", "closedeq") if not self.editor2.report(): self.compliant = False self.detailedresults += self.sec_console_perms2 + " does not contain the correct values\n" if self.ph.check(self.autofspkg): if self.sh.auditService(self.autofssvc, _="_"): self.compliant = False self.detailedresults += "autofs is installed and enabled\n" if os.path.exists(self.gsettings): automountOff = False autorunNever = False cmd = [self.gsettings, "get", "org.gnome.desktop.media-handling", "automount"] self.ch.executeCommand(cmd) if re.search("false", self.ch.getOutputString()): automountOff = True cmd = [self.gsettings, "get", "org.gnome.desktop.media-handling", "autorun-never"] self.ch.executeCommand(cmd) if re.search("true", self.ch.getOutputString()): autorunNever = True debug = "autorun-never is enabled" self.logger.log(LogPriority.DEBUG, debug) self.automountOff = automountOff self.autorunNever = autorunNever if not automountOff or not autorunNever: self.compliant = False self.detailedresults += "GNOME automounting is enabled\n" # check for gnome automounting if os.path.exists(self.gconftool): cmd = [self.gconftool, "-R", "/desktop/gnome/volume_manager"] if os.path.exists("/desktop/gnome/volume_manager"): self.ch.executeCommand(cmd) retcode = self.ch.getReturnCode() if retcode != 0: self.compliant = False self.detailedresults += "\nFailed to read gnome volume manager config" output = self.ch.getOutputString() if re.search("automount_media.*false", output): self.automountMedia = False if re.search("automount_drives.*false", output): self.automountDrives = False else: self.automountMedia = False self.automountDrives = False mylist = [self.automountMedia, self.automountDrives] if any(mylist): self.compliant = False self.detailedresults += "GNOME automounting is enabled\n" # reset these directories to be owned by their respective users dirs = '' if os.path.exists('/run/user'): dirs = os.listdir('/run/user') if dirs: for d in dirs: # check if the directory is an integer representing a uid if re.search('^([+-]?[1-9]\d*|0)$', d, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Found UID directory") try: os.chown('/run/user/' + d + '/dconf/user', int(d), int(d)) except Exception as errmsg: self.logger.log(LogPriority.DEBUG, str(errmsg)) continue else: self.logger.log(LogPriority.DEBUG, "no directories in /run/user") except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): ''' ''' self.detailedresults = "" self.iditerator = 0 self.rulesuccess = True success = True consoleaccess = self.consoleCi.getcurrvalue() autofs = self.autofsCi.getcurrvalue() gnomeautomount = self.gnomeCi.getcurrvalue() mylist = [consoleaccess, autofs, gnomeautomount] try: # if none of the CIs are enabled, skip fix if not any(mylist): self.detailedresults += "\nNone of the CI's were enabled. Nothing was done." self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess # clear event list data eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) # if restrict console access CI is enabled, restrict console access if self.consoleCi.getcurrvalue(): if os.path.exists(self.sec_console_perms1): tmpfile = self.sec_console_perms1 + ".stonixtmp" defaultPerms = open(self.sec_console_perms1, "r").read() defaultPerms = re.sub("(<[x]?console>)", r"#\1", defaultPerms) if writeFile(tmpfile, defaultPerms, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.sec_console_perms1} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(self.sec_console_perms1, tmpfile, myid) os.rename(tmpfile, self.sec_console_perms1) resetsecon(self.sec_console_perms1) else: success = False self.detailedresults += "Problem writing new contents to " + \ "temporary file" if os.path.exists(self.sec_console_perms2): self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sec_console_perms2, self.console_perms_temppath, self.data, "present", "closedeq") self.editor.report() if self.editor.fixables: if self.editor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.commit(): debug = self.sec_console_perms2 + "'s contents have been " \ + "corrected\n" self.logger.log(LogPriority.DEBUG, debug) resetsecon(self.sec_console_perms2) else: debug = "kveditor commit not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.sec_console_perms2 + \ " properties could not be set\n" else: debug = "kveditor fix not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.detailedresults += self.sec_console_perms2 + \ " properties could not be set\n" # if autofs CI is enabled, disable autofs if self.autofsCi.getcurrvalue(): if self.ph.check(self.autofspkg) and \ self.sh.auditService(self.autofssvc, _="_"): if self.sh.disableService(self.autofssvc, _="_"): debug = "autofs service successfully disabled\n" self.logger.log(LogPriority.DEBUG, debug) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "servicehelper", "servicename": self.autofssvc, "startstate": "enabled", "endstate": "disabled"} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to disable autofs service\n" self.logger.log(LogPriority.DEBUG, debug) returnCode = 0 if self.gnomeCi.getcurrvalue(): if os.path.exists(self.gsettings): # gsettings requires a D-Bus session bus in order to make # any changes. This is because the dconf daemon must be # activated using D-Bus. if not os.path.exists(self.dbuslaunch): self.ph.install("dbus-x11") if os.path.exists(self.dbuslaunch): if not self.automountOff: cmd = [self.dbuslaunch, self.gsettings, "set", "org.gnome.desktop.media-handling", "automount", "false"] self.ch.executeCommand(cmd) returnCode = self.ch.getReturnCode() if not returnCode: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": ["dbus-launch", "gsettings", "set", "org.gnome.desktop.media-handling", "automount", "true"]} self.statechglogger.recordchgevent(myid, event) if not self.autorunNever: cmd = [self.dbuslaunch, "gsettings", "set", "org.gnome.desktop.media-handling", "autorun-never", "true"] self.ch.executeCommand(cmd) returnCode += self.ch.getReturnCode() if not self.ch.getReturnCode(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": [self.dbuslaunch, self.gsettings, "set", "org.gnome.desktop.media-handling", "autorun-never", "false"]} self.statechglogger.recordchgevent(myid, event) else: success = False debug = "Unable to disable GNOME automounting: " + \ "dbus-x11 is not installed" self.logger.log(LogPriority.DEBUG, debug) if os.path.exists(self.gconftool): if self.automountMedia: cmd = [self.gconftool, "--direct", "--config-source", "xml:readwrite:/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/automount_media", "false"] self.ch.executeCommand(cmd) returnCode = self.ch.getReturnCode() if not returnCode: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": [self.gconftool, "--direct", "--config-source", "xml:readwrite:" + "/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/" + "automount_media", "true"]} self.statechglogger.recordchgevent(myid, event) if self.automountDrives: cmd = [self.gconftool, "--direct", "--config-source", "xml:readwrite:/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/automount_drives", "false"] self.ch.executeCommand(cmd) returnCode += self.ch.getReturnCode() if not self.ch.getReturnCode(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "comm", "command": [self.gconftool, "--direct", "--config-source", "xml:readwrite:" + "/etc/gconf/gconf.xml.mandatory", "--type", "bool", "--set", "/desktop/gnome/volume_manager/" + "automount_drives", "true"]} self.statechglogger.recordchgevent(myid, event) if returnCode: success = False self.detailedresults += "Fix failed to disable GNOME automounting\n" # reset these directories to be owned by their respective users dirs = '' if os.path.exists('/run/user'): dirs = os.listdir('/run/user') if dirs: for d in dirs: # check if the directory is an integer representing a uid if re.search('^([+-]?[1-9]\d*|0)$', d, re.IGNORECASE): self.logger.log(LogPriority.DEBUG, "Found UID directory") try: os.chown('/run/user/' + d + '/dconf/user', int(d), int(d)) except Exception as errmsg: self.logger.log(LogPriority.DEBUG, str(errmsg)) continue else: self.logger.log(LogPriority.DEBUG, "no directories in /run/user") self.rulesuccess = success except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class SecureDHCPServer(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 134 self.rulename = "SecureDHCPServer" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() datatype = "bool" key = "SECUREDHCPSERVER" instructions = '''To disable this rule set the value of \ SECUREDHCPSERVER to False.''' default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = [ "NSA 3.9.4", "CCE 4257-2", "CCE 4403-2", "CCE 4345-5", "CCE 3724-2", "CCE 4243-2", "CCE 4389-3", "CCE 3913-1", "CCE 4169-9", "CCE 4318-2", "CCE 4319-0", "CCE 3733-3" ] self.applicable = {"type": "white", "family": ["linux"]} self.iditerator = 0 self.created = False def report(self): try: self.detailedresults = "" self.ph = Pkghelper(self.logger, self.environ) self.data1 = { "ddns-update-style": "none;", "deny": ["declines;", "bootp;"] } self.data2 = [ "domain-name", "domain-name-servers", "nis-domain", "nis-servers", "ntp-servers", "routers", "time-offset" ] if self.ph.manager == "zypper": self.path = "/etc/dhcpd.conf" elif self.ph.manager == "yum" or self.ph.manager == "dnf": self.path = "/etc/dhcp/dhcpd.conf" elif self.ph.manager == "apt-get": self.path = "/etc/dhcp/dhcpd.conf" self.tmppath = self.path + ".tmp" compliant = True if os.path.exists(self.path): if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "The permissions on " + \ self.path + " are incorrect\n" compliant = False self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmppath, self.data1, "present", "space") if not self.editor.report(): self.detailedresults += self.path + " doesn't contain " + \ "the correct contents\n" compliant = False contents = readFile(self.path, self.logger) for line in contents: if re.match('^#', line) or re.match(r'^\s*$', line): continue if re.search("^option", line): linesplit = line.split() if len(linesplit) >= 2: for item in self.data2: if re.search(item, linesplit[1]): compliant = False self.detailedresults += "Unwanted " + \ "option found in " + self.path + \ ": " + line 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 # Clean out old undo events self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) self.detailedresults = "" if not os.path.exists(self.path): createFile(self.path, self.logger) self.created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmppath, self.data1, "present", "space") self.editor.report() tempstring = "" tmpfile = self.path + ".tmp" contents = readFile(self.path, self.logger) changes = False for line in contents: found = False if re.match('^#', line) or re.match(r'^\s*$', line): tempstring += line continue if re.search("^option", line): temp = line.split() if len(temp) >= 2: for item in self.data2: if re.search(item, temp[1]): found = True changes = True break if found: continue else: tempstring += line else: tempstring += line if changes: debug = "Writing changes to " + tmpfile self.logger.log(LogPriority.DEBUG, debug) if not writeFile(tmpfile, tempstring, self.logger): debug = "Unable to write changes to " + tmpfile self.detailedresults += debug self.logger.log(LogPriority.DEBUG, debug) success = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange( self.path, tmpfile, myid) os.rename(tmpfile, self.path) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) setPerms(self.path, [0, 0, 0o644], self.logger, self.statechglogger, myid) resetsecon(self.path) if self.editor.fixables: if not self.created: if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): if self.editor.commit(): debug = self.path + "'s contents have been " + \ "corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.path, 0, 0) os.chmod(self.path, 0o644) resetsecon(self.path) else: debug = "kveditor commit not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: debug = "kveditor fix not successful\n" self.logger.log(LogPriority.DEBUG, debug) success = False self.rulesuccess = success except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class PreventXListen(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 31 self.rulename = "PreventXListen" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.guidance = ["NSA 3.6.1.3.2"] self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } datatype = 'bool' key = 'PREVENTXLISTEN' instructions = "To disable this rule set the value of " + \ "PREVENTXLISTEN to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.properties = {} self.iditerator = 0 ############################################################################### def report(self): try: self.detailedresults = "" self.fixables1, self.fixables2 = [], [] # self.fp1 contains a regex that needs to conform to each line in file self.fp1 = [ ["^:(.)* -nolisten tcp", "/etc/X11/xdm/Xservers", True], [ "^:(.)* -nolisten tcp", "/usr/X11R6/lib/X11/xdm/Xservers", True ], ["^:(.)* -nolisten tcp", "/etc/dt/config/Xservers", True], ["^:(.)* -nolisten tcp", "/usr/dt/config/Xservers", True] ] # self.fp2 contains regex that only needs to appear once in file self.fp2 = [[ '^command = (.)* -nolisten tcp', '/etc/X11/gdm/gdm.conf', False ], ['^DisallowTCP = true', '/usr/share/gdm/defaults.conf', False], ['^DisallowTCP = true', '/etc/gdm/custom.conf', False], [ '^exec(.)* -nolisten tcp', '/etc/X11/xinit/xserverrc', False ], [ '^ServerArgsLocal=(.)* -nolisten tcp', '/etc/kde/kdm/kdmrc', False ], [ '^ServerArgsLocal=(.)* -nolisten tcp', '/etc/kde4/kdm/kdmrc', False ], [ '^ServerArgsLocal=(.)* -nolisten tcp', '/usr/share/config/kdm/kdmrc', False ], [ 'DISPLAYMANAGER_XSERVER_TCP_PORT_6000_OPEN=NO', '/etc/sysconfig/displaymanager', False ]] compliant = True for item in self.fp1: if os.path.exists(item[1]): if not self.checkConfig(item[0], item[1], item[2]): self.detailedresults += item[1] + " doesn\'t have \ correct configuration\n" self.fixables1.append(item) compliant = False if item[1] == "/etc/X11/xdm/Xservers" or item[1] == \ "/usr/X11R6/lib/X11/xdm/Xservers": if not checkPerms(item[1], [0, 0, 292], self.logger): self.detailedresults += item[1] + " doesn\'t have \ correct permissions\n" compliant = False else: if not checkPerms(item[1], [0, 3, 292], self.logger): self.detailedresults += item[1] + " doesn\'t have \ correct permissions\n" compliant = False for item in self.fp2: if os.path.exists(item[1]): if not self.checkConfig(item[0], item[1], item[2]): self.detailedresults += item[1] + " doesn\'t have \ correct configuration\n" self.fixables2.append(item) if item[1] == "/etc/X11/xinit/xserverrc": if not checkPerms(item[1], [0, 0, 493], self.logger): self.detailedresults += item[1] + " doesn\'t have \ correct permissions\n" compliant = False elif not checkPerms(item[1], [0, 0, 420], self.logger): self.detailedresults += item[1] + " doesn\'t have \ correct permissions\n" compliant = False if self.environ.getosfamily() == "solaris": fp3 = "/etc/X11/gdm/gdm.conf" keys = {"security": {"DisallowTCP": "true"}} if os.path.exists(fp3): tmpPath = "/etc/X11/gdm/gdm.conf.tmp" kvtype = "tagconf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, fp3, tmpPath, keys, intent, "closedeq") if not self.editor.report(): self.detailedresults += "Kveditor report for solaris \ is non compliant\n" compliant = False self.compliant = compliant except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant ############################################################################### def fix(self): try: if not self.ci.getcurrvalue(): return self.iditerator = 0 # 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) success = True index = { "/etc/X11/gdm/gdm.conf": "command = /usr/X11R6/bin/X -nolisten tcp", "/usr/share/gdm/defaults.conf": "DisallowTCP = true", "/etc/gdm/custom.conf": "DisallowTCP = true", "/etc/X11/xinit/xserverrc": "exec /usr/X11R6/bin/X -nolisten tcp", "/etc/kde/kdm/kdmrc": "ServerArgsLocal = -nolisten tcp", "/etc/kde4/kdm/kdmrc": "ServerArgsLocal = -nolisten tcp", "/usr/share/config/kdm/kdmrc": "ServerArgsLocal = -nolisten tcp", "/etc/sysconfig/displaymanager": 'DISPLAYMANAGER_XSERVER_TCP_PORT_6000_OPEN=NO', "/etc/dt/config/Xservers": ":0 Local local_uid@console root /usr/X11/bin/Xserver :0 -nobanner -nolisten tcp", "/usr/dt/config/Xservers": ":0 Local local_uid@console root /usr/X11/bin/Xserver :0 -nobanner -nolisten tcp" } for item in self.fp1: if os.path.exists(item[1]): if item[1] == "/etc/X11/xdm/Xservers" or \ item[1] == "/usr/X11R6/lib/X11/xdm/Xservers": if not checkPerms(item[1], [0, 0, 292], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(item[1], [0, 0, 292], self.logger, self.statechglogger, myid): success = False else: if not checkPerms(item[1], [0, 3, 292], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(item[1], [0, 3, 292], self.logger, self.statechglogger, myid): success = False for item in self.fp2: if os.path.exists(item[1]): if item[1] == "/etc/X11/xinit/xserverrc": if not checkPerms(item[1], [0, 0, 493], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(item[1], [0, 0, 493], self.logger, self.statechglogger, myid): success = False elif not checkPerms(item[1], [0, 0, 420], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(item[1], [0, 0, 420], self.logger, self.statechglogger, myid): success = False for item in self.fixables1: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if self.writeConfig(item[1], myid, item[0], item[2]): os.chown(item[1], 0, 0) os.chmod(item[1], 292) resetsecon(item[1]) else: success = False for item in self.fixables2: for item2 in index: if item[1] == item2: item[0] = index[item2] self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if self.writeConfig(item[1], myid, item[0], item[2]): if item[1] == "/etc/X11/xinit/xserverrc": os.chmod(item[1], 493) else: os.chmod(item[1], 420) os.chown(item[1], 0, 0) resetsecon(item[1]) else: success = False if self.environ.getosfamily() == "solaris": fp3 = "/etc/X11/gdm/gdm.conf" if self.editor.fixables(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.commit(): self.detailedresults += "/etc/X11/gdm/gdm.conf \ file has been fixed\n" os.chown(fp3, 0, 0) os.chmod(fp3, 292) resetsecon(fp3) else: debug = "kveditor commit did not run successfully, must return" self.logger.log(LogPriority.DEBUG, debug) success = False else: debug = "kveditor fix did not run successfully, must return" self.logger.log(LogPriority.DEBUG, debug) success = False 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 checkConfig(self, regex, filepath, mult): contents = readFile(filepath, self.logger) if not contents: debug = filepath + " contents are blank\n" self.logger.log(LogPriority.DEBUG, debug) return False if mult: for line in contents: if re.match("^#", line) or re.match(r"^\s*$", line): continue if not re.search(regex, line.strip()): return False return True else: for line in contents: if re.match("^#", line) or re.match("^\s*$", line): continue if re.search(regex, line.strip()): return True return False ############################################################################## def writeConfig(self, filepath, myid, regex, mult): tempfile = filepath + ".tmp" tempstring = "" contents = readFile(filepath, self.logger) if not contents: debug = filepath + " contents are blank\n" self.logger.log(LogPriority.DEBUG, debug) return False if mult: for line in contents: if re.match("^#", line) or re.match("^\s*$", line): tempstring += line elif not re.search(regex, line): tempstring += line.strip() + " -nolisten tcp\n" else: for line in contents: tempstring += line tempstring += regex + "\n" if not writeFile(tempfile, tempstring, self.logger): return False event = {"eventtype": "conf", "filepath": filepath} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(filepath, tempfile, myid) os.rename(tempfile, filepath) return True
class ConfigureSystemAuthentication(Rule): """ Configure system authentication and password settings in accordance with rhel 7 stig requirements """ def __init__(self, config, environ, logger, statechglogger): """ :param config: :param environ: :param logger: :param statechglogger: """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 57 self.rulename = "ConfigureSystemAuthentication" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.applicable = {'type': 'white', 'family': 'linux'} datatype = "bool" key = "CONFIGSYSAUTH" instructions = "To disable this rule, set the value of " + \ "CONFIGSYSAUTH to False." default = True self.ci1 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "PASSWORDREQ" instructions = "To not configure password requirements, set " + \ "PASSWORDREQ to False. This configuration item will configure " + \ "PAM's password requirements when changing to a new password." default = True self.ci2 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "PASSWORDFAIL" instructions = "To not configure password fail locking, set " + \ "PASSWORDFAIL to False. This configuration item will " + \ "configure PAM's failed login attempts mechanism using either " + \ "faillock or tally2." default = True self.ci3 = self.initCi(datatype, key, instructions, default) datatype = "bool" key = "PWHASHING" instructions = "To not set the hashing algorithm, set " + \ "PWHASHING to False. This configuration item will configure " + \ "libuser and/or login.defs, which specifies the hashing " + \ "algorithm to use." default = True self.ci4 = self.initCi(datatype, key, instructions, default) self.guidance = ["NSA 2.3.3.1,", "NSA 2.3.3.2"] self.created = False self.localize() def localize(self): """ set up session variables based on system platform and version """ myos = self.environ.getostype().lower() if re.search("suse", myos): self.password = PASSWORD_ZYPPER self.auth = AUTH_ZYPPER self.acct = ACCOUNT_ZYPPER elif re.search("debian|ubuntu", myos): self.password = PASSWORD_APT self.auth = AUTH_APT self.acct = ACCOUNT_APT else: self.password = PASSWORD_YUM self.auth = AUTH_YUM self.acct = ACCOUNT_YUM self.session = SESSION_YUM def report(self): """ ConfigureSystemAuthentication() report method to report if system is compliant with authentication and password settings @author: Derek Walker :return: self.compliant :rtype: bool """ self.compliant = True self.detailedresults = "" self.ci2comp, self.ci3comp, self.ci4comp = True, True, True try: if not self.reportLinux(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): """ ConfigureSystemAuthentication.fix() method to fix the system to be compliant with authentication and password settings @author: Derek Walker :return: self.rulesuccess :rtype: bool """ self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 try: if not self.ci1.getcurrvalue(): return self.rulesuccess # delete past state change records from previous fix eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not self.fixLinux(): self.rulesuccess = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def reportLinux(self): """ Linux-specific submethod for config reporting @author: Derek Walker :return: compliant :rtype: bool """ self.logindefs = "/etc/login.defs" debug = "" compliant = True self.editor1, self.editor2 = "", "" self.pwqeditor = "" self.usingpwquality, self.usingcracklib = False, False self.usingpamtally2, self.usingpamfail = False, False self.created1, self.created2 = False, False self.libuserfile = "/etc/libuser.conf" self.ph = Pkghelper(self.logger, self.environ) self.determine_pwreqs_mechanism() # set pam password and authentication file paths pampassfiles = ["/etc/pam.d/common-password", "/etc/pam.d/common-password-pc", "/etc/pam.d/password-auth", "/etc/pam.d/password-auth-ac"] pamauthfiles = ["/etc/pam.d/common-auth", "/etc/pam.d/common-auth-pc", "/etc/pam.d/system-auth", "/etc/pam.d/system-auth-ac"] for f in pampassfiles: if os.path.isfile(f): self.pampassfile = f for f in pamauthfiles: if os.path.isfile(f): self.pamauthfile = f if not bool(self.pampassfile and self.pamauthfile): if self.ph.manager == "apt-get": self.pampassfile = "/etc/pam.d/common-password" self.pamauthfile = "/etc/pam.d/common-auth" elif self.ph.manager == "zypper": self.pampassfile = "/etc/pam.d/common-password-pc" self.pamauthfile = "/etc/pam.d/common-auth-pc" else: self.pampassfile = "/etc/pam.d/password-auth" self.pamauthfile = "/etc/pam.d/system-auth" if not self.check_pwreqs_configured(): self.ci2comp = False debug += "checkpasswordreqs method is False compliancy\n" compliant = False if not self.checkaccountlockout(): self.ci3comp = False debug += "checkaccountlockout method is False compliancy\n" compliant = False if not self.checklogindefs(): self.ci4comp = False debug += "checklogindefs method is False compliancy\n" compliant = False if not self.checklibuser(): self.ci4comp = False debug += "checklibuser method is False compliancy\n" compliant = False if debug: self.logger.log(LogPriority.DEBUG, debug) if not self.check_showfailed_logins(): compliant = False return compliant def fixLinux(self): """ Linux specific submethod to correct linux distributions. If your system is portage based, i.e. gentoo, you will need to do a manual fix for everything except the login.defs file @author: Derek Walker :return: success :rtype: bool """ success = True try: if self.ph.manager == "dnf": if not self.ph.check("authconfig"): self.ph.install("authconfig") # this is needed by fedora to continue except: pass # """create backups of pamfiles""" if os.path.exists(self.pampassfile): createFile(self.pampassfile + ".backup", self.logger) if os.path.exists(self.pamauthfile): createFile(self.pamauthfile + ".backup", self.logger) if self.ci2.getcurrvalue(): if not self.ci2comp: # configure regex for pwquality if self.usingpwquality: self.password = re.sub("pam_cracklib\.so", "pam_pwquality.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex = PWQUALITY_HIGH_REGEX else: regex = PWQUALITY_REGEX if self.pwqinstalled: if not self.setpasswordsetup(regex): success = False else: if not self.setpasswordsetup(regex, self.pwqualitypkgs): success = False # configure regex for cracklib elif self.usingcracklib: self.password = re.sub("pam_pwquality\.so", "pam_cracklib.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex = CRACKLIB_HIGH_REGEX else: regex = CRACKLIB_REGEX if self.clinstalled: if not self.setpasswordsetup(regex): success = False else: if not self.setpasswordsetup(regex, self.cracklibpkgs): success = False else: error = "Could not find pwquality/cracklib pam module. Fix failed." self.logger.log(LogPriority.ERROR, error) self.detailedresults += error + "\n" return False if self.ci3.getcurrvalue(): if not self.ci3comp: if self.usingpamfail: regex = PAMFAIL_REGEX if not self.setaccountlockout(regex): success = False self.detailedresults += "Unable to configure pam for faillock\n" elif self.usingpamtally2: regex = PAMTALLY_REGEX if not self.setaccountlockout(regex): success = False self.detailedresults += "Unable to configure pam for pam_tally2\n" else: self.detailedresults += "There is no account lockout program available for this system\n" success = False if self.ci4.getcurrvalue(): if not self.ci4comp: if not self.checklibuser(): if not self.setlibuser(): debug = "setlibuser() failed\n" self.detailedresults += "Unable to configure /etc/libuser.conf\n" self.logger.log(LogPriority.DEBUG, debug) success = False if not self.checklogindefs(): if not self.setlogindefs(): debug = "setdefpasshash() failed\n" self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += "Unable to configure /etc/login.defs file\n" success = False if not self.set_showfailed_logins(): success = False return success def check_showfailed_logins(self): """ config file: /etc/pam.d/postlogin-ac config option: session required pam_lastlog.so showfailed :return: """ configured = True search_line = {"session": "required pam_lastlog.so showfailed"} config_file = "/etc/pam.d/postlogin-ac" tmpfile = config_file + ".stonixtmp" self.lastlog_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", config_file, tmpfile, search_line, "present", "space") if not self.lastlog_editor.report(): configured = False self.detailedresults += "\nShow failed logins option is not configured correctly in PAM" return configured def set_showfailed_logins(self): """ config file: /etc/pam.d/postlogin-ac config option: session required pam_lastlog.so showfailed :return: success :rtype: bool """ success = True if not self.lastlog_editor.fix(): success = False self.detailedresults += "\nFailed to enable show failed logins option in PAM" elif not self.lastlog_editor.commit(): success = False self.detailedresults += "\nFailed to enable show failed logins option in PAM" return success def determine_pwreqs_mechanism(self): """ determine whether this system is using cracklib or pwquality as a password requirements control mechanism use pwquality by default since cracklib is legacy """ self.usingcracklib = False self.usingpwquality = False self.pwqualitypkg = "" self.cracklibpkg = "" # potential password requirements package names self.cracklibpkgs = ["libpam-cracklib", "cracklib"] self.pwqualitypkgs = ["libpam-pwquality", "pam_pwquality", "libpwquality", "libpwquality1"] # pwquality check for pkg in self.pwqualitypkgs: if self.ph.check(pkg): self.usingpwquality = True self.pwqualitypkg = pkg break if not self.usingpwquality: for pkg in self.pwqualitypkgs: if self.ph.checkAvailable(pkg): self.usingpwquality = True self.pwqualitypkg = pkg break # cracklib check (only runs if pwquality check turns up nothing) if not self.usingpwquality: for pkg in self.cracklibpkgs: if self.ph.check(pkg): self.usingcracklib = True self.cracklibpkg = pkg break if not self.usingcracklib: for pkg in self.cracklibpkgs: if self.ph.checkAvailable(pkg): self.usingcracklib = True self.cracklibpkg = pkg break def check_pwreqs_installed(self): """ determine if either a cracklib package or pwquality package is installed :return: installed :rtype: bool """ installed = False self.pwqinstalled = False self.clinstalled = False for pkg in self.pwqualitypkgs: if self.ph.check(pkg): self.pwqinstalled = True for pkg in self.cracklibpkgs: if self.ph.check(pkg): self.clinstalled = True if bool(self.clinstalled or self.pwqinstalled): installed = True return installed def check_pwreqs_configured(self): """ check whether the password requirements have been properly configured in PAM, using either cracklib or pwquality :return: passwords_configured :rtype: bool """ passwords_configured = True if not self.check_pwreqs_installed(): passwords_configured = False return passwords_configured if self.usingpwquality: if not self.pwqinstalled: for pkg in self.pwqualitypkgs: if self.ph.install(pkg): self.pwqinstalled = True break if not self.checkpasswordsetup("pwquality"): self.detailedresults += "System is using pwquality but it's not configured properly in PAM\n" passwords_configured = False elif self.usingcracklib: if not self.clinstalled: for pkg in self.cracklibpkgs: if self.ph.install(pkg): self.clinstalled = True break if not self.checkpasswordsetup("cracklib"): self.detailedresults += "System is using cracklib but it's not configured properly in PAM\n" passwords_configured = False return passwords_configured def checkpasswordsetup(self, package): """ Method called from within checkpasswordreqs method @author: Derek Walker :param package: string; name of package to check for :return: compliant :rtype: bool """ compliant = True regex1 = "" if package == "pwquality": self.password = re.sub("pam_cracklib\.so", "pam_pwquality.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex1 = PWQUALITY_HIGH_REGEX else: regex1 = PWQUALITY_REGEX if not self.chkpwquality(): compliant = False elif package == "cracklib": self.password = re.sub("pam_pwquality\.so", "pam_cracklib.so", self.password) if self.environ.getsystemfismacat() == "high": self.password = re.sub("minlen=8", "minlen=14", self.password) self.password = re.sub("minclass=3", "minclass=4", self.password) regex1 = CRACKLIB_HIGH_REGEX else: regex1 = CRACKLIB_REGEX regex2 = "^password[ \t]+sufficient[ \t]+pam_unix.so\s+sha512\s+shadow\s+try_first_pass\s+use_authtok\s+remember=10" pamfiles = [] if self.ph.manager in ("yum", "dnf"): pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) else: pamfiles.append(self.pampassfile) for pamfile in pamfiles: found1, found2 = False, False if not os.path.exists(pamfile): self.detailedresults += pamfile + " doesn't exist\n" compliant = False else: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.detailedresults += "Incorrect permissions or ownership exist for file " + pamfile compliant = False contents = readFile(pamfile, self.logger) if not contents: self.detailedresults += pamfile + " is blank\n" compliant = False else: for line in contents: if re.search(regex1, line.strip()): found1 = True if re.search(regex2, line.strip()): found2 = True if not found1: self.detailedresults += "\n'password requisite ...' line not correct in " + pamfile if not found2: self.detailedresults += "\n'password sufficient ...' line not correct in " + pamfile compliant = False return compliant def setpasswordsetup(self, regex1, pkglist = None): """ configure password requirements in pam, install necessary packages :param regex1: string; regular expression :param pkglist: list; string names of packages to install :return: success :rtype: bool """ regex2 = "^password[ \t]+sufficient[ \t]+pam_unix.so sha512 shadow " + \ "try_first_pass use_authtok remember=10" success = True pamfiles = [] installed = False if pkglist: for pkg in pkglist: if self.ph.check(pkg): installed = True break else: installed = True if not installed: for pkg in pkglist: if self.ph.checkAvailable(pkg): if not self.ph.install(pkg): self.detailedresults += "Unable to install pkg " + pkg + "\n" return False else: installed = True if self.usingpwquality: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() + pkg event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) pwqfile = "/etc/security/pwquality.conf" tmpfile = pwqfile + ".stonixtmp" if self.environ.getsystemfismacat() == "high": data = {"difok": "7", "minlen": "14", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "4"} else: data = {"difok": "7", "minlen": "8", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "3"} self.pwqeditor = KVEditorStonix(self.statechglogger, self.logger, "conf", pwqfile, tmpfile, data, "present", "openeq") self.pwqeditor.report() break if not installed: self.detailedresults += "No password checking program available\n" return False if self.usingpwquality: if not self.setpwquality(): success = False if self.ph.manager in ("yum", "dnf"): writecontents = self.auth + "\n" + self.acct + "\n" + \ self.password + "\n" + self.session pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) else: writecontents = self.password pamfiles.append(self.pampassfile) for pamfile in pamfiles: if not os.path.exists(pamfile): self.detailedresults += pamfile + " doesn't exist.\n" + \ "Stonix will not attempt to create this file " + \ "and the fix for the this rule will not continue\n" return False # """Check permissions on pam file(s)""" for pamfile in pamfiles: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(pamfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set correct permissions on " + pamfile + "\n" contents = readFile(pamfile, self.logger) found1, found2 = False, False for line in contents: if re.search(regex1, line.strip()): found1 = True if re.search(regex2, line.strip()): found2 = True if not found1 or not found2: tmpfile = pamfile + ".stonixtmp" if writeFile(tmpfile, writecontents, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': pamfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(pamfile, tmpfile, myid) os.rename(tmpfile, pamfile) os.chown(pamfile, 0, 0) os.chmod(pamfile, 0o644) resetsecon(pamfile) else: self.detailedresults += "Unable to write to " + pamfile + "\n" success = False return success def checkaccountlockout(self): """ Method to determine which account locking program to use if any @author: Derek Walker :return: compliant :rtype: bool """ which = "/usr/bin/which " cmd1 = which + "faillock" cmd2 = which + "pam_tally2" ch = CommandHelper(self.logger) pamfiles = [] compliant = True regex = "" if ch.executeCommand(cmd1): debug = "ran " + cmd1 + " successfully\n" self.logger.log(LogPriority.DEBUG, debug) if ch.getReturnCode() == 0: debug = "return code of 0 and using faillock\n" self.logger.log(LogPriority.DEBUG, debug) self.usingpamfail = True elif ch.executeCommand(cmd2): debug = "ran " + cmd2 + " successfully\n" self.logger.log(LogPriority.DEBUG, debug) if ch.getReturnCode() == 0: debug = "return code of 0 and using pam_tally2\n" self.logger.log(LogPriority.DEBUG, debug) self.usingpamtally2 = True else: self.detailedresults += "There is no account " + \ "locking program available for this " + \ "distribution\n" return False elif ch.executeCommand(cmd2): debug = "ran " + cmd2 + " successfully\n" self.logger.log(LogPriority.DEBUG, debug) if ch.getReturnCode() == 0: debug = "return code of 0 and using pam_tally2\n" self.logger.log(LogPriority.DEBUG, debug) self.usingpamtally2 = True else: self.detailedresults += "There is no account " + \ "locking program available for this " + \ "distribution\n" return False else: self.detailedresults += "There is no account " + \ "locking program available for this " + \ "distribution\n" return False if self.usingpamfail: regex = "^auth[ \t]+required[ \t]+pam_faillock.so preauth silent audit " + \ "deny=5 unlock_time=900 fail_interval=900" elif self.usingpamtally2: regex = "^auth[ \t]+required[ \t]+pam_tally2.so deny=5 " + \ "unlock_time=900 onerr=fail" if self.ph.manager in("yum", "dnf"): pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) else: pamfiles.append(self.pamauthfile) for pamfile in pamfiles: found = False if not os.path.exists(pamfile): self.detailedresults += "Critical pam file " + pamfile + " doesn't exist\n" compliant = False else: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions aren't correct on " + pamfile + "\n" self.ci3comp = False compliant = False contents = readFile(pamfile, self.logger) if not contents: self.detailedresults += pamfile + " is blank\n" self.ci3comp = False compliant = False else: for line in contents: if re.search(regex, line.strip()): found = True if not found: self.detailedresults += "Didn't find the correct contents in " + pamfile + "\n" self.ci3comp = False compliant = False return compliant def setaccountlockout(self, regex): """ configure the account lockout time in pam :param regex: string; regular expression :return: success :rtype: bool """ success = True pamfiles = [] if self.ph.manager in ("yum", "dnf"): pamfiles.append(self.pamauthfile) pamfiles.append(self.pampassfile) writecontents = self.auth + "\n" + self.acct + "\n" + \ self.password + "\n" + self.session else: pamfiles.append(self.pamauthfile) writecontents = self.auth for pamfile in pamfiles: if not os.path.exists(pamfile): self.detailedresults += pamfile + " doesn't exist.\n" + \ "Stonix will not attempt to create this file " + \ "and the fix for the this rule will not continue\n" return False # """Check permissions on pam file(s)""" for pamfile in pamfiles: if not checkPerms(pamfile, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(pamfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set " + \ "correct permissions on " + pamfile + "\n" contents = readFile(pamfile, self.logger) found = False for line in contents: if re.search(regex, line.strip()): found = True if not found: tmpfile = pamfile + ".stonixtmp" if writeFile(tmpfile, writecontents, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'conf', 'filepath': pamfile} self.statechglogger.recordchgevent(myid, event) self.statechglogger.recordfilechange(pamfile, tmpfile, myid) os.rename(tmpfile, pamfile) os.chown(pamfile, 0, 0) os.chmod(pamfile, 0o644) resetsecon(pamfile) else: self.detailedresults += "Unable to write to " + pamfile + "\n" success = False return success def chkpwquality(self): """ check settings of pwquality pam plugin :return: compliant :rtype: bool """ compliant = True pwqfile = "/etc/security/pwquality.conf" if os.path.exists(pwqfile): tmpfile = pwqfile + ".stonixtmp" if self.environ.getsystemfismacat() == "high": data = {"difok": "7", "minlen": "14", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "4"} else: data = {"difok": "7", "minlen": "8", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "3"} self.pwqeditor = KVEditorStonix(self.statechglogger, self.logger, "conf", pwqfile, tmpfile, data, "present", "openeq") if not self.pwqeditor.report(): compliant = False self.detailedresults += "Not all correct contents were found in " + pwqfile + "\n" else: compliant = False self.detailedresults += "System is using pwquality and crucial file /etc/security/pwquality.conf doesn't exist\n" return compliant def checklogindefs(self): """ Method to check the password hash algorithm settings in login.defs :return: compliant :rtype: bool """ compliant = True if os.path.exists(self.logindefs): if not checkPerms(self.logindefs, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions incorrect for " + \ self.logindefs + " file\n" compliant = False data = {"MD5_CRYPT_ENAB": "no", "ENCRYPT_METHOD": "SHA512", "PASS_MAX_DAYS": "180", "PASS_MIN_DAYS": "1", "PASS_WARN_AGE": "7", "FAIL_DELAY": "4"} tmppath = self.logindefs + ".stonixtmp" self.editor2 = KVEditorStonix(self.statechglogger, self.logger, "conf", self.logindefs, tmppath, data, "present", "space") if not self.editor2.report(): debug = self.logindefs + " doesn't contain the correct " + \ "contents\n" self.detailedresults += self.logindefs + " doesn't contain " + \ "the correct contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False return compliant def checklibuser(self): """ Private method to check the password hash algorithm settings in libuser.conf @author: Derek Walker :return: compliant :rtype: bool """ compliant = True # """check if libuser is intalled""" if not self.ph.check("libuser"): # """if not, check if available""" if self.ph.checkAvailable("libuser"): self.detailedresults += "libuser available but not installed\n" return False else: # """not available, not a problem""" return True # """create a kveditor for file if it exists, if not, we do it in # the setlibuser method inside the fix""" if os.path.exists(self.libuserfile): data = {"defaults": {"crypt_style": "sha512"}} datatype = "tagconf" intent = "present" tmppath = self.libuserfile + ".stonixtmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") if not self.editor1.report(): debug = "/etc/libuser.conf doesn't contain the correct contents\n" self.detailedresults += "/etc/libuser.conf doesn't contain the correct contents\n" self.logger.log(LogPriority.DEBUG, debug) compliant = False if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + self.libuserfile + "\n" compliant = False else: self.detailedresults += "Libuser installed but libuser file doesn't exist\n" compliant = False return compliant def setpwquality(self): """ :return: """ success = True created = False pwqfile = "/etc/security/pwquality.conf" if not os.path.exists(pwqfile): createFile(pwqfile, self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {'eventtype': 'creation', 'filepath': pwqfile} self.statechglogger.recordchgevent(myid, event) created = True tmpfile = pwqfile + ".stonixtmp" if self.environ.getsystemfismacat() == "high": data = {"difok": "7", "minlen": "14", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "4"} else: data = {"difok": "7", "minlen": "8", "dcredit": "0", "ucredit": "0", "lcredit": "0", "ocredit": "0", "maxrepeat": "3", "minclass": "3"} self.pwqeditor = KVEditorStonix(self.statechglogger, self.logger, "conf", pwqfile, tmpfile, data, "present", "openeq") self.pwqeditor.report() if self.pwqeditor.fixables: if self.pwqeditor.fix(): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.pwqeditor.setEventID(myid) if not self.pwqeditor.commit(): success = False self.detailedresults += "Unable to correct " + pwqfile + "\n" else: success = False self.detailedresults += "Unable to correct " + pwqfile + "\n" return success def setlibuser(self): """Method to check if libuser is installed and the contents of libuser file. @author: Derek Walker :return: bool """ created = False success = True data = {"defaults": {"crypt_style": "sha512"}} # """check if installed""" if not self.ph.check("libuser"): # """if not installed, check if available""" if self.ph.checkAvailable("libuser"): # """if available, install it""" if not self.ph.install("libuser"): self.detailedresults += "Unable to install libuser\n" return False else: # """since we're just now installing it we know we now # need to create the kveditor""" self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) comm = self.ph.getRemove() + "libuser" event = {"eventtype": "commandstring", "command": comm} self.statechglogger.recordchgevent(myid, event) datatype = "tagconf" intent = "present" tmppath = self.libuserfile + ".stonixtmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, datatype, self.libuserfile, tmppath, data, intent, "openeq") self.editor1.report() else: return True if not os.path.exists(self.libuserfile): if not createFile(self.libuserfile, self.logger): self.detailedresults += "Unable to create libuser file\n" debug = "Unable to create the libuser file\n" self.logger.log(LogPriority.DEBUG, debug) return False created = True self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.libuserfile} self.statechglogger.recordchgevent(myid, event) tmppath = self.libuserfile + ".stonixtmp" self.editor1 = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.libuserfile, tmppath, data, "present", "openeq") self.editor1.report() if not checkPerms(self.libuserfile, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.libuserfile, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False self.detailedresults += "Unable to set the permissions on " + self.libuserfile + "\n" elif not setPerms(self.libuserfile, [0, 0, 0o644], self.logger): success = False self.detailedresults += "Unable to set the permissions on " + self.libuserfile + "\n" if self.editor1.fixables: if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor1.setEventID(myid) if self.editor1.fix(): if self.editor1.commit(): debug = "/etc/libuser.conf has been corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.libuserfile, 0, 0) os.chmod(self.libuserfile, 0o644) resetsecon(self.libuserfile) else: self.detailedresults += "/etc/libuser.conf couldn't be corrected\n" success = False else: self.detailedresults += "/etc/libuser.conf couldn't be corrected\n" success = False return success def setlogindefs(self): """ configure login.defs options :return: success :rtype: bool """ success = True if not checkPerms(self.logindefs, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.logindefs, [0, 0, 0o644], self.logger, self.statechglogger, myid): self.detailedresults += "Unable to set permissions on " + self.logindefs + " file\n" success = False if self.editor2: if self.editor2.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor2.setEventID(myid) if self.editor2.fix(): if self.editor2.commit(): debug = "/etc/login.defs file has been corrected\n" self.logger.log(LogPriority.DEBUG, debug) os.chown(self.logindefs, 0, 0) os.chmod(self.logindefs, 0o644) resetsecon(self.logindefs) else: debug = "Unable to correct the contents of /etc/login.defs\n" self.detailedresults += "Unable to correct the contents of /etc/login.defs\n" self.logger.log(LogPriority.DEBUG, debug) success = False else: self.detailedresults += "Unable to correct the contents of /etc/login.defs\n" debug = "Unable to correct the contents of /etc/login.defs\n" self.logger.log(LogPriority.DEBUG, debug) success = False return success
class ExecShield(Rule): '''This class is responsible for auditing and correcting the setting of the ExecShield overflow prevention and the virtual address space randomizer. On most modern Linux distributions these are correct by default. @author: dkennel ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.rulenumber = 63 self.rulename = 'ExecShield' self.formatDetailedResults("initialize") self.mandatory = False self.rootrequired = True self.rulesuccess = True self.comment = re.compile('^#|^;') self.sysctlconf = '/etc/sysctl.conf' self.tmpPath = '/etc/sysctl.conf.tmp' self.comment = re.compile('^#|^;') self.guidance = ['CCE-27007-4', 'CCE-26999-3'] self.applicable = {'type': 'white', 'family': ['linux']} self.varandomcompliant = False self.shieldprocpath = '/proc/sys/kernel/exec-shield' if os.path.exists(self.shieldprocpath): self.execshieldapplies = True self.directives = {'kernel.exec-shield': '1', 'kernel.randomize_va_space': '2'} else: self.execshieldapplies = False self.directives = {'kernel.randomize_va_space': '2'} self.execshieldcompliant = False self.ExecCI = self.__initializeExecShield() self.sethelptext() def __initializeExecShield(self): ''' Private method to initialize the configurationitem object for the EXECSHIELD bool. @return: configuration object instance @author: dkennel ''' datatype = 'bool' key = 'EXECSHIELD' instructions = 'If set to yes or true the EXECSHIELD action will, ' + \ 'if needed correct the kernel settings for the ExecShield and ' + \ 'virtual address randomization functions. This should be safe ' + \ 'for all systems.' default = True myci = self.initCi(datatype, key, instructions, default) return myci def checkproc(self, procpath): '''Check for the value of a specific key in proc. Return that value. This method is designed for proc keys that only return a single value. :param procpath: string: fully qualified path to the element to be checked. :returns: string version of the value at that proc location. @author: dkennel ''' myval = '' try: rhandle = open(procpath, 'r') myval = rhandle.read() rhandle.close() myval = myval.strip() return myval except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.detailedresults = 'ExecShield.checkproc: ' self.detailedresults += traceback.format_exc() self.rulesuccess = False self.logdispatch.log(LogPriority.ERROR, ['ExecShield.checkproc', self.detailedresults]) def report(self): '''Main report method. We rely on the active values in proc to make our compliant/not-compliant decision. @author dkennel :returns: self.compliant :rtype: bool @change: Breen Malmberg - 1/10/2017 - minor doc string edit; return var init ''' self.detailedresults = '' self.compliant = False va_path = '/proc/sys/kernel/randomize_va_space' try: if self.execshieldapplies: execval = int(self.checkproc(self.shieldprocpath)) if execval == 1: self.execshieldcompliant = True self.detailedresults += 'Exec-Shield present and ' + \ 'compliant\n' else: self.detailedresults += 'Exec-Shield present but not ' + \ 'compliant. Current value: ' + str(execval) + '\n' vaval = int(self.checkproc(va_path)) if vaval == 2: self.varandomcompliant = True self.detailedresults += 'Randomize_va_space compliant\n' else: self.detailedresults += 'Randomize_va_space not compliant. ' + \ 'Current value: ' + str(vaval) + '\n' if self.execshieldapplies: if self.execshieldcompliant and self.varandomcompliant: self.compliant = True else: if self.varandomcompliant: self.compliant = True except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.detailedresults = 'ExecShield.report: ' self.detailedresults += traceback.format_exc() self.rulesuccess = False 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): '''Main fix method. We update the current vaules in proc via shell commands and set the correct settings in /etc/sysctl.conf since our assumption is that if it didn't pass it's because it's been overridden in sysctl.conf. @author: dkennel :returns: self.rulesuccess :rtype: bool @change: Breen Malmberg - 1/10/2017 - minor doc string edit; self.rulesuccess now default init to True (only being set to False in the method); method now returns self.rulesuccess; fixed perms on file sysctl.conf (should be 0o600; was 420) ''' self.detailedresults = "" self.rulesuccess = True if self.ExecCI.getcurrvalue(): try: kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logdispatch, kvtype, self.sysctlconf, self.tmpPath, self.directives, intent, "openeq") if self.execshieldapplies: cmdshield = '/sbin/sysctl -w kernel.exec-shield=1' subprocess.call(cmdshield, shell=True) cmdvarand = '/sbin/sysctl -w kernel.randomize_va_space=2' subprocess.call(cmdvarand, shell=True) if not self.editor.report(): if self.editor.fixables: myid = '0063001' self.editor.setEventID(myid) if not self.editor.fix(): self.rulesuccess = False elif not self.editor.commit(): self.rulesuccess = False if self.rulesuccess: os.chown(self.sysctlconf, 0, 0) os.chmod(self.sysctlconf, 0o600) resetsecon(self.sysctlconf) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.detailedresults = 'ExecShield.fix: ' self.detailedresults += traceback.format_exc() self.rulesuccess = False 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 SystemAccounting(Rule): """ """ def __init__(self, config, environ, logger, statechglogger): """ Constructor """ Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 9 self.rulename = 'SystemAccounting' self.formatDetailedResults("initialize") self.mandatory = False self.rootrequired = True self.sethelptext() self.guidance = ['CIS 2.4', 'cce-3992-5'] self.applicable = { 'type': 'white', 'family': 'linux', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } # set up configuration item for this rule datatype = 'bool' key = 'SYSTEMACCOUNTING' instructions = "This is an optional rule and is disabled by default, due to the significant load it can place on the system when enabled. To enable system accounting, set the value of SYSTEMACCOUNTING to True." default = False self.ci = self.initCi(datatype, key, instructions, default) self.ostype = self.environ.getostype() self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self._set_paths() def _set_paths(self): """ """ self.sysstat_package = "sysstat" self.sysstat_service_file = "" sysstat_service_locs = [ "/usr/lib/systemd/system/sysstat.service", "/lib/systemd/system/sysstat.service", "/etc/init.d/sysstat" ] for ss in sysstat_service_locs: if os.path.isfile(ss): self.sysstat_service_file = ss break self.accton = "/usr/sbin/accton" self.acct_file = "/var/account/acct" self.cron_file = "/etc/cron.d/sysstat" self.sa1 = "" sa1_locs = [ "/usr/lib64/sa/sa1", "/usr/local/lib64/sa/sa1", "/usr/lib/sysstat/sa1" ] for sl in sa1_locs: if os.path.isfile(sl): self.sa1 = sl break self.sa2 = "" sa2_locs = [ "/usr/lib64/sa/sa2", "/usr/local/lib64/sa/sa2", "/usr/lib/sysstat/sa2" ] for sl in sa2_locs: if os.path.isfile(sl): self.sa2 = sl break self.sysstat_service_contents = """# /usr/lib/systemd/system/sysstat.service # (C) 2012 Peter Schiffer (pschiffe <at> redhat.com) # # sysstat-10.1.7 systemd unit file: # Insert a dummy record in current daily data file. # This indicates that the counters have restarted from 0. [Unit] Description=Resets System Activity Logs [Service] Type=oneshot RemainAfterExit=yes User=root ExecStart=""" + self.sa1 + """ --boot [Install] WantedBy=multi-user.target """ self.sysstat_cron_contents = """# Run system activity accounting tool every 60 minutes */60 * * * * root """ + self.sa1 + """ 1 1 # Generate a daily summary of process accounting at 23:53 53 23 * * * root """ + self.sa2 + """ -A""" def _report_configuration(self): """ :return: compliant :rtype: bool """ compliant = True if self.ostype == "Mac OS X": self.conf_file = "" conf_files = ["/etc/rc.conf", "/etc/rc.common"] for cf in conf_files: if os.path.isfile(cf): self.conf_file = cf break tmpfile = self.conf_file + ".stonixtmp" config_data = {"accounting_enable": "YES"} self.conf_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.conf_file, tmpfile, config_data, "present", "closedeq") if not self.conf_editor.report(): compliant = False else: if not os.path.isfile(self.sysstat_service_file): compliant = False self.detailedresults += "\nSystem accounting service file is missing" else: f = open(self.sysstat_service_file, "r") contents = f.read() f.close() if self.sysstat_service_file != "/etc/init.d/sysstat": if contents != self.sysstat_service_contents: compliant = False self.detailedresults += "\nSystem accounting service file has incorrect contents" if os.path.isfile("/etc/default/sysstat"): f = open("/etc/default/sysstat", "r") contents = f.read() f.close() if not re.search('ENABLED="true"', contents): compliant = False self.detailedresults += "\n/etc/default/sysstat file has incorrect contents" return compliant def _report_installation(self): """ :return: compliant :rtype: bool """ compliant = True if self.ostype != "Mac OS X": if not self.ph.check(self.sysstat_package): compliant = False self.detailedresults += "\nSystem accounting package is not installed" return compliant def _report_schedule(self): """ :return: compliant :rtype: bool """ compliant = True if self.ostype == "Mac OS X": if not os.path.isfile(self.acct_file): compliant = False self.detailedresults += "\nSystem accounting is not enabled on this system" else: if not os.path.isfile(self.cron_file): compliant = False else: f = open(self.cron_file, "r") contents = f.read() f.close() if contents != self.sysstat_cron_contents: self.compliant = False self.detailedresults += "\nSystem account cron job has incorrect contents" return compliant def report(self): """ :return: compliant :rtype: bool """ self.detailedresults = "" self.compliant = True try: if not self._report_installation(): self.compliant = False else: if not self._report_configuration(): self.compliant = False if not self._report_schedule(): self.compliant = False except (KeyboardInterrupt, SystemExit): raise except: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def _fix_installation(self): """ :return: success :rtype: bool """ success = True if self.ostype != "Mac OS X": if not self.ph.install(self.sysstat_package): success = False self.logger.log(LogPriority.DEBUG, "Failed to install sysstat package") return success def _fix_configuration(self): """ :return: success :rtype: bool """ success = True if self.ostype == "Mac OS X": if not self.conf_editor.fix(): success = False self.logger.log(LogPriority.DEBUG, "kveditor failed to fix()") elif not self.conf_editor.commit(): success = False self.logger.log(LogPriority.DEBUG, "kveditor failed to commit()") else: try: if self.sysstat_service_file != "/etc/init.d/sysstat": f = open(self.sysstat_service_file, "w") f.write(self.sysstat_service_contents) f.close() except: success = False if os.path.isfile("/etc/default/sysstat"): default_sysstat_contents = """# # Default settings for /etc/init.d/sysstat, /etc/cron.d/sysstat # and /etc/cron.daily/sysstat files # # Should sadc collect system activity information? Valid values # are 'true' and 'false'. Please do not put other values, they # will be overwritten by debconf! ENABLED="true" """ f = open("/etc/default/sysstat", "w") f.write(default_sysstat_contents) f.close() return success def _fix_schedule(self): """ :return: success :rtype: bool """ success = True try: if self.ostype == "Mac OS X": if not os.path.isdir("/var/account"): os.mkdir("/var/account", 0o755) open(self.acct_file, "a").close() self.ch.executeCommand(self.accton + " " + self.acct_file) else: f = open(self.cron_file, "w") f.write(self.sysstat_cron_contents) f.close() os.chown(self.cron_file, 0, 0) os.chmod(self.cron_file, 0o644) except: success = False return success def fix(self): """ :return: self.rulesuccess :rtype: bool """ self.detailedresults = "" self.rulesuccess = True try: if not self._fix_installation(): self.rulesuccess = False else: self._set_paths() if not self._fix_configuration(): self.rulesuccess = False if not self._fix_schedule(): self.rulesuccess = False except (KeyboardInterrupt, SystemExit): raise except: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
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 SecureIPV4(Rule): def __init__(self, config, environ, logger, statechglogger): '''Constructor''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 15 self.cmdhelper = CommandHelper(self.logger) self.rulename = "SecureIPV4" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() if self.environ.getostype() == "Mac OS X": self.networkTuning2 = self.__InitializeNetworkTuning2() else: self.networkTuning1 = self.__InitializeNetworkTuning1() self.networkTuning2 = self.__InitializeNetworkTuning2() self.guidance = ["NSA 2.5.1.1", "NSA 2.5.1.2"] self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': {'Mac OS X': ['10.15', 'r', '10.15.10']}} self.iditerator = 0 self.ch = CommandHelper(self.logger) def __InitializeNetworkTuning1(self): '''Private method to initialize the configurationitem object for the NetworkTuning1 bool. @return: configurationitem object instance''' datatype = 'bool' key = "NETWORKTUNING1" instructions = "Network Parameter Tuning. You should not need " + \ "to override this under normal circumstances." default = True ci = self.initCi(datatype, key, instructions, default) return ci def __InitializeNetworkTuning2(self): '''Private method to initialize the configurationitem object for the NetworkTuning2 bool. @return: configurationitem object instance''' key = "NETWORKTUNING2" instructions = "Additional network parameters. Set this to False " + \ "if you are running a router or a bridge. Also, in rare " + \ "cases, you may need to set this to False for VMware (if you " + \ "are using normal VMware routing, True should be fine)." default = True datatype = "bool" ci = self.initCi(datatype, key, instructions, default) return ci def report(self): '''Main parent report method that calls the sub report methods :returns: bool ''' try: self.detailedresults = "" if self.environ.getosfamily() == "linux": self.path = "/etc/sysctl.conf" self.tmpPath = "/etc/sysctl.conf.tmp" self.original = readFile(self.path, self.logger) rep1success = self.reportLinux1() rep2success = self.reportLinux2() elif self.environ.getosfamily() == "freebsd": self.path = "/etc/sysctl.conf" self.tmpPath = "/etc/sysctl.conf.tmp" self.original = readFile(self.path, self.logger) rep1success = self.reportFreebsd1() rep2success = self.reportFreebsd2() elif self.environ.getostype() == "Mac OS X": self.path = "/private/etc/sysctl.conf" self.tmpPath = "/private/etc/sysctl.conf.tmp" rep1success = True rep2success = self.reportMac() if rep1success and rep2success: self.compliant = True else: self.compliant = False except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant return self.rulesuccess def reportLinux1(self): '''Linux specific report method that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: lfc = {"net.ipv4.conf.all.secure_redirects": "0", "net.ipv4.conf.all.accept_redirects": "0", "net.ipv4.conf.all.rp_filter": "1", "net.ipv4.conf.all.log_martians": "1", "net.ipv4.conf.all.accept_source_route": "0", "net.ipv4.conf.default.accept_redirects": "0", "net.ipv4.conf.default.secure_redirects": "0", "net.ipv4.conf.default.rp_filter": "1", "net.ipv4.conf.default.accept_source_route": "0", "net.ipv4.tcp_syncookies": "1", "net.ipv4.icmp_echo_ignore_broadcasts": "1", "net.ipv4.tcp_max_syn_backlog": "4096"} editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, lfc, "present", "openeq") if not editor.report(): self.detailedresults += self.path + " is not configured " + \ "correctly for configuration item 1\n" compliant = False if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + "\n" compliant = False for key in lfc: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "Failed to get value of core dumps configuration with sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: output = self.ch.getOutputString() if output.strip() != key + " = " + lfc[key]: compliant = False self.detailedresults += "sysctl output has incorrect value: " + \ output + "\n" return compliant def reportLinux2(self): '''Linux specific report method2 that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): compliant = False else: lfc = {"net.ipv4.conf.default.send_redirects": "0", "net.ipv4.conf.all.send_redirects": "0", "net.ipv4.ip_forward": "0"} editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmpPath, lfc, "present", "openeq") if not editor.report(): self.detailedresults += self.path + " is not configured " + \ "correctly for configuration item 2\n" compliant = False for key in lfc: self.ch.executeCommand("/sbin/sysctl " + key) retcode = self.ch.getReturnCode() if retcode != 0: self.detailedresults += "Failed to get value of core dumps configuration with sysctl command\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) compliant = False else: output = self.ch.getOutputString() if output.strip() != key + " = " + lfc[key]: compliant = False self.detailedresults += "sysctl output has incorrect value: " + \ output + "\n" return compliant def reportMac(self): '''Mac specific report method1 that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. :returns: compliant :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - minor doc string adjustments; fixed permissions on file /etc/sysctl.conf (needs to be 0o600; was 0o644); try/except ''' compliant = True try: self.editor = None if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: mfc = {"net.inet.ip.forwarding": "0", "net.inet.ip.redirect": "0"} kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, mfc, intent, "closedeq") if not self.editor.report(): self.detailedresults += self.path + " is not " + \ "configured correctly\n" compliant = False else: self.detailedresults += self.path + " is " + \ "configured correctly\n" if not checkPerms(self.path, [0, 0, 0o600], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + ": Expected 644, found " + \ str(getOctalPerms(self.path)) + "\n" compliant = False except Exception: raise return compliant def reportFreebsd1(self): '''Freebsd specific report method1 that ensures the items in the file exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: ffc = {"net.inet.icmp.bmcastecho": "0", "net.inet.ip.redirect": "0", "net.inet.icmp.maskrepl": "0", "net.inet.ip.sourceroute": "0", "net.inet.ip.accept_sourceroute": "0", "net.inet.tcp.syncookies": "1"} kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, ffc, intent, "openeq") if not self.editor.report(): compliant = False if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + ": Expected 644, found " + \ str(getOctalPerms(self.path)) + "\n" compliant = False return compliant def reportFreebsd2(self): '''Freebsd specific report method1 that ensures the items in fileContents exist in /etc/sysctl.conf. Sets self.compliant to True if all items exist in the file. Returns True if successful in updating the file :returns: bool ''' compliant = True if not os.path.exists(self.path): self.detailedresults += self.path + " does not exist\n" compliant = False else: ffc = {"net.inet.ip.forwarding": "0", "net.inet.ip.fastforwarding": "0"} if not self.networkTuning1.getcurrvalue(): kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, ffc, intent, "closedeq") else: self.editor.setData(ffc) if not self.editor.report(): compliant = False if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Permissions are incorrect on " + \ self.path + ": Expected 644, found " + \ str(getOctalPerms(self.path)) + "\n" compliant = False return compliant def fix(self): '''Main parent fix method that calls the sub fix methods :returns: bool ''' try: self.detailedresults = "" success = True self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.environ.getosfamily() == "linux": if self.networkTuning1 and self.networkTuning2: success = self.fixLinux() else: self.detailedresults += "Required CI has not been initialized." success = False elif self.environ.getosfamily() == "freebsd": if self.networkTuning1 and self.networkTuning2: success = self.fixFreebsd() else: self.detailedresults += "Required CI has not been initialized." success = False elif self.environ.getosfamily() == "darwin": if self.networkTuning2: success = self.fixMac() else: self.detailedresults += "Required CI has not been initialized." success = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) success = False self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) self.rulesuccess = success return success def fixLinux(self): success = True created = False debug = "" sysctl = "/etc/sysctl.conf" tmpfile = sysctl + ".tmp" if not os.path.exists(sysctl): if createFile(sysctl, self.logger): created = True setPerms(sysctl, [0, 0, 0o644], self.logger) self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": sysctl} self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Could not create file " + self.path + \ "\n" self.formatDetailedResults("fix", False, self.detailedresults) if not checkPerms(sysctl, [0, 0, 0o644], self.logger): if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(sysctl, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False lfc = {} if self.networkTuning1 and self.networkTuning1.getcurrvalue(): lfc.update({"net.ipv4.conf.all.secure_redirects": "0", "net.ipv4.conf.all.accept_redirects": "0", "net.ipv4.conf.all.rp_filter": "1", "net.ipv4.conf.all.log_martians": "1", "net.ipv4.conf.all.accept_source_route": "0", "net.ipv4.conf.default.accept_redirects": "0", "net.ipv4.conf.default.secure_redirects": "0", "net.ipv4.conf.default.rp_filter": "1", "net.ipv4.conf.default.accept_source_route": "0", "net.ipv4.tcp_syncookies": "1", "net.ipv4.icmp_echo_ignore_broadcasts": "1", "net.ipv4.tcp_max_syn_backlog": "4096"}) if self.networkTuning2 and self.networkTuning2.getcurrvalue(): lfc.update({"net.ipv4.conf.default.send_redirects": "0", "net.ipv4.conf.all.send_redirects": "0", "net.ipv4.ip_forward": "0"}) self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", sysctl, tmpfile, lfc, "present", "openeq") if not self.editor.report(): if self.editor.fixables: # If we did not create the file, set an event ID for the # KVEditor's undo event to record the file write if not created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.fix(): success = False debug = "KVEditor fix of " + self.path + \ " was not successful\n" self.logger.log(LogPriority.DEBUG, debug) elif not self.editor.commit(): success = False debug = "KVEditor commit to " + \ self.path + " was not successful\n" self.logger.log(LogPriority.DEBUG, debug) # permissions on file are incorrect if not checkPerms(self.path, [0, 0, 0o644], self.logger): if not setPerms(self.path, [0, 0, 0o644], self.logger): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False resetsecon(self.path) # here we also check the output of the sysctl command for each key # to cover all bases for key in lfc: if self.ch.executeCommand("/sbin/sysctl " + key): output = self.ch.getOutputString().strip() if not re.search(lfc[key] + "$", output): undovalue = output[-1] self.ch.executeCommand("/sbin/sysctl -w " + key + "=" + lfc[key]) retcode = self.ch.getReturnCode() if retcode != 0: success = False self.detailedresults += "Failed to set " + key + " = " + lfc[key] + "\n" errmsg = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg) else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) command = "/sbin/sysctl -w " + key + "=" + undovalue event = {"eventtype": "commandstring", "command": command} self.statechglogger.recordchgevent(myid, event) else: self.detailedresults += "Unable to get value for " + key + "\n" success = False # at the end do a print and ignore any key errors to ensure # the new values are read into the kernel self.ch.executeCommand("/sbin/sysctl -q -e -p") retcode2 = self.ch.getReturnCode() if retcode2 != 0: success = False self.detailedresults += "Failed to load new sysctl configuration from config file\n" errmsg2 = self.ch.getErrorString() self.logger.log(LogPriority.DEBUG, errmsg2) return success def fixMac(self): '''run fix actions for mac systems :returns: success :rtype: bool @author: dwalker @change: Breen Malmberg - 1/10/2017 - added doc string; try/except; fixed perms for file sysctl.conf (should be 0o600; was 0o644) ''' success = True try: if not os.path.exists(self.path): if createFile(self.path, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.path} self.statechglogger.recordchgevent(myid, event) else: return False if self.networkTuning2.getcurrvalue(): if not self.editor: mfc = {"net.inet.ip.forwarding": "0", "net.inet.ip.redirect": "0"} kvtype = "conf" intent = "present" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, self.path, self.tmpPath, mfc, intent, "closedeq") if not self.editor.report(): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): if not self.editor.commit(): success = False self.detailedresults += "KVEditor commit to " + \ self.path + " was not successful\n" else: success = False self.detailedresults += "KVEditor fix of " + self.path + \ " was not successful\n" resetsecon(self.path) if not checkPerms(self.path, [0, 0, 0o600], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o600], self.logger, self.statechglogger, myid): self.detailedresults += "Could not set permissions on " + \ self.path + "\n" success = False except Exception: raise return success def fixFreebsd(self): if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o644], self.logger, self.statechglogger, myid): return False if self.networkTuning1.getcurrvalue() or \ self.networkTuning2.getcurrvalue(): if self.editor.fixables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.fix(): return False elif not self.editor.commit(): return False os.chown(self.path, 0, 0) os.chmod(self.path, 0o644) resetsecon(self.path) cmd = ["/usr/sbin/service", "sysctl", "restart"] self.ch.executeCommand(cmd) if self.ch.getReturnCode() != 0: self.detailedresults = "Unable to restart sysctl\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return False else: return True else: return True
class DisableGDMAutoLogin(Rule): """ The GNOME Display Manager (GDM) can allow users to automatically login without user interaction or credentials. User should always be required to authenticate themselves to the system that they are authorized to use. """ 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 = 192 self.rulename = 'DisableGDMAutoLogin' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.applicable = {'type': 'white', 'family': ['linux']} self.guidance = ['RHEL 7 STIG CCE-80104-3'] datatype = 'bool' key = 'DISABLEGDMAUTOLOGIN' instructions = """To disable this rule set the value of DISABLEGDMAUTOLOGIN to False.""" default = True self.PrimaryCI = self.initCi(datatype, key, instructions, default) def report(self): """ :return: self.compliant :rtype: bool """ self.detailedresults = "" self.compliant = True basedir = "/etc/gdm/" try: # if there is no gdm folder then there is likely no reason to configure it if not os.path.isdir(basedir): self.logger.log(LogPriority.DEBUG, "Rule does not apply to this system in its current state") return self.compliant kvtype = "tagconf" path = basedir + "custom.conf" tmppath = path + ".stonixtmp" data = {"daemon": {"AutomaticLoginEnable": "False", "TimedLoginEnable": "False"}} intent = "present" delimiter = "closedeq" self.gdm_editor = KVEditorStonix(self.statechglogger, self.logger, kvtype, path, tmppath, data, intent, delimiter) if not self.gdm_editor.report(): self.compliant = False try: self.detailedresults += "\nThe following config options in " + str(path) + " are incorrect:\n" + "\n".join(self.gdm_editor.fixables) except: self.detailedresults += "\nOne or more config options in " + str(path) + " are incorrect" 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): """ :return: self.rulesuccess :rtype: bool """ self.detailedresults = "" self.rulesuccess = True basedir = "/etc/gdm/" try: # if there is no gdm folder then there is likely no reason to configure it if not os.path.isdir(basedir): self.logger.log(LogPriority.DEBUG, "Rule does not apply to this system in its current state") return self.rulesuccess if not self.gdm_editor.fix(): self.rulesuccess = False self.logger.log(LogPriority.DEBUG, "KVEditor fix failed") elif not self.gdm_editor.commit(): self.rulesuccess = False self.logger.log(LogPriority.DEBUG, "KVEditor commit failed") if not self.rulesuccess: self.detailedresults += "\nFailed to disable GDM Auto Login" 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 DisableThumbnailers(Rule): """disable the thumbnail creation feature in nautilus/gnome""" def __init__(self, config, environ, logdispatcher, statechglogger): """ :param config: :param environ: :param logdispatcher: :param statechglogger: """ Rule.__init__(self, config, environ, logdispatcher, statechglogger) self.logger = logdispatcher self.rulenumber = 111 self.mandatory = True self.applicable = {'type': 'white', 'family': ['linux', 'solaris', 'freebsd']} self.formatDetailedResults("initialize") self.guidance = ["NSA 2.2.2.6"] self.rulename = "DisableThumbnailers" self.rulesuccess = True self.sethelptext() datatype = 'bool' key = 'DISABLETHUMBNAILERS' instructions = "To disable this rule set the value of DISABLETHUMBNAILERS to False." default = True self.ci = self.initCi(datatype, key, instructions, default) def localize(self): """ set paths based on what versions of which utilities exist on the system :return: void """ self.gsettings = "/usr/bin/gsettings" self.gconf = "/usr/bin/gconftool-2" self.dconf = "/usr/bin/dconf" self.getcmd = "" self.setcmd = "" self.updatecmd = "" packages = ["gnome", "gdm", "gdm3", "gnome3"] self.lockfile = "/etc/dconf/db/local.d/locks/stonix-thumbnailers" self.lockthumbnails = {"/org/gnome/desktop/thumbnailers/disable-all":""} if os.path.exists(self.gsettings): self.getcmd = self.gsettings + " get org.gnome.desktop.thumbnailers disable-all" self.setcmd = self.gsettings + " set org.gnome.desktop.thummailerss disable-all true" elif os.path.exists(self.gconf): self.getcmd = self.gconf + " --get /schemas/org/gnome/desktop/thumbnailers/disable_all" self.setcmd = self.gconf + " --type bool --set /schemass/org/gnome/desktop/thumbnailers/disable_all true" if os.path.exists(self.dconf): self.updatecmd = self.dconf + " update" self.ch = CommandHelper(self.logger) self.ph = Pkghelper(self.logger, self.environ) self.gnome_installed = False if self.environ.getosname() != "Mac OS": if [self.ph.check(p) for p in packages]: self.gnome_installed = True def report(self): """check the gdm/gnome setting for thumbnailers to determine if it is off or on. report compliant if it is off, non-compliant if it is on. :return: self.compliant :rtype: bool """ self.compliant = True self.ch = CommandHelper(self.logger) self.ph = Pkghelper(self.logger, self.environ) self.detailedresults = "" try: self.localize() if self.gnome_installed: # This portion of the code is relevant to user context if self.environ.geteuid() != 0: self.ch.executeCommand(self.getcmd) if not self.ch.findInOutput("true"): self.compliant = False self.detailedresults += "\nGnome thumbnailers are enabled" # This portion of the code is relevant to root context else: if not self.checkLockFile(): self.compliant = False else: self.logger.log(LogPriority.DEBUG, "Gnome is not installed. Nothing to check.") except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.compliant def checkLockFile(self): """ :return: compliant :rtype: bool """ compliant = True kvtype = "conf" path = self.lockfile tmppath = self.lockfile + ".stonixtmp" intent = "present" conftype = "space" self.lock_editor = KVEditorStonix(self.statechglogger, self.logdispatch, kvtype, path, tmppath, self.lockthumbnails, intent, conftype) if not self.lock_editor.report(): compliant = False if self.lock_editor.fixables: self.detailedresults += "\nThe following required configuration lines were not found in the gnome lock file:\n" + "\n".join(self.lock_editor.fixables) else: self.detailedresults += "\nOne or more required configuration lines were not found in the gnome lock file" return compliant def fix(self): """ set the value of schema thumbnailers disable-all to true and create the thumbnailers lock file if it doesn't exist :return: self.rulesuccess :rtype: bool """ self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 try: if self.ci.getcurrvalue(): # This portion of the code is run in user context if self.environ.geteuid() != 0: if not self.setValue(): self.rulesuccess = False else: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "commandstring", "command": re.sub("true", "false", self.setcmd)} self.statechglogger.recordchgevent(myid, event) # This portion of the code has to be run in root context else: if not self.setLockFile(): self.rulesuccess = False if os.path.exists(self.dconf): self.ch.executeCommand(self.updatecmd) if self.ch.getReturnCode() == 0: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "commandstring", "command": self.updatecmd} self.statechglogger.recordchgevent(myid, event) else: self.logger.log(LogPriority.DEBUG, "CI not enabled. Fix was not performed.") except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logger.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def setValue(self): """ set the thumbnailers disable-all configuration value to true :return: success :rtype: bool """ success = True self.ch.executeCommand(self.setcmd) retcode = self.ch.getReturnCode() if retcode != 0: success = False errstr = self.ch.getErrorString() self.detailedresults += "\n" + errstr return success def setLockFile(self): """ create the lock file for disable thumbnailers (if running in root context) :return: success - True if file is created; False if not :rtype: bool """ success = True if not os.path.isdir("/etc/dconf/db/local.d/locks"): try: os.makedirs("/etc/dconf/db/local.d/locks", 0o755) except Exception: pass try: if not self.lock_editor.fix(): self.detailedresults += "\nFailed to lock thumbnailers setting" success = False elif not self.lock_editor.commit(): self.detailedresults += "\nFailed to lock thumbnailers setting" success = False except Exception: success = False if success: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "conf", "filepath": self.lockfile} self.statechglogger.recordchgevent(myid, event) return success
class RestrictAdminSSH(Rule): def __init__(self, config, environ, logger, statechglogger): Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 269 self.rulename = "RestrictAdminSSH" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() datatype = 'bool' key = 'RESTRICTADMINSSH' instructions = '''To disable this rule set the value of RESTRICTADMINSSH to False.''' default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = [] self.ssh = {"DenyGroups": "admin"} self.iditerator = 0 self.applicable = { 'type': 'white', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } def usesSip(self): '''Determines whether this is Mac OS X >= v10.11 @author: Eric Ball :returns: True if this is Mac OS X >= v10.11 ''' if self.environ.getosfamily() == "darwin": versplit = self.environ.getosver().split(".") verlist = [] for num in versplit: verlist.append(int(num)) if verlist[0] >= 10 and verlist[1] >= 11: return True return False def report(self): try: results = "" compliant = True path1 = "/private/etc/sshd_config" path2 = "/private/etc/ssh/sshd_config" if self.usesSip(): if os.path.exists(path2): self.path = path2 elif os.path.exists(path1): self.path = path1 else: compliant = False results += "Could not find path to sshd_config file\n" else: if os.path.exists(path1): self.path = path1 elif os.path.exists(path2): self.path = path2 else: compliant = False results += "Could not find path to sshd_config file\n" self.tmppath = self.path + ".tmp" if os.path.exists(self.path): self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.path, self.tmppath, self.ssh, "present", "space") if not self.editor.report(): compliant = False results += "Settings in " + self.path + " are not " + \ "correct\n" if not checkPerms(self.path, [0, 0, 0o644], self.logger): compliant = False results += self.path + " permissions are incorrect\n" self.detailedresults = results self.compliant = compliant except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): try: if not self.ci.getcurrvalue(): return results = "" success = True # 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 os.path.exists(self.path): if not checkPerms(self.path, [0, 0, 0o644], self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.path, [0, 0, 0o644], self.logger, self.statechglogger, myid): success = False results += "Could not set permissions on " + \ self.path + "\n" if self.editor.fixables or self.editor.removeables: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if not self.editor.fix(): debug = "kveditor fix did not run successfully\n" self.logger.log(LogPriority.DEBUG, debug) success = False elif not self.editor.commit(): debug = "kveditor commit did not run successfully\n" self.logger.log(LogPriority.DEBUG, debug) success = False os.chown(self.path, 0, 0) os.chmod(self.path, 0o644) resetsecon(self.path) else: success = False results += "Could not find path to sshd_config\n" self.detailedresults = results self.rulesuccess = success except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False success = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", success, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class DisablePrelinking(Rule): def __init__(self, config, enviro, logger, statechglogger): Rule.__init__(self, config, enviro, logger, statechglogger) self.logger = logger self.rulenumber = 89 self.rulename = "DisablePrelinking" self.sethelptext() self.formatDetailedResults("initialize") self.mandatory = True self.applicable = {'type': 'white', 'family': ['linux']} # Configuration item instantiation datatype = "bool" key = "DISABLEPRELINKING" instructions = "To disable this rule, set the value of " + \ "DISABLEPRELINKING to false." default = True self.ci = self.initCi(datatype, key, instructions, default) self.guidance = ["CCE-RHEL7-CCE-TBA 2.1.3.1.2"] self.iditerator = 0 self.ch = CommandHelper(self.logger) if re.search("debian|ubuntu", self.environ.getostype().lower()): self.isDebian = True else: self.isDebian = False def report(self): try: if self.isDebian: path = "/etc/default/prelink" else: path = "/etc/sysconfig/prelink" self.path = path prelink = "/usr/sbin/prelink" self.compliant = True self.detailedresults = "" if os.path.exists(path): tmppath = path + ".tmp" data = {"PRELINKING": "no"} self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", path, tmppath, data, "present", "closedeq") if not self.editor.report(): self.compliant = False self.detailedresults += path + " does not have the " + \ "correct settings.\n" else: self.compliant = False self.detailedresults += path + " does not exist.\n" if os.path.exists(prelink): self.ch.executeCommand([prelink, "-p"]) output = self.ch.getOutputString() splitout = output.split() try: if len(splitout) > 0: numPrelinks = int(splitout[0]) # Potential ValueError if numPrelinks > 0: self.compliant = False self.detailedresults += "There are currently " + \ str(numPrelinks) + " prelinked binaries.\n" except ValueError: debug = "Unexpected result from " + prelink + ". This " + \ "does not affect compliance." self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += debug + "\n" except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def fix(self): try: if not self.ci.getcurrvalue(): return success = True path = self.path tmppath = path + ".tmp" prelinkCache = "/etc/prelink.cache" self.detailedresults = "" self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if not os.path.exists(path): if createFile(path, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": path} self.statechglogger.recordchgevent(myid, event) else: success = False self.detailedresults += "Failed to create file: " + \ path + "\n" if writeFile(tmppath, "PRELINKING=no", self.logger): os.rename(tmppath, path) resetsecon(path) else: success = False self.detailedresults += "Failed to write settings " + \ "to file: " + path + "\n" elif not self.editor.report(): if self.editor.fix(): if self.editor.commit(): self.detailedresults += "Changes successfully " + \ "committed to " + path + "\n" else: success = False self.detailedresults += "Changes could not be " + \ "committed to " + path + "\n" else: success = False self.detailedresults += "Could not fix file " + path + "\n" # Although the guidance and documentation recommends using "prelink # -ua" command, testing has shown this command to be completely # unreliable. Instead, the prelink cache will be removed entirely. if os.path.exists(prelinkCache): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.statechglogger.recordfiledelete(prelinkCache, myid) os.remove(prelinkCache) self.rulesuccess = success except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
class ConfigureSudo(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.rulenumber = 56 self.rulename = "ConfigureSudo" self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.guidance = ["NSA 2.3.1.3"] self.applicable = { 'type': 'white', 'family': 'linux', 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } datatype2 = 'bool' key2 = 'CONFIGURESUDO' instructions2 = """To disable this rule set the value of CONFIGURESUDO to False.""" default2 = True self.primary_ci = self.initCi(datatype2, key2, instructions2, default2) self.localization() def localization(self): """ set up class variables, specific to OS type """ self.logger.log(LogPriority.DEBUG, "Running localization() method...") ostype = self.environ.getostype() osname = self.environ.getosname() if ostype == "darwin": self.sudoers_file = "/private/etc/sudoers" else: self.sudoers_file = "/etc/sudoers" if ostype == "darwin": self.sudoers_opts = { "root": "ALL = (ALL) ALL", "%admin": "ALL = (ALL) ALL" } elif osname == "Debian": self.sudoers_opts = { "root": "ALL=(ALL:ALL) ALL", "%sudo": "ALL=(ALL:ALL) ALL" } elif osname == "Ubuntu": self.sudoers_opts = { "root": "ALL=(ALL:ALL) ALL", "%admin": "ALL=(ALL) ALL", "%sudo": "ALL=(ALL:ALL) ALL" } else: self.sudoers_opts = { "root": "ALL=(ALL) ALL", "%wheel": "ALL=(ALL) ALL" } def report(self): """ ConfigureScreenLocking.report() method to report whether system is configured with a sudoers group. @author: dwalker :param self: essential if you override this definition :returns: bool - True if system is compliant, False if it isn't """ try: self.detailedresults = "" self.compliant = True if not self.sudoers_file: self.detailedresults += "\nCan't find sudoers file!" self.compliant = False elif not os.path.isfile(self.sudoers_file): self.detailedresults += "\nCan't find sudoers file!" self.compliant = False else: self.sudoers_backup = self.sudoers_file + ".stonixbak" tmppath = self.sudoers_file + ".stonixtmp" self.sudoers_editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.sudoers_file, tmppath, self.sudoers_opts, "present", "space") if not self.sudoers_editor.report(): if self.sudoers_editor.fixables: self.detailedresults += "\nThe following configuration options are missing or incorrect in the sudoers file:\n" + "\n".join( self.sudoers_editor.fixables) self.compliant = False else: self.detailedresults += "\nOne or more configuration options are missing or incorrect in the sudoers file" self.compliant = False 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): """ Fix method that writes specified or default sudo group to sudoers file if not present from the report method :param self: essential if you override this definition :returns: bool - True if fix is successful, False if it isn't """ self.detailedresults = "" self.rulesuccess = True try: if not self.primary_ci.getcurrvalue(): self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess if not os.path.isfile(self.sudoers_backup): shutil.copy2(self.sudoers_file, self.sudoers_backup) if not self.sudoers_editor.fix(): self.detailedresults += "\nFailed to update sudoers configuration" self.rulesuccess = False elif not self.sudoers_editor.commit(): self.detailedresults += "\nFailed to update sudoers configuration" self.rulesuccess = False else: self.detailedresults += "\nSuccessfully updated sudoers configuration" 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): """ Revert all fix actions taken by this rule """ try: if os.path.isfile(self.sudoers_backup): os.rename(self.sudoers_backup, self.sudoers_file) resetsecon(self.sudoers_file) os.remove(self.sudoers_backup) self.detailedresults += "\nOriginal files/settings restored." else: self.detailedresults += "\nNo files/settings to restore." except (KeyboardInterrupt, SystemExit): raise except: self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults)
class ConfigureScreenLocking(RuleKVEditor): def __init__(self, config, environ, logdispatcher, statechglogger): RuleKVEditor.__init__(self, config, environ, logdispatcher, statechglogger) self.logger = logdispatcher self.rulenumber = 74 self.rulename = "ConfigureScreenLocking" self.mandatory = True self.rootrequired = False self.applicable = { 'type': 'white', 'family': ['linux', 'solaris', 'freebsd'], 'os': { 'Mac OS X': ['10.15', 'r', '10.15.10'] } } self.effectiveUserID = self.environ.geteuid() self.sethelptext() self.formatDetailedResults("initialize") self.guidance = ["NSA 2.3.5.6.1"] if self.environ.getosfamily() == "darwin": if self.effectiveUserID == 0: self.addKVEditor( "SystemAskForPasswordSystem", "defaults", "/Library/Preferences/com.apple.screensaver", "", {"askForPassword": ["1", "-int 1"]}, "present", "", "Ask for password when system wide " + "screen saver is on.", None, False, {"askForPassword": ["0", "-int 0"]}) self.addKVEditor( "SystemSetScreenSaverIdleTime", "defaults", "/Library/Preferences/com.apple.screensaver", "", {"idleTime": ["840", "-int 840"]}, "present", "", "Sets system the screen saver to " + "activate after 14 minutes of idleTime.", None, False, { "idleTime": [ "The domain/default pair of ( .+" + "com\.apple\.screensaver, " + "idleTime) does not " + "exist", None ] }) self.addKVEditor( "SystemLoginWindowIdleTime", "defaults", "/Library/Preferences/com.apple.screensaver", "", {"loginWindowIdleTime": ["840", "-int 840"]}, "present", "", "Sets system LoginWindowIdleTime to " + "14 minutes.", None, False, { "loginWindowIdleTime": [ "The domain/default pair of ( .+" + "com\.apple\.screensaver, " + "loginWindowIdleTime) does not " + "exist", None ] }) else: self.addKVEditor( "AskForPassword", "defaults", "~/Library/Preferences/com.apple.screensaver", "", {"askForPassword": ["1", "-int 1"]}, "present", "", "Ask for password when screen saver is on.", None, False, {"askForPassword": ["0", "-int 0"]}) self.addKVEditor( "AskForPasswordDelay", "defaults", "~/Library/Preferences/com.apple.screensaver", "", {"askForPasswordDelay": ["0", "-int 0"]}, "present", "", "Delay asking for password by 0 seconds.", None, False, { "askForPasswordDelay": [ "The domain/default pair of ( .+" + "com\.apple\.screensaver, " + "askForPassword) does not " + "exist", None ] }) else: datatype = 'bool' key = 'CONFIGURESCREENLOCKING' instructions = "To prevent the configuration of idle screen locking, set the value of CONFIGURESCREENLOCKING to False." default = True self.ci = self.initCi(datatype, key, instructions, default) #self.gnomeInst variable determines in the fixGnome method #at the beginning if we even proceed. If False we don't proceed #and is fine. Gets set to True in reportGnome method if #either gconftool-2 or gsettings binaries exist. self.gnomeInst = False self.useGconf = True self.iditerator = 0 self.cmdhelper = CommandHelper(self.logger) self.ph = Pkghelper(self.logger, self.environ) self.ch = CommandHelper(self.logger) self.euid = self.environ.geteuid() def report(self): """ConfigureScreenLocking.report() method to report whether system is configured to screen locking NSA standards. If the system is linux, although many desktops are available, this rule will only check the two most popular desktops, KDE, and Gnome. @author: dwalker :param self: essential if you override this definition :return: self.compliant :rtype: bool """ self.detailedresults = "" self.compliant = True try: compliant = True self.detailedresults = "" if self.environ.osfamily == 'linux': if not self.check_package(): compliant = False if self.euid != 0: self.detailedresults += "\nThis is expected if not running with elevated privileges since STONIX " \ "requires elevated privileges to install packages. Please run STONIX with elevated privileges " \ "and run the fix for this rule again, to fix this issue." if self.ph.check("gdm") or self.ph.check("gdm3"): self.gnomeInstalled = True if not self.reportGnome(): self.detailedresults += "\nGnome GUI environment " + \ "does not appear to be correctly configured " + \ "for screen locking parameters." compliant = False else: self.detailedresults += "\nGnome GUI environment " + \ "appears to be correctly configured for " + \ "screen locking parameters." else: self.gnomeInstalled = False self.detailedresults += "\nGnome not installed. No need to configure for gnome." if self.ph.check("kdm") or self.ph.check("kde-workspace") or \ self.ph.check("sddm") or self.ph.check("patterns-kde-kde_yast"): self.kdeInstalled = True if not self.reportKde(): self.detailedresults += "\nKDE GUI environment " + \ "does not appear to be correctly configured " + \ "for screen locking parameters." compliant = False else: self.detailedresults += "\nKDE GUI environment " + \ "appears to be correctly configured for " + \ "screen locking parameters." else: self.kdeInstalled = False self.detailedresults += "\nKDE not installed. No need to configure for kde." elif self.environ.getosfamily() == "darwin": compliant = self.reportMac() self.compliant = compliant except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("report", self.compliant, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.compliant def check_package(self): """ for rhel 7 and similar generation linux rpm-based systems, the 'screen' package is required by the STIG for rhel 8 and beyond (and similar), the 'tmux' package is required. :return: installed :rtype: bool """ self.screen_pkg = "" installed = True if self.ph.checkAvailable("tmux"): self.screen_pkg = "tmux" elif self.ph.check("tmux"): self.screen_pkg = "tmux" else: self.screen_pkg = "screen" if not self.ph.check(self.screen_pkg): self.detailedresults += "\nThe required package: " + str( self.screen_pkg) + " is not installed" installed = False else: self.detailedresults += "\nThe required package: " + str( self.screen_pkg) + " is installed" return installed def reportMac(self): """Mac osx specific report submethod @author: dwalker :param self: essential if you override this definition :returns: bool - True if system is compliant, False if it isn't """ success = RuleKVEditor.report(self, True) return success def reportGnome(self): """determines if gnome is installed, if so, checks to see if the return value strings from running the gconftool-2 command are correct. Gconftool-2 command only works in root mode so if not root do not audit gnome and just return true @author: dwalker :param self: essential if you override this definition :returns: bool @change: dwalker - mass refactor, added comments """ compliant = True gsettings = "/usr/bin/gsettings" gconf = "/usr/bin/gconftool-2" self.gesttingsidletime = "" self.gconfidletime = "" self.setcmds = "" #may need to change code in the future to make gconf and #gsettings mutually exclusive with if elif else self.gnomeInstalled = False #if they are found to conflict with each other if os.path.exists(gconf): getcmds = { "/apps/gnome-screensaver/idle_activation_enabled": "true", "/apps/gnome-screensaver/lock_enabled": "true", "/apps/gnome-screensaver/mode": "blank-only", "/desktop/gnome/session/idle_delay": "15" } #make a copy of the getcmds dictionary tempdict = dict(getcmds) #check each value using the gconftool-2 command for cmd in getcmds: #set the get command for each value cmd2 = gconf + " --get " + cmd #execute the command self.cmdhelper.executeCommand(cmd2) #get output and error output output = self.cmdhelper.getOutput() error = self.cmdhelper.getError() #the value is set if output: #check this one separately since the value is #also able to be less than 15 mins and be compliant if cmd == "/desktop/gnome/session/idle_delay": if int(output[0].strip()) > 15: self.detailedresults += "Idle delay value " + \ "is not 15 or lower (value: " + \ output[0].strip() + ")\n" #if they have a value less than 15 mins this is ok #and we set the self.gconfidletime variable which is #used during setting gconf values in the fixGnome method elif int(output[0].strip()) < 15: self.gconfidletime = output[0].strip() del tempdict[cmd] #value is correct so remove it from the tempdic else: del tempdict[cmd] #check if value is correct with associated key elif output[0].strip() != getcmds[cmd]: self.detailedresults += cmd2 + " didn't produce the " + \ "desired value after being run which is " + \ getcmds[cmd] + "\n" #value is correct so remove it from the tempdict else: del tempdict[cmd] #else the value isn't set elif error: self.detailedresults += "There is no value set for:\n" + \ cmd2 + "\n" #if tempdict still has leftover values then #there were values that weren't correctly set #and is non compliant if tempdict: compliant = False #set self.setcmds variable to be a copy of tempdict which #we use later in the fix to determine if we fix this portion #of the rule or not self.setcmds = tempdict if os.path.exists(gsettings): self.gnomeInst = True #use gsettings command to see if the correct values are set #for each key in getcmds dictionary getcmds = { " get org.gnome.desktop.screensaver " + "idle-activation-enabled": "true", " get org.gnome.desktop.screensaver lock-enabled": "true", " get org.gnome.desktop.screensaver lock-delay": "0", " get org.gnome.desktop.screensaver picture-opacity": "100", " get org.gnome.desktop.screensaver picture-uri": "''", " get org.gnome.desktop.session idle-delay": "900" } #run each gsettings get command for each key and get value for cmd in getcmds: cmd2 = gsettings + cmd self.cmdhelper.executeCommand(cmd2) output = self.cmdhelper.getOutput() error = self.cmdhelper.getError() if output: #check this one separately since the value is #also able to be less than 900 secs and be compliant if cmd == " get org.gnome.desktop.session idle-delay": try: splitOut = output[0].split() if len(splitOut) > 1: num = splitOut[1] else: num = splitOut[0] if int(num) > 900: compliant = False self.detailedresults += "Idle delay value " + \ "is not 900 seconds (value: " +\ num + ")\n" #if they have a value less than 900 secs this is ok #and we set the self.gsettingsidletime variable which is #used during setting gsettings values in the fixGnome method # elif int(num) < 900: # self.gsettingsidletime = num elif int(num) == 0: compliant = False self.detailedresults += "Idle delay set " + \ "to 0, meaning it is disabled.\n" else: self.gsettingsidletime = "900" except ValueError: self.detailedresults += "Unexpected result: " + \ '"' + cmd2 + '" output was not a number\n' compliant = False elif cmd == " get org.gnome.desktop.screensaver lock-delay": try: splitOut = output[0].split() if len(splitOut) > 1: num = splitOut[1] else: num = splitOut[0] if int(num) != 0: compliant = False self.detailedresults += "Lock delay is not " + \ "set to 0\n" except ValueError: self.detailedresults += "Unexpected result: " + \ '"' + cmd2 + '" output was not a number\n' compliant = False elif output[0].strip() != getcmds[cmd]: self.detailedresults += '"' + cmd2 + \ "\" produced value: " + output[0].strip() + \ " instead of the desired value: " + getcmds[cmd] + "\n" compliant = False elif error: if re.search("No such key", error[0], re.I): continue self.detailedresults += "There is no value set for:" + \ cmd2 + "\n" compliant = False if self.environ.geteuid() == 0: # instantiate a kveditor to ensure self.dconfsettings file # contains correct contents self.dconfsettings = "/etc/dconf/db/local.d/local.key" if os.path.exists(self.dconfsettings): self.dconfdata = { "org/gnome/desktop/screensaver": { "idle-activation-enabled": "true", "lock-enabled": "true", "lock-delay": "0", "picture-opacity": "100", "picture-uri": "\'\'" }, "org/gnome/desktop/session": { "idle-delay": "uint32 900" } } self.kveditordconf = KVEditorStonix( self.statechglogger, self.logger, "tagconf", self.dconfsettings, self.dconfsettings + ".tmp", self.dconfdata, "present", "closedeq") if not self.kveditordconf.report(): self.detailedresults += self.dconfsettings + \ " doesn't cotain correct contents\n" compliant = False else: compliant = False self.detailedresults += self.dconfsettings + " not found\n" # check self.dconfuserprofile file to ensure existence # and/or correct contents self.dconfuserprofile = "/etc/dconf/profile/user" self.userprofilecontent = "user-db:user\n" + \ "system-db:local" if os.path.exists(self.dconfuserprofile): contents = readFile(self.dconfuserprofile, self.logger) contentstring = "" for line in contents: contentstring += line if not re.search(self.userprofilecontent, contentstring): compliant = False self.detailedresults += "Correct contents weren't " + \ "found in " + self.dconfuserprofile + "\n" else: compliant = False self.detailedresults += self.dconfuserprofile + " not " + \ "found\n" # the following file locks the settings we earlier set with the # gsettings command so that they don't default upon logout and/or # reboot. Check the file for the correct contents self.dconfsettingslock = "/etc/dconf/db/local.d/locks/stonix-settings.conf" self.dconflockdata = [ "/org/gnome/desktop/session/idle-delay", "/org/gnome/desktop/screensaver/idle-activation-enabled", "/org/gnome/desktop/screensaver/lock-enabled", "/org/gnome/desktop/screensaver/lock-delay", "/org/gnome/desktop/screensaver/picture-uri" ] locks_missing = [] if os.path.exists(self.dconfsettingslock): contents = readFile(self.dconfsettingslock, self.logger) for line in contents: if line.strip() not in self.dconflockdata: locks_missing.append(line.strip()) if locks_missing: self.detailedresults += "\nThe following settings should be locked from changes by the user but aren't:\n" + "\n".join( locks_missing) compliant = False else: compliant = False self.detailedresults += "\nGnome settings lock file not found" return compliant def reportKde(self): """determines if kde is installed, if so, ensures kde is configured by enabling screenlocking, automatically going black after 14 minutes and if inactivity ensues after 14 minutes, screen fully locks after 1 additional minute of inactivity for a total of 15 minutes activity @author: dwalker :param self: essential if you override this definition :returns: bool """ self.kdefix = {} if self.ph.manager == "apt-get" or self.ph.manager == "zypper": self.rcpath = ".config/kscreenlockerrc" self.kdeprops = { "Daemon": { "Autolock": "true", "LockGrace": "60", "LockOnResume": "true", "Timeout": "14" } } else: self.rcpath = ".kde/share/config/kscreensaverrc" self.kdeprops = { "ScreenSaver": { "Enabled": "true", "Lock": "true", "LockGrace": "60000", "Timeout": "840" } } if self.environ.geteuid() == 0: contents = readFile("/etc/passwd", self.logger) for line in contents: temp = line.split(":") try: username = temp[0] homepath = temp[5] except IndexError: self.logdispatch.log(LogPriority.DEBUG, [ 'ConfigureScreenLocking', 'IndexError processing ' + str(temp) ]) continue kdeparent1 = os.path.join(homepath, ".kde") kdeparent2 = os.path.join(homepath, ".kde4") kdefile = os.path.join(homepath, self.rcpath) if not os.path.exists(kdeparent1) and not os.path.exists( kdeparent2): # User does not user KDE continue elif not os.path.exists(kdefile): self.kdefix[username] = homepath self.detailedresults += kdefile + " not found for " + \ str(username) + "\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) continue elif not self.searchFile(kdefile): self.detailedresults += "Did not find " + \ "required contents " + "in " + username + \ "'s " + kdefile + "\n" self.kdefix[username] = homepath if self.kdefix: self.detailedresults += "The following users don't " + \ "have kde properly configured for " + \ "screen locking:\n" for user in self.kdefix: self.detailedresults += user + "\n" return False else: return True else: kdeparent1 = os.path.join(self.environ.geteuidhome(), ".kde") kdeparent2 = os.path.join(self.environ.geteuidhome(), ".kde4") kdefile = os.path.join(self.environ.geteuidhome(), self.rcpath) if not os.path.exists(kdeparent1) and not os.path.exists( kdeparent2): self.detailedresults += "Current user doesn't use kde. " + \ "No need to configure.\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return True else: if not os.path.exists(kdefile): self.detailedresults += "Your " + kdefile + \ " file doesn't exist.\n" return False elif not self.searchFile(kdefile): self.detailedresults += "Did not find " + \ "required contents in " + kdefile + "\n" return False else: return True def fix(self): """ConfigureScreenLocking.fix() method to correct screen locking @author: dwalker :param self: essential if you override this definition :returns: bool - True if fix is successful, False if it isn't """ self.detailedresults = "" self.rulesuccess = True self.iditerator = 0 try: self.detailedresults = "" success = True if self.environ.getosfamily() == "linux": if not self.ci.getcurrvalue(): self.detailedresults += "Rule not enabled so nothing was done\n" self.logger.log( LogPriority.DEBUG, 'Rule was not enabled, so nothing was done') return if self.euid == 0: if not self.ph.install(self.screen_pkg): success = False self.logger.log( LogPriority.DEBUG, "Failed to install required package 'screen'") else: self.detailedresults += "\nNote: Some required packages may not be installed because STONIX is not running with elevated privileges." if self.gnomeInstalled: if not self.fixGnome(): success = False if self.kdeInstalled: if not self.fixKde(): success = False elif self.environ.getosfamily() == "darwin": if self.environ.geteuid() == 0: self.iditerator = 0 eventlist = self.statechglogger.findrulechanges( self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) success = self.fixMac() self.rulesuccess = success except (KeyboardInterrupt, SystemExit): raise except Exception: self.detailedresults += "\n" + traceback.format_exc() self.logger.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def fixKde(self): """This method checks if the kde screenlock file is configured properly. Please note, this rule may fail if the owner and group of configuration file are not that of the user in question but doesn't necessarily mean your system is out of compliance. If the fix fails Please check the logs to determing the real reason of non rule success. @author: dwalker :param self: essential if you override this definition :returns: bool - True if KDE is successfully configured, False if it isn't """ success = True if self.environ.geteuid() == 0: self.logdispatch.log(LogPriority.DEBUG, 'ConfigureScreenLocking.fixKde') if not self.kdefix: return True for user in self.kdefix: homepath = self.kdefix[user] kdefile = os.path.join(homepath, self.rcpath) if not self.correctFile(kdefile, user): success = False self.detailedresults += "Unable to configure " + \ kdefile + "\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) else: username = "" homepath = self.environ.geteuidhome() kdefile = os.path.join(homepath, self.rcpath) uidnum = int(self.environ.geteuid()) passwddata = readFile("/etc/passwd", self.logger) found = False for user in passwddata: user = user.split(':') try: puidnum = int(user[2]) if puidnum == uidnum: username = user[0] found = True except IndexError: continue if not found: self.detailedresults += "Could not obtain your user id.\n" + \ "Stonix couldn't proceed with correcting " + kdefile + "\n" success = False elif not self.correctFile(kdefile, username): self.detailedresults += "Stonix couldn't correct the contents " + \ " of " + kdefile + "\n" success = False return success def fixGnome(self): """ensures gnome is configured to automatically screen lock after 15 minutes of inactivity, if gnome is installed @author: dwalker :param self: essential if you override this definition :returns: bool - True if gnome is successfully configured, False if it isn't """ info = "" success = True gconf = "/usr/bin/gconftool-2" gsettings = "/usr/bin/gsettings" if os.path.exists(gconf): #variable self.setcmds still has items left in its dictionary #which was set in the reportGnome method, meaning some values #either were incorrect or didn't have values. Go through and #set each remaining value that isn't correct cmd = "" if self.setcmds: for item in self.setcmds: if item == "/apps/gnome-screensaver/idle_activation_enabled": cmd = gconf + " --type bool --set /apps/gnome-screensaver/idle_activation_enabled true" elif item == "/apps/gnome-screensaver/lock_enabled": cmd = gconf + " --type bool --set /apps/gnome-screensaver/lock_enabled true" elif item == "/apps/gnome-screensaver/mode": cmd = gconf + ' --type string --set /apps/gnome-screensaver/mode "blank-only"' elif item == "/desktop/gnome/session/idle_delay": if self.gconfidletime: cmd = gconf + " --type int --set /desktop/gnome/session/idle_delay " + \ self.gconfidletime else: cmd = gconf + " --type int --set /desktop/gnome/session/idle_delay 15" if self.cmdhelper.executeCommand(cmd): if self.cmdhelper.getReturnCode() != 0: info += "Unable to set value for " + cmd + "\n" success = False else: info += "Unable to set value for " + cmd + "\n" success = False if os.path.exists(gsettings): setcmds = [ "org.gnome.desktop.screensaver idle-activation-enabled true", "org.gnome.desktop.screensaver lock-enabled true", "org.gnome.desktop.screensaver lock-delay 0", "org.gnome.desktop.screensaver picture-opacity 100", "org.gnome.desktop.screensaver picture-uri ''", "org.gnome.desktop.session idle-delay 900" ] # " set org.gnome.desktop.session idle-delay " + self.gsettingsidletime] for cmd in setcmds: cmd2 = gsettings + " set " + cmd self.cmdhelper.executeCommand(cmd2) if self.cmdhelper.getReturnCode() != 0: success = False info += "Unable to set value for " + cmd + \ " using gsettings\n" # Set gsettings with dconf # Unlock dconf settings # Create dconf settings lock file if self.environ.geteuid() == 0: if not os.path.exists(self.dconfsettingslock): if not createFile(self.dconfsettingslock, self.logger): self.rulesuccess = False self.detailedresults += "Unable to create stonix-settings file\n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False #write correct contents to dconf lock file if os.path.exists(self.dconfsettingslock): # Write to the lock file if self.dconflockdata: contents = "" tmpfile = self.dconfsettingslock + ".tmp" for line in self.dconflockdata: contents += line + "\n" if not writeFile(tmpfile, contents, self.logger): self.rulesuccess = False self.detailedresults += "Unable to write contents to " + \ "stonix-settings file\n" else: os.rename(tmpfile, self.dconfsettingslock) os.chown(self.dconfsettingslock, 0, 0) os.chmod(self.dconfsettingslock, 0o644) resetsecon(self.dconfsettingslock) # Create dconf user profile file if not os.path.exists(self.dconfuserprofile): if not createFile(self.dconfuserprofile, self.logger): self.rulesuccess = False self.detailedresults += "Unable to create dconf " + \ "user profile file\n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False # Write dconf user profile if os.path.exists(self.dconfuserprofile): tmpfile = self.dconfuserprofile + ".tmp" if not writeFile(tmpfile, self.userprofilecontent, self.logger): self.rulesuccess = False self.detailedresults += "Unabled to write to dconf user" + \ " profile file\n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False else: os.rename(tmpfile, self.dconfuserprofile) os.chown(self.dconfuserprofile, 0, 0) os.chmod(self.dconfuserprofile, 0o644) resetsecon(self.dconfuserprofile) # Fix dconf settings if not os.path.exists(self.dconfsettings): if not createFile(self.dconfsettings, self.logger): self.rulesuccess = False self.detailedresults += "Unable to create " + self.dconfsettings + " file \n" self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return False self.dconfdata = { "org/gnome/desktop/screensaver": { "idle-activation-enabled": "true", "lock-enabled": "true", "lock-delay": "0", "picture-opacity": "100", "picture-uri": "\'\'" }, "org/gnome/desktop/session": { "idle-delay": "uint32 900" } } self.kveditordconf = KVEditorStonix( self.statechglogger, self.logger, "tagconf", self.dconfsettings, self.dconfsettings + ".tmp", self.dconfdata, "present", "closedeq") self.kveditordconf.report() if self.kveditordconf.fixables: if not self.kveditordconf.fix(): success = False self.detailedresults += "Unable to put correct settings inside " + \ self.dconfsettings + "\n" elif not self.kveditordconf.commit(): success = False self.detailedresults += "Unable to put correct settings inside " + \ self.dconfsettings + "\n" #run dconf update command to make dconf changes take effect if os.path.exists("/bin/dconf"): cmd = "/bin/dconf update" self.cmdhelper.executeCommand(cmd) elif os.path.exists("/usr/bin/dconf"): cmd = "/usr/bin/dconf update" self.cmdhelper.executeCommand(cmd) self.detailedresults += info return success def fixMac(self): """Mac osx specific fix submethod @author: dwalker :param self: essential if you override this definition :returns: bool - True if system is successfully fix, False if it isn't """ success = RuleKVEditor.fix(self, True) return success def searchFile(self, filehandle): """temporary method to separate the code to find directives from the rest of the code. Will put back all in one method eventually @author: dwalker :param filehandle: string :returns: bool """ self.editor = "" kvt = "tagconf" intent = "present" tpath = filehandle + ".tmp" conftype = "closedeq" self.editor = KVEditorStonix(self.statechglogger, self.logger, kvt, filehandle, tpath, self.kdeprops, intent, conftype) if not self.editor.report(): return False else: return True def correctFile(self, kfile, user): """separate method to find the correct contents of each file passed in as a parameter. @author: dwalker :param kfile: :param user: :returns: bool """ success = True if not os.path.exists(kfile): if not createFile(kfile, self.logger): self.detailedresults += "Unable to create " + kfile + \ " file for the user\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return False if not self.searchFile(kfile): if self.editor.fixables: if not self.editor.fix(): debug = "Kveditor fix is failing for file " + \ kfile + "\n" self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += "Unable to correct contents for " + \ kfile + "\n" return False elif not self.editor.commit(): debug = "Kveditor commit is failing for file " + \ kfile + "\n" self.logger.log(LogPriority.DEBUG, debug) self.detailedresults += "Unable to correct contents for " + \ kfile + "\n" return False uid = getpwnam(user)[2] gid = getpwnam(user)[3] if uid != "" and gid != "": os.chmod(kfile, 0o600) os.chown(kfile, uid, gid) resetsecon(kfile) else: success = False self.detailedresults += "Unable to obtain uid and gid of " + user + "\n" self.logger.log(LogPriority.DEBUG, self.detailedresults) return success def undo(self): self.detailedresults += "This rule cannot be reverted\n" self.rulesuccess = False self.formatDetailedResults("undo", self.rulesuccess, self.detailedresults)
class DisableInteractiveStartup(Rule): def __init__(self, config, environ, logger, statechglogger): '''Constructor''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.rulenumber = 119 self.rulename = 'DisableInteractiveStartup' self.mandatory = True self.formatDetailedResults("initialize") self.guidance = ['CCE 4245-7'] self.applicable = {'type': 'white', 'family': ['linux']} # configuration item instantiation datatype = 'bool' key = 'DISABLEINTERACTIVESTARTUP' instructions = "To prevent the disabling of interactive startup, " + \ "set the value of DISABLEINTERACTIVESTARTUP to False." default = True self.ci = self.initCi(datatype, key, instructions, default) self.iditerator = 0 self.ch = CommandHelper(self.logger) self.restart = "" self.created = False self.sethelptext() def report(self): '''The report method examines the current configuration and determines whether or not it is correct. If the config is correct then the self.compliant, self.detailedresults and self.currstate properties are updated to reflect the system status. self.rulesuccess will be updated if the rule does not succeed. Perform a check to see if PROMPT has been set to 'no' or not :returns: bool @author bemalmbe @change: dwalker ''' try: self.detailedresults = "" compliant = True self.perms = [0, 0, 420] self.helper = Pkghelper(self.logger, self.environ) if self.helper.manager == "portage": self.filepath = "/etc/conf.d/rc" keyval = {"RC_INTERACTIVE": "no"} elif self.helper.manager == "zypper": self.filepath = "/etc/sysconfig/boot" keyval = {"PROMPT_FOR_CONFIRM": "no"} elif self.helper.manager == "apt-get": self.filepath = "/etc/default/grub" keyval = {"GRUB_DISABLE_RECOVERY": '"true"'} self.restart = "/usr/sbin/update-grub" elif self.helper.manager == "yum" or self.helper.manager == "dnf": self.filepath = "/etc/sysconfig/init" keyval = {"PROMPT": "no"} tmpPath = self.filepath + ".tmp" if not os.path.exists(self.filepath): if createFile(self.filepath, self.logger): self.created = True if not checkPerms(self.filepath, self.perms, self.logger): compliant = False self.detailedresults += "Permissions are not correct on " + \ self.filepath + "\n" self.editor = KVEditorStonix(self.statechglogger, self.logger, "conf", self.filepath, tmpPath, keyval, "present", "closedeq") if os.path.exists(self.filepath): if not self.editor.report(): self.detailedresults += "Configuration for " + \ self.filepath + " is incorrect via kveditor report\n" compliant = False 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): '''The fix method will apply the required settings to the system. self.rulesuccess will be updated if the rule does not succeed. Search for the /etc/sysconfig/init configuration file and set the PROMPT setting to PROMPT=no @author bemalmbe @change: dwalker 4/8/2014 implementing KVEditorStonix ''' try: if not self.ci.getcurrvalue(): return self.detailedresults = "" # clear out event history so only the latest fix is recorded self.iditerator = 0 eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if os.path.exists(self.filepath): if not checkPerms(self.filepath, self.perms, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) if not setPerms(self.filepath, self.perms, self.logger, self.statechglogger, myid): self.rulesuccess = False if self.editor.fixables: if not self.created: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.editor.setEventID(myid) if self.editor.fix(): self.detailedresults += "kveditor fix ran successfully\n" if self.editor.commit(): self.detailedresults += "kveditor commit ran " + \ "successfully\n" else: self.detailedresults += "kveditor commit did not " + \ "run successfully\n" self.rulesuccess = False else: self.detailedresults += "kveditor fix did not run " + \ "successfully\n" self.rulesuccess = False os.chown(self.filepath, self.perms[0], self.perms[1]) os.chmod(self.filepath, self.perms[2]) resetsecon(self.filepath) if self.restart: self.ch.executeCommand(self.restart) if self.ch.getReturnCode() != 0: self.detailedresults += "Unable to restart Grub with " + \ "new changes\n" self.rulesuccess = False except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += "\n" + traceback.format_exc() self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess
def fixkde(self): '''run fix functionality for kde-based systems :returns: success :rtype: boolean @author: Breen Malmberg @change: Breen Malmberg - 5/25/2016 - moved the commit calls to ensure they are only called if the fix calls completed successfully ''' success = True try: if not self.kdefile: self.logger.log( LogPriority.DEBUG, "Unable to identify kde configuration file. " + "Can not continue with fix") self.detailedresults += "Unable to identify kde " + \ "configuration file. Can not continue with fix." success = False return success fileCreated = False if not os.path.exists(self.kdefile): if createFile(self.kdefile, self.logger): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "creation", "filepath": self.kdefile} self.statechglogger.recordchgevent(myid, event) fileCreated = True debug = "kdmrc file successfully created at " + \ self.kdefile self.logger.log(LogPriority.DEBUG, debug) setPerms(self.kdefile, [0, 0, 0o644], self.logger) self.reportkde() else: self.detailedresults += "\nCould not create kdmrc file " + \ "at " + self.kdefile return False if not self.kdeditor.fix(): success = False self.detailedresults += "KVEditor fix failed. Fix not applied" else: if not fileCreated: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.kdeditor.setEventID(myid) if not self.kdeditor.commit(): success = False self.detailedresults += 'kdeditor commit failed. ' + \ 'Fix not applied' key1 = 'GreetString' val1 = '"' + self.kdebannertext + '"' data = {"X-*-Greeter": {key1: val1}} tmpfile = self.kdefile + ".stonixtmp2" greeteditor = KVEditorStonix(self.statechglogger, self.logger, "tagconf", self.kdefile, tmpfile, data, "present", "closedeq") if not greeteditor.report(): if greeteditor.fix(): if not greeteditor.commit(): if not fileCreated: self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) self.kdeditor.setEventID(myid) success = False self.detailedresults += "\nGreetString commit failed" else: success = False self.detailedresults += "\nGreetString fix failed" except Exception: raise return success