[yum-cvs] yum-utils/plugins/security security.conf, NONE, 1.1 security.py, NONE, 1.1

Tim Lauridsen timlau at linux.duke.edu
Fri Apr 20 10:25:36 UTC 2007


Update of /home/groups/yum/cvs/yum-utils/plugins/security
In directory login1.linux.duke.edu:/tmp/cvs-serv12694/plugins/security

Added Files:
	security.conf security.py 
Log Message:
Added security plugin written by James Antill <james at and.org>

--- NEW FILE security.conf ---
[main]
enabled=1

--- NEW FILE security.py ---
#! /usr/bin/python -tt
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
# Copyright Red Hat Inc. 2007
#
# Author: James Antill <james.antill at redhat.com>
#
# Examples:
#
#  yum --security info updates
#  yum --security list updates
#  yum --security check-update
#  yum --security update
#
# yum --cve CVE-2007-1667      <cmd>
# yum --bz  235374 --bz 234688 <cmd>
# yum --advisory FEDORA-2007-420 --advisory FEDORA-2007-346 <cmd>
#
# yum sec-list
# yum sec-list bugzillas / bzs
# yum sec-list cves
# yum sec-list security / sec

import yum
import time
import textwrap
import sys
from yum.plugins import TYPE_INTERACTIVE
from yum.update_md import UpdateMetadata
from rpmUtils.miscutils import compareEVR
import logging # for commands
from yum import logginglevels

requires_api_version = '2.5'
plugin_type = (TYPE_INTERACTIVE,)

def ysp_gen_metadata(conduit):
    """ Generate the info. from the updateinfo.xml files. """
    md_info = UpdateMetadata()
    for repo in conduit.getRepos().listEnabled():
        if not repo.enabled:
            continue
        
        try: # attempt to grab the updateinfo.xml.gz from the repodata
            md_info.add(repo)
        except yum.Errors.RepoMDError:
            continue # No metadata found for this repo
    return md_info

def ysp_should_show_pkg(pkg, md, rname=None):
    """ Do we want to show this package in sec-list. """
    
    md = md.get_notice((pkg.name, pkg.ver, pkg.rel))
    if not md:
        return None
    md = md.get_metadata()

    if not rname:
        return md
    if rname:
        if rname == "security":
            return md['type'] == 'security'
        for ref in md['references']:
            if ref['type'] != rname:
                continue
            return md

    return None

def ysp_show_pkg_md_info(pkg, md, msg):
    msg(pkg)
    msg('  ID      ' + md['update_id'])
    msg('  Type    ' + md['type'])
    msg('  Issued  ' + md['issued'])
    if md['issued'] != md['updated']:
        msg('  Updated ' + md['updated'])
    if md['references']:
        msg('  References')
        for ref in md['references']:
            if ref['type'] == 'cve':
                txt = "    CVE " + ref['id'];
            elif ref['type'] == 'bugzilla':
                txt = "    BZ  " + ref['id'];
            else:
                msg("   *" + ref['type'])
            if 'summary' in ref:
                if (len(txt) + len(ref['summary'])) <= 76:
                    msg("%s: %s" % (txt, ref['summary']))
                else:
                    msg("%s: %.*s..." % (txt, 73 -len(txt), ref['summary']))
            elif 'href' in ref and \
                     (len(txt) + len(ref['href'])) <= 76:
                msg("%s - %s" % (txt, ref['href']))
            else:
                msg(txt)

class SecurityListCommands:
    def getNames(self):
        return ['list-sec', 'list-security']

    def getUsage(self):
        return 'list-sec'

    def doCheck(self, base, basecmd, extcmds):
        pass

    def getRepos(self): # so we can act as a "conduit"
        return self.repos

    def show_pkg(self, msg, pkg, md):
        ysp_show_pkg_md_info(pkg, md, msg)
        msg('')
            
    def doCommand(self, base, basecmd, extcmds):
        ygh = base.doPackageLists('updates')
        self.repos = base.repos
        md_info = ysp_gen_metadata(self)
        done = False
        logger = logging.getLogger("yum.verbose.main")
        def msg(x):
            logger.log(logginglevels.INFO_2, x)
        def msg_warn(x):
            logger.warn(x)

        ygh.updates.sort(key=lambda x: x.name)
        if not extcmds:
            for pkg in ygh.updates:
                md = ysp_should_show_pkg(pkg, md_info)
                if not md:
                    continue
                self.show_pkg(msg, pkg, md)
        elif len(extcmds) == 1 and (extcmds[0] == "bugzillas" or \
                                    extcmds[0] == "bzs"):
            done = False
            for pkg in ygh.updates:
                md = ysp_should_show_pkg(pkg, md_info, "bugzilla")
                if not md:
                    continue
                if not done:
                    msg(" ---- Bugzillas ----")
                    done = True
                self.show_pkg(msg, pkg, md)
        elif len(extcmds) == 1 and extcmds[0] == "cves":
            done = False
            for pkg in ygh.updates:
                md = ysp_should_show_pkg(pkg, md_info, "cve")
                if not md:
                    continue
                if not done:
                    msg(" ---- CVEs ----")
                    done = True
                self.show_pkg(msg, pkg, md)
        elif len(extcmds) == 1 and (extcmds[0] == "security" or \
                                    extcmds[0] == "sec"):
            done = False
            for pkg in ygh.updates:
                md = ysp_should_show_pkg(pkg, md_info, "security")
                if not md:
                    continue
                if not done:
                    msg(" ---- Security ----")
                    done = True
                self.show_pkg(msg, pkg, md)
        else:
            uids_done = {}
            for uid in extcmds:
                uids_done[uid] = False
            for pkg in ygh.updates:
                md = ysp_should_show_pkg(pkg, md_info)
                if not md:
                    continue
                
                if md['update_id'] in extcmds:
                    uids_done[md['update_id']] = True
                    self.show_pkg(msg, pkg, md)
            for uid in extcmds:
                if not uids_done[uid]:
                    msg_warn('Advisory \"%s\" not found applicable'
                             ' for this system' % uid)
