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 SoftwarePatching(Rule): '''The Software Patching class checks to see if the system is patched, is using gpg secured updates where applicable is using local update servers when available, and ensures that the system is updating automatically from a scheduled job where feasible. ''' def __init__(self, config, environ, logger, statechglogger): ''' Constructor ''' Rule.__init__(self, config, environ, logger, statechglogger) self.logger = logger self.statechglogger = statechglogger self.rulenumber = 7 self.rulename = 'SoftwarePatching' self.formatDetailedResults("initialize") self.mandatory = True self.sethelptext() self.rootrequired = True self.environ = environ self.applicable = { 'type': 'black', 'family': ['darwin', 'solaris', 'suse'] } data = "bool" key = "SCHEDULEUPDATE" instructions = "To disable creation of a scheduled " + \ "update job set the value of this " + \ "setting to no or false. Doing so " + \ "puts a larger burden for keeping " + \ "this system up to date on the system " + \ "administrators. This setting doesn't " + \ "apply to some systems whose updates " + \ "cannot be installed automatically " + \ "for various reasons. " default = True self.ci = self.initCi(data, key, instructions, default) self.guidance = [ 'CCE 14813-0', 'CCE 14914-6', 'CCE 4218-4', 'CCE 14440-2' ] self.caveats = '' self.ch = CommandHelper(self.logger) self.ph = Pkghelper(self.logger, self.environ) self.constlist = [PROXY, UPDATESERVERS] def updated(self): '''This method checks to see if the system is fully patched or if there are updates that need to be done. Returns True if the system is patched or the check doesn't apply. If there are updates that need to be applied then it returns False. :returns: updated :rtype: bool @author: dkennel @change: Breen Malmberg - 4/26/2017 - added method call checkUpdate() added code to use package helper instead of dynamically looking up the package manager and command to use ''' updated = False osEnvBkup = os.environ if PROXY is not None: os.environ["http_proxy"] = PROXY os.environ["https_proxy"] = PROXY self.ph = Pkghelper(self.logger, self.environ) try: if not self.ph.checkUpdate(): updated = True except Exception: raise os.environ = osEnvBkup return updated def report(self): '''Method to report on the configuration status of the system. :returns: self.compliant :rtype: bool @author: dkennel ''' self.detailedresults = "" self.caveats = "" # UPDATE THIS SECTION IF THE CONSTANTS BEING USED IN THIS CLASS CHANGE if not self.checkConsts(self.constlist): self.compliant = False self.detailedresults += "\nThis rule requires that the following constants, in localize.py, be defined and not None: PROXY, UPDATESERVERS" self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant try: self.logger.log(LogPriority.DEBUG, 'Checking patching') patchingcurrent = self.updated() self.logger.log(LogPriority.DEBUG, 'Checking cron jobs') crons = self.cronsconfigured() if not self.ph.determineMgr() == "apt-get": self.logger.log(LogPriority.DEBUG, 'Checking update source') localupdates = self.localupdatesource() self.logger.log(LogPriority.DEBUG, 'Checking update security') updatesec = self.updatesecurity() self.logger.log(LogPriority.DEBUG, 'patchingcurrent=' + str(patchingcurrent)) self.logger.log(LogPriority.DEBUG, 'crons=' + str(crons)) self.logger.log(LogPriority.DEBUG, 'localupdates=' + str(localupdates)) self.logger.log(LogPriority.DEBUG, 'updatesec=' + str(updatesec)) else: localupdates = True updatesec = True if patchingcurrent and crons and localupdates and \ updatesec: self.compliant = True self.currstate = 'configured' self.detailedresults = 'System appears to be up to date ' + \ 'and all software patching settings look correct.\n' else: self.detailedresults = 'The following problems were ' + \ 'detected with system software patching:\n' if not patchingcurrent: self.detailedresults = self.detailedresults + \ 'The system is not current on available updates.\n' if not crons: self.detailedresults = self.detailedresults + \ 'The system is not configured for automatic updates.\n' if not localupdates: self.detailedresults = self.detailedresults + \ 'The system is not configured to use a local ' + \ 'update source.\n' if not updatesec: self.detailedresults = self.detailedresults + \ 'The system is not configured to use signed updates. ' + \ 'Check yum.conf, all rpmrc files and all .repo files. ' # Make variables available to fix() self.crons = crons self.updatesec = updatesec self.detailedresults = self.detailedresults + self.caveats except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.rulesuccess = 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 cronsconfigured(self): '''Method to check to see if updates are scheduled to run automatically :returns: cronpresent :rtype: bool @author: dkennel @change: Breen Malmberg - 4/26/2017 - doc string edit; added try/except ''' cronpresent = False try: if os.path.exists('/var/spool/cron/root'): fhandle = open('/var/spool/cron/root') crons = fhandle.readlines() fhandle.close() for line in crons: if re.search('^#', line): continue elif re.search('yum', line) and re.search( '-y update', line): cronpresent = True elif re.search('emerge -[NuD]{2,3}', line) and \ re.search('emerge --sync', line): cronpresent = True elif re.search('zypper -n', line) and \ re.search('patch|update|up|', line): cronpresent = True elif re.search('apt-get update', line) and \ re.search('apt-get -y upgrade', line): cronpresent = True elif re.search('freebsd-update fetch', line) and \ re.search('freebsd-update install', line): cronpresent = True except Exception: raise return cronpresent def localupdatesource(self): '''Method to check to see if the system is getting updates from a local source. :returns: local :rtype: bool @author: dkennel @change: Breen Malmberg - 4/26/2017 - doc string edit; added try/except ''' local = False myos = self.environ.getostype() if re.search('Red Hat Enterprise', myos): up2file = '/etc/sysconfig/rhn/up2date' if os.path.exists(up2file): fhandle = open(up2file, 'r') config = fhandle.read() fhandle.close() for server in UPDATESERVERS: if re.search(server, config): local = True elif self.environ.getosfamily() == 'darwin': pass # check plist value set local = True if correct. else: self.caveats = self.caveats + \ 'A local update source may not be available for this platform. ' return local def updatesecurity(self): '''Method to check to see if the package signing is set up correctly. :returns: pkgsigning :rtype: bool @author: dkennel @change: Breen Malmberg - 4/26/2017 - doc string edit; method now returns a variable; added try/except ''' gpgok = False gpgcheckok = True try: if os.path.exists('/bin/rpm'): rpmcmd = '/bin/rpm -q --queryformat "%{SUMMARY}\n" gpg-pubkey' cmd = subprocess.Popen(rpmcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, close_fds=True) cmddata = cmd.stdout.readlines() for line in cmddata: if re.search( '[email protected]|security@suse|fedora@fedoraproject|security@centos', line.decode('utf-8')) and re.search( 'gpg', line.decode('utf-8')): gpgok = True rpmrc = [ '/etc/rpmrc', '/usr/lib/rpm/rpmrc', '/usr/lib/rpm/redhat/rpmrc', '/root/.rpmrc' ] for rcfile in rpmrc: if os.path.exists(rcfile): self.logger.log(LogPriority.DEBUG, [ 'SoftwarePatching.updatesecurity', 'Checking RC file ' + rcfile ]) rchandle = open(rcfile, 'r') rcdata = rchandle.read() self.logger.log(LogPriority.DEBUG, [ 'SoftwarePatching.updatesecurity', 'RC File Data: ' + str(rcdata) ]) if re.search('nosignature', rcdata): gpgcheckok = False rchandle.close() pkgsigning = gpgok and gpgcheckok except Exception: raise return pkgsigning def fix(self): '''Method to set system settings to configure software update sources and schedule updates. :returns: self.rulesuccess :rtype: bool @author: dkennel ''' if not self.checkConsts(self.constlist): self.rulesuccess = False self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) return self.rulesuccess try: self.detailedresults = "" if self.ci.getcurrvalue() and not self.crons: self.makecrons() mytype = 'conf' mystart = self.currstate myend = self.targetstate myid = '0007001' event = { 'eventtype': mytype, 'startstate': mystart, 'endstate': myend } self.statechglogger.recordchgevent(myid, event) if not self.updatesec: self.installkeys() self.detailedresults = "Automated updates configured. " + \ "Local software update sources may need to be configured " + \ "manually. " elif not self.ci.getcurrvalue(): self.detailedresults = str(self.ci.getkey()) + \ " was disabled. No action was taken. " except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception as err: self.rulesuccess = False self.detailedresults = self.detailedresults + "\n" + str(err) + \ " - " + str(traceback.format_exc()) self.logdispatch.log(LogPriority.ERROR, self.detailedresults) self.formatDetailedResults("fix", self.rulesuccess, self.detailedresults) self.logdispatch.log(LogPriority.INFO, self.detailedresults) return self.rulesuccess def makecrons(self): '''This method creates cron entries for automating update installations. @author: dkennel ''' rootcron = "/var/spool/cron/root" random.seed() hour = random.randrange(0, 5) minute = random.randrange(0, 59) crontime = str(minute) + " " + str(hour) + " * * * " if os.path.exists('/usr/bin/yum') and \ os.path.exists('/usr/sbin/rhn_check'): command = "/usr/sbin/rhn_check > /dev/null 2>&1 && /usr/bin/yum " \ + "--exclude=kernel* -y update > /dev/null 2>&1" elif os.path.exists('/usr/bin/yum'): command = "/usr/bin/yum --exclude=kernel* -y update > /dev/null 2>&1" elif os.path.exists('/usr/sbin/freebsd-update'): command = "/usr/sbin/freebsd-update fetch &> && " + \ "/usr/sbin/freebsd-update install &>" elif os.path.exists('/usr/bin/apt-get'): command = '/usr/bin/apt-get update &> && ' + \ '/usr/bin/apt-get -y upgrade' elif os.path.exists('/usr/bin/emerge'): command = '/usr/bin/emerge --sync &> && ' + \ '/usr/bin/emerge --NuD &> && /usr/bin/revdep-rebuild &>' elif os.path.exists('/usr/bin/zypper'): command = '/usr/bin/zypper -n up -l &>' cronentry = crontime + command self.logger.log( LogPriority.DEBUG, ['SoftwarePatching.makecrons', "Cronentry: " + cronentry]) try: crontab = open(rootcron, 'a') crontab.write(cronentry) crontab.close() except (IOError): self.logger.log(LogPriority.ERROR, [ 'SoftwarePatching.makecrons', "Error writing to root user crontab entry" ]) self.rulesuccess = False def installkeys(self): '''This method will install the gpg keys used by RPM based distros to authenticate updates. @author: dkennel ''' importcmd = '/bin/rpm --import ' keylist = [ '/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release', '/usr/share/rhn/RPM-GPG-KEY', '/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-17-primary', '/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-17-secondary', '/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-16-primary', '/etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-16-secondary', '/etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-6' ] for key in keylist: if not os.path.exists(key): continue try: proc = subprocess.Popen(importcmd + key, shell=True, close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) cmdoutput = proc.stdout.read() + proc.stderr.read() self.logger.log(LogPriority.DEBUG, ['SoftwarePatching.installkeys', cmdoutput]) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except Exception: self.detailedresults = traceback.format_exc() self.rulesuccess = False self.logger.log( LogPriority.ERROR, ['SoftwarePatching.installkeys', self.detailedresults]) def undo(self): ''' :returns: @author: D. Kennel ''' self.targetstate = 'notconfigured' try: event1 = self.statechglogger.getchgevent('0007001') if event1['startstate'] == 'notconfigured' and \ event1['endstate'] == 'configured': rootcron = "/var/spool/cron/root" fhandle = open(rootcron, 'r') crondata = fhandle.readlines() fhandle.close() newcron = [] if os.path.exists('/usr/bin/yum') and \ os.path.exists('/usr/sbin/rhn_check'): command = "/usr/sbin/rhn_check > /dev/null 2>&1 && /usr/bin/yum " \ + "--exclude=kernel* -y update > /dev/null 2>&1" elif os.path.exists('/usr/bin/yum'): command = "/usr/bin/yum --exclude=kernel* -y update > /dev/null 2>&1" elif os.path.exists('/usr/sbin/freebsd-update'): command = "/usr/sbin/freebsd-update fetch &> && " + \ "/usr/sbin/freebsd-update install &>" elif os.path.exists('/usr/bin/apt-get'): command = '/usr/bin/apt-get update &> && ' + \ '/usr/bin/apt-get -y upgrade' elif os.path.exists('/usr/bin/emerge'): command = '/usr/bin/emerge --sync &> && ' + \ '/usr/bin/emerge --NuD &> && /usr/bin/revdep-rebuild &>' elif os.path.exists('/usr/bin/zypper'): command = '/usr/bin/zypper -n up -l &>' else: command = '' if len(command) != 0: for line in crondata: if not re.search(command, line): newcron.append(line) whandle = open(rootcron, 'w') whandle.writelines(newcron) whandle.close() os.chown(rootcron, 0, 0) os.chmod(rootcron, 384) resetsecon(rootcron) except (KeyboardInterrupt, SystemExit): # User initiated exit raise except KeyError: # Key not found in statechglogger return except Exception: self.detailedresults = traceback.format_exc() self.rulesuccess = False self.logger.log(LogPriority.ERROR, ['SoftwarePatching.undo', self.detailedresults]) self.report() if self.currstate == self.targetstate: self.detailedresults = '''SoftwarePatching: Some changes successfully reverted'''
class ConfigureKerberos(Rule): '''@author: Ekkehard J. Koch''' def __init__(self, config, environ, logdispatcher, statechglogger): """ @param config: @param environ: @param logdispatcher: @param statechglogger: """ Rule.__init__(self, config, environ, logdispatcher, statechglogger) self.rulenumber = 255 self.rulename = 'ConfigureKerberos' 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']}} # This if/else statement fixes a bug in Configure Kerberos that # occurs on Debian systems due to the fact that Debian has no wheel # group by default. if self.environ.getosfamily() == 'darwin': self.files = {"krb5.conf": {"path": "/etc/krb5.conf", "remove": False, "content": MACKRB5, "permissions": 0o644, "owner": os.getuid(), "group": "wheel", "eventid": str(self.rulenumber).zfill(4) + "krb5"}, "edu.mit.Kerberos": {"path": "/Library/Preferences/edu.mit.Kerberos", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "Kerberos"}, "edu.mit.Kerberos.krb5kdc.launchd": {"path": "/Library/Preferences/edu.mit.Kerberos.krb5kdc.launchd", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "krb5kdc"}, "kerb5.conf": {"path": "/etc/kerb5.conf", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "kerb5"}, "edu.mit.Kerberos.kadmind.launchd": {"path": "/Library/Preferences/edu.mit.Kerberos.kadmind.launchd", "remove": True, "content": None, "permissions": None, "owner": None, "group": None, "eventid": str(self.rulenumber).zfill(4) + "kadmind"}, } else: self.files = {"krb5.conf": {"path": "/etc/krb5.conf", "remove": False, "content": LINUXKRB5, "permissions": 0o644, "owner": "root", "group": "root", "eventid": str(self.rulenumber).zfill(4) + "krb5"}} self.ch = CommandHelper(self.logdispatch) self.fh = FileHelper(self.logdispatch, self.statechglogger) if self.environ.getosfamily() == 'linux': self.ph = Pkghelper(self.logdispatch, self.environ) self.filepathToConfigure = [] for filelabel, fileinfo in sorted(self.files.items()): if fileinfo["remove"]: msg = "Remove if present " + str(fileinfo["path"]) else: msg = "Add or update if needed " + str(fileinfo["path"]) self.filepathToConfigure.append(msg) self.fh.addFile(filelabel, fileinfo["path"], fileinfo["remove"], fileinfo["content"], fileinfo["permissions"], fileinfo["owner"], fileinfo["group"], fileinfo["eventid"] ) # Configuration item instantiation datatype = "bool" key = "CONFIGUREFILES" instructions = "When Enabled will fix these files: " + \ str(self.filepathToConfigure) default = True self.ci = self.initCi(datatype, key, instructions, default) def report(self): '''run report actions for configure kerberos determine compliance status of the current system return True if compliant, False if non-compliant :returns: self.compliant :rtype: bool @author: ??? @change: Breen Malmberg - 2/23/2017 - added doc string; added const checks preamble to report and fix methods ''' self.compliant = True self.detailedresults = "" # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [MACKRB5, LINUXKRB5] if not self.checkConsts(constlist): self.compliant = False self.detailedresults = "\nPlease ensure that the constants: MACKRB5, LINUXKRB5, in localize.py, are defined and are not None. This rule will not function without them." self.formatDetailedResults("report", self.compliant, self.detailedresults) return self.compliant try: if self.environ.getosfamily() == 'linux': packagesRpm = ["pam_krb5", "krb5-libs", "krb5-workstation", "sssd-krb5", "sssd-krb5-common"] packagesDeb = ["krb5-config", "krb5-user", "libpam-krb5"] packagesSuse = ["pam_krb5", "sssd-krb5", "sssd-krb5-common", "krb5-client", "krb5"] if self.ph.determineMgr() == "apt-get": self.packages = packagesDeb elif self.ph.determineMgr() == "zypper": self.packages = packagesSuse else: self.packages = packagesRpm for package in self.packages: if not self.ph.check(package) and self.ph.checkAvailable(package): self.compliant = False self.detailedresults += package + " is not installed\n" if not self.fh.evaluateFiles(): self.compliant = False self.detailedresults += self.fh.getFileMessage() except (KeyboardInterrupt, SystemExit): raise except Exception: self.compliant = False self.detailedresults += 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 fix actions :returns: self.rulesuccess :rtype: bool @author: ??? @change: Breen Malmberg - 2/23/2017 - added doc string; added checkconsts preamble to ensure the rule does not attempt to run without requied information (from localize.py) ''' self.rulesuccess = True self.detailedresults = "" self.iditerator = 0 # UPDATE THIS SECTION IF YOU CHANGE THE CONSTANTS BEING USED IN THE RULE constlist = [MACKRB5, LINUXKRB5] if not self.checkConsts(constlist): fixsuccess = False self.formatDetailedResults("fix", fixsuccess, self.detailedresults) return fixsuccess try: eventlist = self.statechglogger.findrulechanges(self.rulenumber) for event in eventlist: self.statechglogger.deleteentry(event) if self.ci.getcurrvalue(): pkgsToInstall = [] if self.environ.getosfamily() == 'linux': for package in self.packages: if not self.ph.check(package): if self.ph.checkAvailable(package): pkgsToInstall.append(package) for package in pkgsToInstall: if self.ph.install(package): self.iditerator += 1 myid = iterate(self.iditerator, self.rulenumber) event = {"eventtype": "pkghelper", "pkgname": package, "startstate": "removed", "endstate": "installed"} self.statechglogger.recordchgevent(myid, event) else: self.rulesuccess = False self.detailedresults += "Installation of " + package + " did not succeed.\n" if not self.fh.fixFiles(): self.rulesuccess = False self.detailedresults += self.fh.getFileMessage() else: self.rulesuccess = False self.detailedresults = str(self.ci.getkey()) + " was disabled. No action was taken!" except (KeyboardInterrupt, SystemExit): raise except Exception: self.rulesuccess = False self.detailedresults += 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