[Yum-devel] [PATCH 2/2] big change for having gpgcakeys
Seth Vidal
skvidal at fedoraproject.org
Sat Dec 11 05:16:20 UTC 2010
The idea is you define a gpgcakey for repo. This is the key that once you import will let you import everything else automatically.
If you have a cakey defined then when yum goes to import any other key it will look for a .asc detached signature for that key.
if that signature is from the cakey you've already imported then yum will import the new gpgkey w/o prompting you.
this works for signed packages as well as signed repomd.xml files in repos
this also moves all gpg keyrings into a new per-repo persistent directory in /var/lib/yum/repos/$basearch/$releasever/repoid
so we don't have to worry about a yum clean all removing our gpgkeys like we have in the past.
---
output.py | 3 +
yum/__init__.py | 175 +++++++++++++++++++++++++++++++++++++++++--------------
yum/config.py | 1 +
yum/repos.py | 9 ++-
yum/yumRepo.py | 26 +++++++-
5 files changed, 163 insertions(+), 51 deletions(-)
diff --git a/output.py b/output.py
index f99ab37..a36e66c 100755
--- a/output.py
+++ b/output.py
@@ -1228,12 +1228,15 @@ Downgrade %5.5s Package(s)
def setupKeyImportCallbacks(self):
confirm_func = self._cli_confirm_gpg_key_import
gpg_import_func = self.getKeyForRepo
+ gpgca_import_func = self.getCAKeyForRepo
if hasattr(self, 'prerepoconf'):
self.prerepoconf.confirm_func = confirm_func
self.prerepoconf.gpg_import_func = gpg_import_func
+ self.prerepoconf.gpgca_import_func = gpgca_import_func
else:
self.repos.confirm_func = confirm_func
self.repos.gpg_import_func = gpg_import_func
+ self.repos.gpgca_import_func = gpgca_import_func
def interrupt_callback(self, cbobj):
'''Handle CTRL-C's during downloads
diff --git a/yum/__init__.py b/yum/__init__.py
index 92fa0d0..67cf5b3 100644
--- a/yum/__init__.py
+++ b/yum/__init__.py
@@ -85,6 +85,7 @@ from yum.rpmtrans import RPMTransaction,SimpleCliCallBack
from yum.i18n import to_unicode, to_str
import string
+import StringIO
from weakref import proxy as weakref
@@ -135,6 +136,7 @@ class _YumPreRepoConf:
self.interrupt_callback = None
self.confirm_func = None
self.gpg_import_func = None
+ self.gpgca_import_func = None
self.cachedir = None
self.cache = None
@@ -415,7 +417,13 @@ class YumBase(depsolve.Depsolve):
else:
thisrepo.repo_config_age = repo_age
thisrepo.repofile = repofn
-
+ # repos are ver/arch specific so add $basearch/$releasever
+ self.conf._repos_persistdir = os.path.normpath('%s/repos/%s/%s/'
+ % (self.conf.persistdir, self.yumvar.get('basearch', '$basearch'),
+ self.yumvar.get('releasever', '$releasever')))
+ thisrepo.base_persistdir = self.conf._repos_persistdir
+
+
if thisrepo.id in self.repo_setopts:
for opt in self.repo_setopts[thisrepo.id].items:
if not hasattr(thisrepo, opt):
@@ -575,6 +583,7 @@ class YumBase(depsolve.Depsolve):
self.repos.setInterruptCallback(prerepoconf.interrupt_callback)
self.repos.confirm_func = prerepoconf.confirm_func
self.repos.gpg_import_func = prerepoconf.gpg_import_func
+ self.repos.gpgca_import_func = prerepoconf.gpgca_import_func
if prerepoconf.cachedir is not None:
self.repos.setCacheDir(prerepoconf.cachedir)
if prerepoconf.cache is not None:
@@ -4309,15 +4318,16 @@ class YumBase(depsolve.Depsolve):
self.conf.obsoletes = old_conf_obs
return done
- def _retrievePublicKey(self, keyurl, repo=None):
+ def _retrievePublicKey(self, keyurl, repo=None, getSig=True):
"""
Retrieve a key file
@param keyurl: url to the key to retrieve
Returns a list of dicts with all the keyinfo
"""
key_installed = False
-
- self.logger.info(_('Retrieving GPG key from %s') % keyurl)
+
+ msg = _('Retrieving key from %s') % keyurl
+ self.verbose_logger.log(logginglevels.INFO_2, msg)
# Go get the GPG key from the given URL
try:
@@ -4336,6 +4346,33 @@ class YumBase(depsolve.Depsolve):
except urlgrabber.grabber.URLGrabError, e:
raise Errors.YumBaseError(_('GPG key retrieval failed: ') +
to_unicode(str(e)))
+
+ # check for a .asc file accompanying it - that's our gpg sig on the key
+ # suck it down and do the check
+ sigfile = None
+ valid_sig = False
+ if getSig and repo and repo.gpgcakey:
+ self.getCAKeyForRepo(repo, callback=repo.confirm_func)
+ try:
+ url = misc.to_utf8(keyurl + '.asc')
+ opts = repo._default_grabopts()
+ text = repo.id + '/gpgkeysig'
+ sigfile = urlgrabber.urlopen(url, **opts)
+
+ except urlgrabber.grabber.URLGrabError, e:
+ sigfile = None
+
+ if sigfile:
+ if not misc.valid_detached_sig(sigfile,
+ StringIO.StringIO(rawkey), repo.gpgcadir):
+ #if we decide we want to check, even though the sig failed
+ # here is where we would do that
+ raise Errors.YumBaseError(_('GPG key signature on key %s does not match CA Key for repo: %s') % (url, repo.id))
+ else:
+ msg = _('GPG key signature verified against CA Key(s)')
+ self.verbose_logger.log(logginglevels.INFO_2, msg)
+ valid_sig = True
+
# Parse the key
try:
keys_info = misc.getgpgkeyinfo(rawkey, multiple=True)
@@ -4352,29 +4389,31 @@ class YumBase(depsolve.Depsolve):
_('GPG key parsing failed: key does not have value %s') + info
thiskey[info] = keyinfo[info]
thiskey['hexkeyid'] = misc.keyIdToRPMVer(keyinfo['keyid']).upper()
+ thiskey['valid_sig'] = valid_sig
+ thiskey['has_sig'] = bool(sigfile)
keys.append(thiskey)
return keys
- def _getKeyImportMessage(self, info, keyurl):
+ def _getKeyImportMessage(self, info, keyurl, keytype='GPG'):
msg = None
if keyurl.startswith("file:"):
fname = keyurl[len("file:"):]
pkgs = self.rpmdb.searchFiles(fname)
if pkgs:
pkgs = sorted(pkgs)[-1]
- msg = (_('Importing GPG key 0x%s:\n'
+ msg = (_('Importing %s key 0x%s:\n'
' Userid : %s\n'
' Package: %s (%s)\n'
' From : %s') %
- (info['hexkeyid'], to_unicode(info['userid']),
+ (keytype, info['hexkeyid'], to_unicode(info['userid']),
pkgs, pkgs.ui_from_repo,
keyurl.replace("file://","")))
if msg is None:
- msg = (_('Importing GPG key 0x%s:\n'
+ msg = (_('Importing %s key 0x%s:\n'
' Userid: "%s"\n'
' From : %s') %
- (info['hexkeyid'], to_unicode(info['userid']),
+ (keytype, info['hexkeyid'], to_unicode(info['userid']),
keyurl.replace("file://","")))
self.logger.critical("%s", msg)
@@ -4405,24 +4444,34 @@ class YumBase(depsolve.Depsolve):
self.logger.info(_('GPG key at %s (0x%s) is already installed') % (
keyurl, info['hexkeyid']))
continue
-
- # Try installing/updating GPG key
- self._getKeyImportMessage(info, keyurl)
- rc = False
- if self.conf.assumeyes:
- rc = True
- elif fullaskcb:
- rc = fullaskcb({"po": po, "userid": info['userid'],
- "hexkeyid": info['hexkeyid'],
- "keyurl": keyurl,
- "fingerprint": info['fingerprint'],
- "timestamp": info['timestamp']})
- elif askcb:
- rc = askcb(po, info['userid'], info['hexkeyid'])
-
- if not rc:
- raise Errors.YumBaseError, _("Not installing key")
+ if repo.gpgcakey and info['has_sig'] and info['valid_sig']:
+ key_installed = True
+ else:
+ # Try installing/updating GPG key
+ self._getKeyImportMessage(info, keyurl)
+ rc = False
+ if self.conf.assumeyes:
+ rc = True
+
+ # grab the .sig/.asc for the keyurl, if it exists
+ # if it does check the signature on the key
+ # if it is signed by one of our ca-keys for this repo or the global one
+ # then rc = True
+ # else ask as normal.
+
+ elif fullaskcb:
+ rc = fullaskcb({"po": po, "userid": info['userid'],
+ "hexkeyid": info['hexkeyid'],
+ "keyurl": keyurl,
+ "fingerprint": info['fingerprint'],
+ "timestamp": info['timestamp']})
+ elif askcb:
+ rc = askcb(po, info['userid'], info['hexkeyid'])
+
+ if not rc:
+ raise Errors.YumBaseError, _("Not installing key")
+
# Import the key
ts = self.rpmdb.readOnlyTS()
result = ts.pgpImportPubkey(misc.procgpgkey(info['raw_key']))
@@ -4446,43 +4495,55 @@ class YumBase(depsolve.Depsolve):
self.logger.info(_("Import of key(s) didn't help, wrong key(s)?"))
raise Errors.YumBaseError, errmsg
- def getKeyForRepo(self, repo, callback=None):
+ def _getAnyKeyForRepo(self, repo, destdir, keyurl_list, is_cakey=False, callback=None):
"""
Retrieve a key for a repository If needed, prompt for if the key should
be imported using callback
@param repo: Repository object to retrieve the key of.
+ @param destdir: destination of the gpg pub ring
+ @param keyurl_list: list of urls for gpg keys
+ @param is_cakey: bool - are we pulling in a ca key or not
@param callback: Callback function to use for asking for verification
of a key. Takes a dictionary of key info.
"""
- keyurls = repo.gpgkey
+
key_installed = False
- for keyurl in keyurls:
- keys = self._retrievePublicKey(keyurl, repo)
+ for keyurl in keyurl_list:
+ keys = self._retrievePublicKey(keyurl, repo, getSig=not is_cakey)
for info in keys:
# Check if key is already installed
- if info['keyid'] in misc.return_keyids_from_pubring(repo.gpgdir):
+ if hex(int(info['keyid']))[2:-1].upper() in misc.return_keyids_from_pubring(destdir):
self.logger.info(_('GPG key at %s (0x%s) is already imported') % (
keyurl, info['hexkeyid']))
+ key_installed = True
continue
# Try installing/updating GPG key
- self._getKeyImportMessage(info, keyurl)
- rc = False
- if self.conf.assumeyes:
- rc = True
- elif callback:
- rc = callback({"repo": repo, "userid": info['userid'],
- "hexkeyid": info['hexkeyid'], "keyurl": keyurl,
- "fingerprint": info['fingerprint'],
- "timestamp": info['timestamp']})
-
-
- if not rc:
- raise Errors.YumBaseError, _("Not installing key for repo %s") % repo
+ if is_cakey:
+ keytype = 'CA'
+ else:
+ keytype = 'GPG'
+
+ if repo.gpgcakey and info['has_sig'] and info['valid_sig']:
+ key_installed = True
+ else:
+ self._getKeyImportMessage(info, keyurl, keytype)
+ rc = False
+ if self.conf.assumeyes:
+ rc = True
+ elif callback:
+ rc = callback({"repo": repo, "userid": info['userid'],
+ "hexkeyid": info['hexkeyid'], "keyurl": keyurl,
+ "fingerprint": info['fingerprint'],
+ "timestamp": info['timestamp']})
+
+
+ if not rc:
+ raise Errors.YumBaseError, _("Not installing key for repo %s") % repo
# Import the key
- result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=repo.gpgdir)
+ result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=destdir)
if not result:
raise Errors.YumBaseError, _('Key import failed')
self.logger.info(_('Key imported successfully'))
@@ -4495,6 +4556,29 @@ class YumBase(depsolve.Depsolve):
'Check that the correct key URLs are configured for ' \
'this repository.') % (repo.name)
+ def getKeyForRepo(self, repo, callback=None):
+ """
+ Retrieve a key for a repository If needed, prompt for if the key should
+ be imported using callback
+
+ @param repo: Repository object to retrieve the key of.
+ @param callback: Callback function to use for asking for verification
+ of a key. Takes a dictionary of key info.
+ """
+ self._getAnyKeyForRepo(repo, repo.gpgdir, repo.gpgkey, is_cakey=False, callback=callback)
+
+ def getCAKeyForRepo(self, repo, callback=None):
+ """
+ Retrieve a key for a repository If needed, prompt for if the key should
+ be imported using callback
+
+ @param repo: Repository object to retrieve the key of.
+ @param callback: Callback function to use for asking for verification
+ of a key. Takes a dictionary of key info.
+ """
+
+ self._getAnyKeyForRepo(repo, repo.gpgcadir, repo.gpgcakey, is_cakey=True, callback=callback)
+
def _limit_installonly_pkgs(self):
""" Limit packages based on conf.installonly_limit, if any of the
packages being installed have a provide in conf.installonlypkgs.
@@ -4772,6 +4856,7 @@ class YumBase(depsolve.Depsolve):
newrepo.gpgcheck = self.conf.gpgcheck
newrepo.repo_gpgcheck = self.conf.repo_gpgcheck
newrepo.basecachedir = self.conf.cachedir
+ newrepo.base_persistdir = self.conf._repos_persistdir
for key in kwargs.keys():
if not hasattr(newrepo, key): continue # skip the ones which aren't vars
diff --git a/yum/config.py b/yum/config.py
index 14eb992..97e5e3d 100644
--- a/yum/config.py
+++ b/yum/config.py
@@ -794,6 +794,7 @@ class RepoConf(BaseConfig):
metalink = UrlOption()
mediaid = Option()
gpgkey = UrlListOption()
+ gpgcakey = UrlListOption()
exclude = ListOption()
includepkgs = ListOption()
diff --git a/yum/repos.py b/yum/repos.py
index 4b74ac6..f11ed52 100644
--- a/yum/repos.py
+++ b/yum/repos.py
@@ -32,9 +32,12 @@ class _wrap_ayum_getKeyForRepo:
we have a seperate class).
A "better" fix might be to explicitly pass the YumBase instance to
the callback ... API change! """
- def __init__(self, ayum):
+ def __init__(self, ayum, ca=False):
self.ayum = weakref(ayum)
+ self.ca = ca
def __call__(self, repo, callback=None):
+ if self.ca:
+ return self.ayum.getCAKeyForRepo(repo, callback)
return self.ayum.getKeyForRepo(repo, callback)
class RepoStorage:
@@ -57,6 +60,7 @@ class RepoStorage:
# even quasi-useful
# defaults to what is probably sane-ish
self.gpg_import_func = _wrap_ayum_getKeyForRepo(ayum)
+ self.gpgca_import_func = _wrap_ayum_getKeyForRepo(ayum, ca=True)
self.confirm_func = None
# This allow listEnabled() to be O(1) most of the time.
@@ -77,7 +81,8 @@ class RepoStorage:
for repo in repos:
repo.setup(self.ayum.conf.cache, self.ayum.mediagrabber,
- gpg_import_func = self.gpg_import_func, confirm_func=self.confirm_func)
+ gpg_import_func = self.gpg_import_func, confirm_func=self.confirm_func,
+ gpgca_import_func = self.gpgca_import_func)
# if we come back from setup NOT enabled then mark as disabled
# so nothing else touches us
if not repo.enabled:
diff --git a/yum/yumRepo.py b/yum/yumRepo.py
index b0e23c6..4926e9d 100644
--- a/yum/yumRepo.py
+++ b/yum/yumRepo.py
@@ -255,6 +255,7 @@ class YumRepository(Repository, config.RepoConf):
# config is very, very old
# throw in some stubs for things that will be set by the config class
self.basecachedir = ""
+ self.base_persistdir = ""
self.cost = 1000
self.copy_local = 0
# holder for stuff we've grabbed
@@ -273,6 +274,7 @@ class YumRepository(Repository, config.RepoConf):
# callbacks for gpg key importing and confirmation
self.gpg_import_func = None
+ self.gpgca_import_func = None
self.confirm_func = None
# The reason we want to turn this off are things like repoids
@@ -368,7 +370,8 @@ class YumRepository(Repository, config.RepoConf):
# we exclude all vars which start with _ or are in this list:
excluded_vars = ('mediafunc', 'sack', 'metalink_data', 'grab',
'grabfunc', 'repoXML', 'cfg', 'retrieved',
- 'mirrorlistparsed', 'gpg_import_func', 'failure_obj',
+ 'mirrorlistparsed', 'gpg_import_func',
+ 'gpgca_import_func', 'failure_obj',
'callback', 'confirm_func', 'groups_added',
'interrupt_callback', 'id', 'mirror_failure_obj',
'repo_config_age', 'groupsfilename', 'copy_local',
@@ -541,12 +544,15 @@ class YumRepository(Repository, config.RepoConf):
"""make the necessary dirs, if possible, raise on failure"""
cachedir = os.path.join(self.basecachedir, self.id)
+ persistdir = os.path.join(self.base_persistdir, self.id)
pkgdir = os.path.join(cachedir, 'packages')
hdrdir = os.path.join(cachedir, 'headers')
self.setAttribute('_dir_setup_cachedir', cachedir)
self.setAttribute('_dir_setup_pkgdir', pkgdir)
self.setAttribute('_dir_setup_hdrdir', hdrdir)
- self.setAttribute('_dir_setup_gpgdir', self.cachedir + '/gpgdir')
+ self.setAttribute('_dir_setup_persistdir', persistdir)
+ self.setAttribute('_dir_setup_gpgdir', persistdir + '/gpgdir')
+ self.setAttribute('_dir_setup_gpgcadir', persistdir + '/gpgcadir')
cookie = self.cachedir + '/' + self.metadata_cookie_fn
self.setAttribute('_dir_setup_metadata_cookie', cookie)
@@ -554,6 +560,14 @@ class YumRepository(Repository, config.RepoConf):
for dir in [self.cachedir, self.pkgdir]:
self._dirSetupMkdir_p(dir)
+ # persistdir is really root-only but try the make anyway and just
+ # catch the exception
+ for dir in [self.persistdir]:
+ try:
+ self._dirSetupMkdir_p(dir)
+ except Errors.RepoError, e:
+ pass
+
# if we're using a cachedir that's not the system one, copy over these
# basic items from the system one
self._preload_md_from_system_cache('repomd.xml')
@@ -583,12 +597,16 @@ class YumRepository(Repository, config.RepoConf):
self._dirSetupMkdir_p(val)
return ret
cachedir = property(lambda self: self._dirGetAttr('cachedir'))
+ persistdir = property(lambda self: self._dirGetAttr('persistdir'))
+
pkgdir = property(lambda self: self._dirGetAttr('pkgdir'),
lambda self, x: self._dirSetAttr('pkgdir', x))
hdrdir = property(lambda self: self._dirGetAttr('hdrdir'),
lambda self, x: self._dirSetAttr('hdrdir', x))
gpgdir = property(lambda self: self._dirGetAttr('gpgdir'),
lambda self, x: self._dirSetAttr('gpgdir', x))
+ gpgcadir = property(lambda self: self._dirGetAttr('gpgcadir'),
+ lambda self, x: self._dirSetAttr('gpgcadir', x))
metadata_cookie = property(lambda self: self._dirGetAttr('metadata_cookie'))
def baseurlSetup(self):
@@ -947,11 +965,12 @@ class YumRepository(Repository, config.RepoConf):
fo.close()
del fo
- def setup(self, cache, mediafunc = None, gpg_import_func=None, confirm_func=None):
+ def setup(self, cache, mediafunc = None, gpg_import_func=None, confirm_func=None, gpgca_import_func=None):
try:
self.cache = cache
self.mediafunc = mediafunc
self.gpg_import_func = gpg_import_func
+ self.gpgca_import_func = gpgca_import_func
self.confirm_func = confirm_func
except Errors.RepoError, e:
raise
@@ -1451,7 +1470,6 @@ class YumRepository(Repository, config.RepoConf):
size=102400)
except URLGrabError, e:
raise URLGrabError(-1, 'Error finding signature for repomd.xml for %s: %s' % (self, e))
-
valid = misc.valid_detached_sig(result, filepath, self.gpgdir)
if not valid and self.gpg_import_func:
try:
--
1.7.2.1
More information about the Yum-devel
mailing list