#        else:
#            return 1, [str(PluginYumExit('Bad %s commands' % basecmd))]
        return 0, [basecmd + ' done']
            
class SecurityInfoCommands(SecurityListCommands):
    def getNames(self):
        return ['info-sec', 'info-security']

    def getUsage(self):
        return 'info-sec'

    def show_pkg(self, msg, pkg, md):
        ysp_show_pkg_md_info(pkg, md, msg)
        if md['description'] != None:
            msg('  Description')
            msg(textwrap.fill(md['description'],
                              width=75, expand_tabs=False,
                              initial_indent="     ", subsequent_indent="    "))
        msg('')
    
def config_hook(conduit):
    '''
    Yum Plugin Config Hook: 
    Setup the option parser with the '--advisory', '--bz', '--cve', and
    '--security' command line options. And the 'sec-list' command.
    '''

    parser = conduit.getOptParser()
    if not parser:
        return

    conduit.registerCommand(SecurityListCommands())
    conduit.registerCommand(SecurityInfoCommands())
    parser.values.advisory = []
    parser.values.cve      = []
    parser.values.bz       = []
    parser.values.security = False
    def osec(opt, key, val, parser):
         # CVE is a subset of --security on RHEL, but not on Fedora
        if False and parser.values.cve:
            raise OptionValueError("can't use %s after --cve" % key)
        parser.values.security = True
    def ocve(opt, key, val, parser):
        if False and parser.values.security:
            raise OptionValueError("can't use %s after --security" % key)
        parser.values.cve.append(val)
    def obz(opt, key, val, parser):
        parser.values.bz.append(str(val))
    def oadv(opt, key, val, parser):
        parser.values.advisory.append(val)
            
    parser.add_option('--security', action="callback",
                      callback=osec, dest='security', default=False,
                      help='Include security relevant packages')
    parser.add_option('--cve', action="callback", type="string",
                      callback=ocve, dest='cve', default=[],
                      help='Include packages needed to fix the given CVE')
    parser.add_option('--bz', action="callback",
                      callback=obz, dest='bz', default=[], type="int",
                      help='Include packages needed to fix the given BZ')
    parser.add_option('--advisory', action="callback",
                      callback=oadv, dest='advisory', default=[], type="string",
                      help='Include packages needed to fix the given advisory')

#  You might think we'd just use the exclude_hook, and call delPackage
# and indeed that works for list updates etc.
#
# __but__ that doesn't work for dependancies on real updates
#
#  So to fix deps. we need to do it at the preresolve stage and take the
# "transaction package list" and then remove packages from that.
#
# __but__ that doesn't work for lists ... so we do it two ways
#
def ysp_should_keep_pkg(opts, pkg, md, used_map):
    """ Do we want to keep this package to satisfy the security limits. """
    
    def has_id(refs, ref_type, ref_ids):
        ''' Check if the given ID is a match. '''
        for ref in refs:
            if ref['type'] != ref_type:
                continue
            if ref['id'] not in ref_ids:
                continue
            used_map[ref_type][ref['id']] = True
            return ref
        return None

    md = md.get_notice((pkg.name, pkg.ver, pkg.rel))
    if not md:
        return False
    md = md.get_metadata()
    
    if opts.advisory and md['update_id'] in opts.advisory:
        used_map['id'][md['update_id']] = True
        return True
    elif opts.cve and has_id(md['references'], "cve", opts.cve):
        return True
    elif opts.bz and has_id(md['references'], "bugzilla", opts.bz):
        return True
    elif opts.security:
        return md['type'] == 'security'
    else:
        return False

