Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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'''
Ejemplo n.º 3
0
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