[yum-git] 2 commits - plugins/security plugins/tmprepo yum-utils.spec
James Antill
james at linux.duke.edu
Mon Aug 4 06:50:33 UTC 2008
plugins/security/security.py | 309 ++++++++++++++++++++++++++++++-------------
plugins/tmprepo/tmprepo.py | 16 --
yum-utils.spec | 2
3 files changed, 226 insertions(+), 101 deletions(-)
New commits:
commit efb71d1f5d76c26d39913a3a3dd4440d29f4fbab
Author: James Antill <james at and.org>
Date: Fri Jul 25 12:08:10 2008 -0400
Initial WFM. Security plugin overhaul:
Go back to all previous updates looking for matches.
Add update-minimal which will do a minimal update given the specs. (security
only by default).
Eg. Given:
pkgA-1 installed
pkgA-2 (security update, BZ 69)
pkgA-3 (Bugfix update, BZ 69)
pkgA-4 (security update)
pkgA-5 (Bugfix update)
pkgA-6 (Bugfix update)
...then (assuming we see all of the above, and "update" would move us to
pkgA-6):
1. update --security will now move from pkgA-1 to pkgA-6
2. update-minimal will move from pkgA-1 to pkgA-4
3. update --bz=69 will now move from pkgA-1 to pkgA-6
4. update-minimal --bz=69 will now move from pkgA-1 to pkgA-3
...list-sec will also list all relevant notices from the installed version to
the latest available version.
Also minor i18n python-2.4.z fix.
Requires yum-3.2.18
diff --git a/plugins/security/security.py b/plugins/security/security.py
index e1cdec2..5920517 100755
--- a/plugins/security/security.py
+++ b/plugins/security/security.py
@@ -14,7 +14,7 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
#
-# Copyright Red Hat Inc. 2007
+# Copyright Red Hat Inc. 2007, 2008
#
# Author: James Antill <james.antill at redhat.com>
#
@@ -29,10 +29,10 @@
# 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
+# yum list-security
+# yum list-security bugzillas / bzs
+# yum list-security cves
+# yum list-security security / sec
import yum
import fnmatch
@@ -41,13 +41,30 @@ from yum.update_md import UpdateMetadata
import logging # for commands
from yum import logginglevels
+import rpmUtils.miscutils
+
requires_api_version = '2.5'
plugin_type = (TYPE_INTERACTIVE,)
-def ysp_gen_metadata(conduit):
+def _rpm_tup_vercmp(tup1, tup2):
+ """ Compare two "std." tuples, (n, a, e, v, r). """
+ return rpmUtils.miscutils.compareEVR((tup1[2], tup1[3], tup1[4]),
+ (tup2[2], tup2[3], tup2[4]))
+
+class CliError(yum.Errors.YumBaseError):
+
+ """
+ Command line interface related Exception.
+ """
+
+ def __init__(self, args=''):
+ yum.Errors.YumBaseError.__init__(self)
+ self.args = args
+
+def ysp_gen_metadata(repos):
""" Generate the info. from the updateinfo.xml files. """
md_info = UpdateMetadata()
- for repo in conduit.getRepos().listEnabled():
+ for repo in repos:
if not repo.enabled:
continue
@@ -64,45 +81,46 @@ def ysp__safe_refs(refs):
return []
return refs
-def ysp_should_filter_pkg(opts, pkg, md, used_map):
- """ Do the package filtering for should_show and should_keep. """
+def _match_sec_cmd(sec_cmds, pkgname, notice):
+ for i in sec_cmds:
+ if fnmatch.fnmatch(pkgname, i):
+ return i
+ if notice['update_id'] == i:
+ return i
+ return None
+
+def _has_id(refs, ref_type, ref_ids):
+ ''' Check if the given ID is a match. '''
+ for ref in ysp__safe_refs(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
- def has_id(refs, ref_type, ref_ids):
- ''' Check if the given ID is a match. '''
- for ref in ysp__safe_refs(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
- def match_sec_cmd(sec_cmds, pkg, md):
- for i in sec_cmds:
- if fnmatch.fnmatch(pkg.name, i):
- return i
- if md['update_id'] == i:
- return i
- return None
+def ysp_should_filter_pkg(opts, pkgname, notice, used_map):
+ """ Do the package filtering for should_show and should_keep. """
- rcmd = match_sec_cmd(opts.sec_cmds, pkg, md)
+ rcmd = _match_sec_cmd(opts.sec_cmds, pkgname, notice)
if rcmd:
used_map['cmd'][rcmd] = True
- return md
- elif opts.advisory and md['update_id'] in opts.advisory:
- used_map['id'][md['update_id']] = True
- return md
- elif opts.cve and has_id(md['references'], "cve", opts.cve):
- return md
- elif opts.bz and has_id(md['references'], "bugzilla", opts.bz):
- return md
+ return True
+ elif opts.advisory and notice['update_id'] in opts.advisory:
+ used_map['id'][notice['update_id']] = True
+ return True
+ elif opts.cve and _has_id(notice['references'], "cve", opts.cve):
+ return True
+ elif opts.bz and _has_id(notice['references'], "bugzilla", opts.bz):
+ return True
elif opts.security:
- if md['type'] == 'security':
- return md
+ if notice['type'] == 'security':
+ return True
elif not (opts.advisory or opts.cve or opts.bz or opts.security or \
opts.sec_cmds):
- return md # This is only possible from should_show_pkg
- return None
+ return True # This is only possible from should_show_pkg
+ return False
def ysp_has_info_md(rname, md):
if rname == "security":
@@ -113,16 +131,15 @@ def ysp_has_info_md(rname, md):
continue
return md
-def ysp_should_show_pkg(opts, pkg, md, used_map, rname=None):
+def ysp_should_show_pkgtup(opts, pkgtup, md_info, used_map, rname=None):
""" Do we want to show this package in list-security. """
- md = md.get_notice((pkg.name, pkg.ver, pkg.rel))
- if not md:
- return None
-
- if rname and not ysp_has_info_md(rname, md):
- return None
- return ysp_should_filter_pkg(opts, pkg, md, used_map)
+ name = pkgtup[0]
+ for (pkgtup, notice) in md_info.get_applicable_notices(pkgtup):
+ if rname and not ysp_has_info_md(rname, notice):
+ continue
+ if ysp_should_filter_pkg(opts, name, notice, used_map):
+ yield (pkgtup, notice)
def ysp_gen_used_map(opts):
used_map = {'bugzilla' : {}, 'cve' : {}, 'id' : {}, 'cmd' : {}}
@@ -150,7 +167,7 @@ def ysp_chk_used_map(used_map, msg):
if not used_map['cve'][i]:
msg('CVE \"%s\" not found applicable for this system' % i)
-class SecurityListCommands:
+class SecurityListCommand:
def getNames(self):
return ['list-security', 'list-sec']
@@ -163,33 +180,28 @@ class SecurityListCommands:
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, disp=None):
+ def show_pkg(self, msg, pkg, notice, disp=None):
# Make the list view much smaller
# ysp_show_pkg_md_info(pkg, md, msg)
- if disp and ysp_has_info_md(disp, md):
- for ref in ysp__safe_refs(md['references']):
+ if disp and ysp_has_info_md(disp, notice):
+ for ref in ysp__safe_refs(notice['references']):
if ref['type'] != disp:
continue
- msg(" %s %-8s %s" % (str(ref['id']), md['type'], pkg))
+ msg(" %s %-8s %s" % (str(ref['id']), notice['type'], pkg))
else:
- msg("%s %-8s %s" % (md['update_id'], md['type'], pkg))
+ msg("%s %-8s %s" % (notice['update_id'], notice['type'], pkg))
def show_pkg_exit(self):
pass
def doCommand(self, base, basecmd, extcmds):
- ygh = base.doPackageLists('updates')
self.repos = base.repos
- md_info = ysp_gen_metadata(self)
+ md_info = ysp_gen_metadata(self.repos.listEnabled())
logger = logging.getLogger("yum.verbose.main")
def msg(x):
logger.log(logginglevels.INFO_2, x)
opts, cmdline = base.plugins.cmdline
- ygh.updates.sort(key=lambda x: x.name)
filt_type = None
show_type = None
if len(extcmds) >= 1:
@@ -225,44 +237,146 @@ class SecurityListCommands:
opts.sec_cmds = extcmds
used_map = ysp_gen_used_map(opts)
- for pkg in ygh.updates:
- md = ysp_should_show_pkg(opts, pkg, md_info, used_map,
- filt_type)
- if not md:
- continue
- self.show_pkg(msg, pkg, md, show_type)
+ name2tup = _get_name2oldpkgtup(base)
+ for pkgname in sorted(name2tup):
+ for (pkgtup, notice) in ysp_should_show_pkgtup(opts,
+ name2tup[pkgname],
+ md_info,
+ used_map, filt_type):
+ d = {}
+ (d['n'], d['a'], d['e'], d['v'], d['r']) = pkgtup
+ if d['e'] is None: d['e'] = '0'
+ self.show_pkg(msg, "%(n)s-%(e)s:%(v)s-%(r)s.%(a)s" % d,
+ notice, show_type)
ysp_chk_used_map(used_map, msg)
self.show_pkg_exit()
return 0, [basecmd + ' done']
-class SecurityInfoCommands(SecurityListCommands):
+class SecurityInfoCommand(SecurityListCommand):
show_pkg_info_done = {}
def getNames(self):
return ['info-security', 'info-sec']
- def show_pkg(self, msg, pkg, md, disp=None):
- if md['update_id'] in self.show_pkg_info_done:
+ def show_pkg(self, msg, pkg, notice, disp=None):
+ if notice['update_id'] in self.show_pkg_info_done:
return
- self.show_pkg_info_done[md['update_id']] = True
- msg(md)
+ self.show_pkg_info_done[notice['update_id']] = True
+ # Python-2.4.* doesn't understand str(x) returning unicode *sigh*
+ obj = notice.__str__()
+ msg(obj)
def show_pkg_exit(self):
self.show_pkg_info_done = {}
-
+
+# "Borrowed" from yumcommands.py
+def yumcommands_checkRootUID(base):
+ """
+ Verify that the program is being run by the root user.
+
+ @param base: a YumBase object.
+ """
+ if base.conf.uid != 0:
+ base.logger.critical('You need to be root to perform this command.')
+ raise CliError
+def yumcommands_checkGPGKey(base):
+ if not base.gpgKeyCheck():
+ for repo in base.repos.listEnabled():
+ if repo.gpgcheck != 'false' and repo.gpgkey == '':
+ msg = """
+You have enabled checking of packages via GPG keys. This is a good thing.
+However, you do not have any GPG public keys installed. You need to download
+the keys for packages you wish to install and install them.
+You can do that by running the command:
+ rpm --import public.gpg.key
+
+
+Alternatively you can specify the url to the key you would like to use
+for a repository in the 'gpgkey' option in a repository section and yum
+will install it for you.
+
+For more information contact your distribution or package provider.
+"""
+ base.logger.critical(msg)
+ raise CliError
+
+# We need the list of installed pkgs, that are going to be updated
+# (by default). Then we match their names to the above.
+def _get_name2oldpkgtup(base):
+ oupdates = map(lambda x: x[1], base.up.getUpdatesTuples())
+ name2tup = {}
+ for pkgtup in oupdates: # Get the latest "old" pkgtups
+ if (pkgtup[0] in name2tup and
+ _rpm_tup_vercmp(name2tup[pkgtup[0]], pkgtup) > 0):
+ continue
+ name2tup[pkgtup[0]] = pkgtup
+ return name2tup
+
+class SecurityUpdateCommand:
+ def getNames(self):
+ return ['update-minimal']
+
+ def getUsage(self):
+ return "[PACKAGE-wildcard]"
+
+ def getSummary(self):
+ return "Works like update, but goes to the 'newest' package match which fixes a problem that affects your system"
+
+ def doCheck(self, base, basecmd, extcmds):
+ yumcommands_checkRootUID(base)
+ yumcommands_checkGPGKey(base)
+
+ def doCommand(self, base, basecmd, extcmds):
+ md_info = ysp_gen_metadata(base.repos.listEnabled())
+ opts = base.plugins.cmdline[0]
+ opts.sec_cmds = []
+ used_map = ysp_gen_used_map(opts)
+
+ # Minimal on it's own is "just security"
+ if not (opts.security or opts.advisory or opts.bz or opts.cve):
+ opts.security = True
+
+ # NOTE: Not doing obsoletes processing atm. ... maybe we should? --
+ # Also worth pointing out we don't go backwards for obsoletes in the:
+ # update --security case etc.
+
+ # obsoletes = base.up.getObsoletesTuples(newest=False)
+ # for (obsoleting, installed) in sorted(obsoletes, key=lambda x: x[0]):
+ # pass
+
+ # Tuples == (n, a, e, v, r)
+ oupdates = map(lambda x: x[1], base.up.getUpdatesTuples())
+ for oldpkgtup in sorted(oupdates):
+ for (pkgtup, notice) in md_info.get_applicable_notices(oldpkgtup):
+ if extcmds and not _match_sec_cmd(extcmds, pkgtup[0], notice):
+ continue
+ if not ysp_should_filter_pkg(opts, pkgtup[0], notice, used_map):
+ continue
+ base.update(name=pkgtup[0], arch=pkgtup[1], epoch=pkgtup[2],
+ version=pkgtup[3], release=pkgtup[4])
+ break
+
+ if len(base.tsInfo) > 0:
+ msg = '%d packages marked for minimal Update' % len(base.tsInfo)
+ return 2, [msg]
+ else:
+ return 0, ['No Packages marked for minimal Update']
+
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.
+ '--security' command line options. And the 'list-security',
+ 'info-security', and 'update-minimal' commands.
'''
parser = conduit.getOptParser()
if not parser:
return
- conduit.registerCommand(SecurityListCommands())
- conduit.registerCommand(SecurityInfoCommands())
+ conduit.registerCommand(SecurityListCommand())
+ conduit.registerCommand(SecurityInfoCommand())
+ conduit.registerCommand(SecurityUpdateCommand())
parser.values.advisory = []
parser.values.cve = []
parser.values.bz = []
@@ -300,14 +414,13 @@ def config_hook(conduit):
#
# __but__ that doesn't work for lists ... so we do it two ways
#
-def ysp_should_keep_pkg(opts, pkg, md, used_map):
+def ysp_should_keep_pkg(opts, pkgtup, md_info, used_map):
""" Do we want to keep this package to satisfy the security limits. """
-
- md = md.get_notice((pkg.name, pkg.ver, pkg.rel))
- if not md:
- return False
-
- return ysp_should_filter_pkg(opts, pkg, md, used_map)
+ name = pkgtup[0]
+ for (pkgtup, notice) in md_info.get_applicable_notices(pkgtup):
+ if ysp_should_filter_pkg(opts, name, notice, used_map):
+ return True
+ return False
def ysp_check_func_enter(conduit):
""" Stuff we need to do in both list and update modes. """
@@ -323,6 +436,11 @@ def ysp_check_func_enter(conduit):
if ((args[0] == "info") and (args[1] == "updates")):
ret = {"skip": ndata, "list_cmd": True}
if len(args):
+
+ # All the args. stuff is done in our command:
+ if (args[0] == "update-minimal"):
+ return (opts, {"skip": True, "list_cmd": False, "msg": True})
+
if (args[0] == "check-update"):
ret = {"skip": ndata, "list_cmd": True}
if (args[0] in ["update", "upgrade"]):
@@ -356,7 +474,7 @@ def exclude_hook(conduit):
conduit.info(2, 'Limiting package lists to security relevant ones')
- md_info = ysp_gen_metadata(conduit)
+ md_info = ysp_gen_metadata(conduit.getRepos().listEnabled())
def ysp_del_pkg(pkg):
""" Deletes a package from all trees that yum knows about """
@@ -366,22 +484,28 @@ def exclude_hook(conduit):
opts.sec_cmds = []
used_map = ysp_gen_used_map(opts)
+
# The official API is:
#
# pkgs = conduit.getPackages()
#
# ...however that is _extremely_ slow, deleting all packages. So we ask
- # for the list of update packages, which is all we care about.
+ # for the list of update packages, which is all we care about.
upds = conduit._base.doPackageLists(pkgnarrow='updates')
pkgs = upds.updates
+
+ name2tup = _get_name2oldpkgtup(conduit._base)
+
tot = 0
cnt = 0
for pkg in pkgs:
tot += 1
- if ysp_should_keep_pkg(opts, pkg, md_info, used_map):
- cnt += 1
- else:
+ name = pkg.name
+ if (name not in name2tup or
+ not ysp_should_keep_pkg(opts, name2tup[name], md_info, used_map)):
ysp_del_pkg(pkg)
+ continue
+ cnt += 1
ysp_chk_used_map(used_map, lambda x: conduit.error(2, x))
if cnt:
@@ -404,7 +528,7 @@ def preresolve_hook(conduit):
conduit.info(2, 'Limiting packages to security relevant ones')
- md_info = ysp_gen_metadata(conduit)
+ md_info = ysp_gen_metadata(conduit.getRepos().listEnabled())
def ysp_del_pkg(tspkg):
""" Deletes a package within a transaction. """
@@ -417,13 +541,18 @@ def preresolve_hook(conduit):
used_map = ysp_gen_used_map(opts)
tsinfo = conduit.getTsInfo()
tspkgs = tsinfo.getMembers()
- # Ok, here we keep any pkgs that pass "ysp" tests, then we keep all
+ # Ok, here we keep any pkgs that pass "ysp" tests, then we keep all
# related pkgs ... Ie. "installed" version marked for removal.
keep_pkgs = set()
+
+ name2tup = _get_name2oldpkgtup(conduit._base)
for tspkg in tspkgs:
tot += 1
- if ysp_should_keep_pkg(opts, tspkg.po, md_info, used_map):
- keep_pkgs.add(tspkg.po)
+ name = tspkg.po.name
+ if (name not in name2tup or
+ not ysp_should_keep_pkg(opts, name2tup[name], md_info, used_map)):
+ continue
+ keep_pkgs.add(tspkg.po)
scnt = len(keep_pkgs)
mini_depsolve_again = True
diff --git a/yum-utils.spec b/yum-utils.spec
index 758b08a..ebac530 100644
--- a/yum-utils.spec
+++ b/yum-utils.spec
@@ -159,7 +159,7 @@ package.
%package -n yum-security
Summary: Yum plugin to enable security filters
Group: System Environment/Base
-Requires: yum >= 3.0.5
+Requires: yum >= 3.2.18
%description -n yum-security
This plugin adds the options --security, --cve, --bz and --advisory flags
commit fd9097b917c3cb5e95e7a0a43b0d677f296cc3b8
Author: James Antill <james at and.org>
Date: Tue Jul 29 15:08:51 2008 -0400
Make true/false work for gpgcheck in tmprepo
diff --git a/plugins/tmprepo/tmprepo.py b/plugins/tmprepo/tmprepo.py
index c408a0d..95bd039 100644
--- a/plugins/tmprepo/tmprepo.py
+++ b/plugins/tmprepo/tmprepo.py
@@ -195,22 +195,18 @@ def config_hook(conduit):
rgpgcheck = conduit.confString('main', 'remote_gpgcheck', default='repo')
lgpgcheck = conduit.confString('main', 'local_gpgcheck', default='packages')
- rgpgcheck = CaselessSelectionOption('none',
+ opt_gpg = CaselessSelectionOption('all',
('none', 'all', 'packages', 'repo'),
{'0' : 'none',
'no' : 'none',
+ 'false' : 'none',
'1' : 'all',
'yes' : 'all',
+ 'true' : 'all',
'pkgs' : 'packages',
- 'repository' : 'repo'}).parse(rgpgcheck)
- lgpgcheck = CaselessSelectionOption('none',
- ('none', 'all', 'packages', 'repo'),
- {'0' : 'none',
- 'no' : 'none',
- '1' : 'all',
- 'yes' : 'all',
- 'pkgs' : 'packages',
- 'repository' : 'repo'}).parse(lgpgcheck)
+ 'repository' : 'repo'}).parse
+ rgpgcheck = opt_gpg(rgpgcheck)
+ lgpgcheck = opt_gpg(lgpgcheck)
def_tmp_repos_cleanup = conduit.confBool('main', 'cleanup', default=False)
_tmprepo_done = False
More information about the Yum-cvs-commits
mailing list