def ysp_check_func_enter(conduit):
    """ Stuff we need to do in both list and update modes. """
    
    opts, args = conduit.getCmdLine()

    ndata = not (opts.security or opts.advisory or opts.bz or opts.cve)

    ret = None
    if len(args) >= 2:
        if ((args[0] == "list") and (args[1] == "updates")):
            ret = {"skip": ndata, "list_cmd": True}
        if ((args[0] == "info") and (args[1] == "updates")):
            ret = {"skip": ndata, "list_cmd": True}
    if len(args):
        if (args[0] == "check-update"):
            ret = {"skip": ndata, "list_cmd": True}
        if (args[0] == "update"):
            ret = {"skip": ndata, "list_cmd": False}
        if (args[0] == "list-sec") or (args[0] == "list-security"):
            if not ndata:
                conduit.error(2, 'Skipping security plugin arguments')
            return (opts, {"skip": True, "list_cmd": True})
        if (args[0] == "info-sec") or (args[0] == "info-security"):
            if not ndata:
                conduit.error(2, 'Skipping security plugin arguments')
            return (opts, {"skip": True, "list_cmd": True})

    if ret:
        if ndata:
            conduit.info(2, 'Skipping security plugin, no data')
        return (opts, ret)
    
    if not ndata:
        conduit.error(2, 'Skipping security plugin, other command')
    return (opts, {"skip": True, "list_cmd": False, "msg": True})

def ysp_gen_used_map(opts):
    used_map = {'bugzilla' : {}, 'cve' : {}, 'id' : {}}
    for i in opts.advisory:
        used_map['id'][i] = False
    for i in opts.bz:
        used_map['bugzilla'][i] = False
    for i in opts.cve:
        used_map['cve'][i] = False
    return used_map

def ysp_chk_used_map(conduit, used_map):
    for i in used_map['id']:
        if not used_map['id'][i]:
            conduit.error(2, 'Advisory \"%s\" not found applicable'
                          ' for this system' % i)
    for i in used_map['bugzilla']:
        if not used_map['bugzilla'][i]:
            conduit.error(2, 'BZ \"%s\" not found applicable'
                          ' for this system' % i)
    for i in used_map['cve']:
        if not used_map['cve'][i]:
            conduit.error(2, 'CVE \"%s\" not found applicable'
                          ' for this system' % i)

def exclude_hook(conduit):
    '''
    Yum Plugin Exclude Hook:
    Check and remove packages that don\'t align with the security config.
    '''
    
    opts, info = ysp_check_func_enter(conduit)
    if info["skip"]:
        return

    if not info["list_cmd"]:
        return
    
    conduit.info(2, 'Limiting package lists to security relevant ones')
    
    md_info = ysp_gen_metadata(conduit)

    def ysp_del_pkg(pkg):
        """ Deletes a package from all trees that yum knows about """
        conduit.info(3," --> %s from %s excluded (non-security)" %
                     (pkg,pkg.repoid))
        conduit.delPackage(pkg)

    used_map = ysp_gen_used_map(opts)
    for pkg in conduit.getPackages():
        if not ysp_should_keep_pkg(opts, pkg, md_info, used_map):
            ysp_del_pkg(pkg)
    ysp_chk_used_map(conduit, used_map)
            
def preresolve_hook(conduit):
    '''
    Yum Plugin PreResolve Hook:
    Check and remove packages that don\'t align with the security config.
    '''

    opts, info = ysp_check_func_enter(conduit)
    if info["skip"]:
        return

    if info["list_cmd"]:
        return
    
    conduit.info(2, 'Limiting packages to security relevant ones')

    md_info = ysp_gen_metadata(conduit)

    def ysp_del_pkg(tspkg):
        """ Deletes a package within a transaction. """
        conduit.info(3," --> %s from %s excluded (non-security)" %
                     (tspkg.po,tspkg.po.repoid))
        tsinfo.remove(tspkg.pkgtup)

    cnt = 0
    used_map = ysp_gen_used_map(opts)
    tsinfo = conduit.getTsInfo()
    tspkgs = tsinfo.getMembers()
    for tspkg in tspkgs:
        if not ysp_should_keep_pkg(opts, tspkg.po, md_info, used_map):
            ysp_del_pkg(tspkg)
        else:
            cnt += 1
    ysp_chk_used_map(conduit, used_map)
    
    if cnt:
        conduit.info(2, 'Needed %d packages, for security' % (cnt))
    else:
        conduit.info(2, 'No packages needed, for security')
        sys.exit(0)

if __name__ == '__main__':
    print "This is a plugin that is supposed to run from inside YUM"




More information about the Yum-cvs-commits mailing list