Hi, Am Montag, 21. März 2011, 15:56:50 schrieb Michael Mraka: > Michael Calmer wrote: > % Hi, > ... > % > % I would suggest, that we go step by step. So let's wait until "weak deps" > % and > % "updateinfo to errata" is applied. After this I will submit a new patch > % for this feature based on the new master. > > Sure, no problem.
I re-worked the patches now. spacewalk-repo-sync uses now a fix directory with a GPG keyring to store trusted keys. Please review. Thanks. -- Regards Michael Calmer -------------------------------------------------------------------------- Michael Calmer SUSE LINUX Products GmbH, Maxfeldstr. 5, D-90409 Nuernberg T: +49 (0) 911 74053 0 F: +49 (0) 911 74053575 - e-mail: michael.cal...@suse.com -------------------------------------------------------------------------- SUSE LINUX Products GmbH, GF: Jeff Hawn, Jennifer Guild, Felix Imendörffer HRB 16746 (AG Nürnberg)
From d7c0e9d8ae3684566ae79573d1e8e369e2388323 Mon Sep 17 00:00:00 2001 From: Michael Calmer <m...@suse.de> Date: Sat, 21 May 2011 15:56:47 +0200 Subject: [PATCH 1/3] add column metadata_signed to rhnContentSource table --- .../spacewalk/common/tables/rhnContentSource.sql | 4 ++++ .../019-add-metadata_signed.sql | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 0 deletions(-) create mode 100644 schema/spacewalk/upgrade/spacewalk-schema-1.4-to-spacewalk-schema-1.5/019-add-metadata_signed.sql diff --git a/schema/spacewalk/common/tables/rhnContentSource.sql b/schema/spacewalk/common/tables/rhnContentSource.sql index dc93a27..2f36f2e 100644 --- a/schema/spacewalk/common/tables/rhnContentSource.sql +++ b/schema/spacewalk/common/tables/rhnContentSource.sql @@ -29,6 +29,10 @@ rhnContentSource references rhnContentSourceType(id), source_url varchar2(512) NOT NULL, label varchar2(64) NOT NULL, + metadata_signed CHAR(1) + DEFAULT ('N') NOT NULL + CONSTRAINT rhn_cs_ms_ck + CHECK (metadata_signed in ( 'Y' , 'N' )), created date default(sysdate) NOT NULL, modified date default(sysdate) NOT NULL ) diff --git a/schema/spacewalk/upgrade/spacewalk-schema-1.4-to-spacewalk-schema-1.5/019-add-metadata_signed.sql b/schema/spacewalk/upgrade/spacewalk-schema-1.4-to-spacewalk-schema-1.5/019-add-metadata_signed.sql new file mode 100644 index 0000000..8839641 --- /dev/null +++ b/schema/spacewalk/upgrade/spacewalk-schema-1.4-to-spacewalk-schema-1.5/019-add-metadata_signed.sql @@ -0,0 +1,16 @@ +-- +-- Copyright (c) 2011 SUSE LINUX Products GmbH +-- +-- This software is licensed to you under the GNU General Public License, +-- version 2 (GPLv2). There is NO WARRANTY for this software, express or +-- implied, including the implied warranties of MERCHANTABILITY or FITNESS +-- FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 +-- along with this software; if not, see +-- http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. +-- +-- +alter table rhnContentSource +add metadata_signed char(1) default('N') + constraint rhn_cs_ms_nn not null + constraint rhn_cs_ms_ck + check (metadata_signed in ('Y','N')); -- 1.7.3.4
From 77ea4ace147ac72ebc2834bb8954b53198278da5 Mon Sep 17 00:00:00 2001 From: Michael Calmer <m...@suse.de> Date: Sat, 21 May 2011 16:17:29 +0200 Subject: [PATCH 2/3] add has signed metadata checkbox to manage repositories page --- .../rhn/domain/channel/ContentSource.hbm.xml | 1 + .../redhat/rhn/domain/channel/ContentSource.java | 17 ++++++++++++++++ .../channel/manage/repo/RepoDetailsAction.java | 16 +++++++++++++++ .../frontend/strings/jsp/StringResource_en_US.xml | 3 ++ .../rhn/manager/channel/repo/BaseRepoCommand.java | 21 ++++++++++++++++++++ .../redhat/rhn/taskomatic/task/RepoSyncTask.java | 1 + .../pages/channel/manage/repo/repodetails.jsp | 8 +++++++ java/code/webapp/WEB-INF/struts-config.xml | 1 + 8 files changed, 68 insertions(+), 0 deletions(-) diff --git a/java/code/src/com/redhat/rhn/domain/channel/ContentSource.hbm.xml b/java/code/src/com/redhat/rhn/domain/channel/ContentSource.hbm.xml index 79ad517..39bf85f 100644 --- a/java/code/src/com/redhat/rhn/domain/channel/ContentSource.hbm.xml +++ b/java/code/src/com/redhat/rhn/domain/channel/ContentSource.hbm.xml @@ -17,6 +17,7 @@ PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" <property name="created" type="timestamp" column="created" insert="false" update="false" /> <property name="modified" type="timestamp" column="modified" insert="false" update="false" /> <property name="label" type="string" column="label"/> + <property name="metadataSigned" type="yes_no" column="metadata_signed"/> <many-to-one name="org" column="org_id" diff --git a/java/code/src/com/redhat/rhn/domain/channel/ContentSource.java b/java/code/src/com/redhat/rhn/domain/channel/ContentSource.java index 54115b5..eb991ed 100644 --- a/java/code/src/com/redhat/rhn/domain/channel/ContentSource.java +++ b/java/code/src/com/redhat/rhn/domain/channel/ContentSource.java @@ -12,6 +12,9 @@ * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ +/* + * Copyright (c) 2010 SUSE LINUX Products GmbH, Nuernberg, Germany. + */ package com.redhat.rhn.domain.channel; import com.redhat.rhn.domain.BaseDomainHelper; @@ -32,6 +35,7 @@ public class ContentSource extends BaseDomainHelper implements Identifiable { private ContentSourceType type; private String sourceUrl; private String label; + private boolean metadataSigned; private Set<Channel> channels = new HashSet<Channel>(); /** * @return Returns the label. @@ -78,6 +82,19 @@ public class ContentSource extends BaseDomainHelper implements Identifiable { this.id = idIn; } + /** + * @return Returns metadataSigned + */ + public boolean getMetadataSigned() { + return this.metadataSigned; + } + + /** + * @param md set metadataSigned + */ + public void setMetadataSigned(boolean md) { + this.metadataSigned = md; + } /** * @return Returns the type. diff --git a/java/code/src/com/redhat/rhn/frontend/action/channel/manage/repo/RepoDetailsAction.java b/java/code/src/com/redhat/rhn/frontend/action/channel/manage/repo/RepoDetailsAction.java index 029e55b..4112a47 100644 --- a/java/code/src/com/redhat/rhn/frontend/action/channel/manage/repo/RepoDetailsAction.java +++ b/java/code/src/com/redhat/rhn/frontend/action/channel/manage/repo/RepoDetailsAction.java @@ -12,6 +12,9 @@ * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ +/* + * Copyright (c) 2010 SUSE LINUX Products GmbH, Nuernberg, Germany. + */ package com.redhat.rhn.frontend.action.channel.manage.repo; import java.util.HashMap; @@ -54,6 +57,7 @@ public class RepoDetailsAction extends RhnAction { public static final String URL = "url"; public static final String LABEL = "label"; public static final String SOURCEID = "sourceid"; + public static final String METADATA_SIGNED = "metadataSigned"; private static final String VALIDATION_XSD = "/com/redhat/rhn/frontend/action/channel/" + @@ -112,6 +116,10 @@ public class RepoDetailsAction extends RhnAction { else if (!isCreateMode(request)) { setup(request, form); } + else if (isCreateMode(request)) { + // default for has signed metadata should be true + form.set(METADATA_SIGNED, new Boolean(true)); + } return mapping.findForward(RhnHelper.DEFAULT_FORWARD); } @@ -140,6 +148,7 @@ public class RepoDetailsAction extends RhnAction { form.set(LABEL, repo.getLabel()); form.set(URL, repo.getSourceUrl()); form.set(SOURCEID, repo.getId()); + form.set(METADATA_SIGNED, new Boolean(repo.getMetadataSigned())); bindRepo(request, repo); } @@ -157,6 +166,12 @@ public class RepoDetailsAction extends RhnAction { RequestContext context = new RequestContext(request); String url = form.getString(URL); String label = form.getString(LABEL); + Boolean metadataSigned = (Boolean) form.get(METADATA_SIGNED); + if (metadataSigned == null) { + // disabled checkbox doesn't return a value, so result is null + // set it to false + metadataSigned = Boolean.FALSE; + } Org org = context.getLoggedInUser().getOrg(); BaseRepoCommand repoCmd = null; if (isCreateMode(request)) { @@ -169,6 +184,7 @@ public class RepoDetailsAction extends RhnAction { repoCmd.setLabel(label); repoCmd.setUrl(url); + repoCmd.setMetadataSigned(metadataSigned.booleanValue()); try { repoCmd.store(); diff --git a/java/code/src/com/redhat/rhn/frontend/strings/jsp/StringResource_en_US.xml b/java/code/src/com/redhat/rhn/frontend/strings/jsp/StringResource_en_US.xml index 1cf501e..19ed25d 100644 --- a/java/code/src/com/redhat/rhn/frontend/strings/jsp/StringResource_en_US.xml +++ b/java/code/src/com/redhat/rhn/frontend/strings/jsp/StringResource_en_US.xml @@ -9869,6 +9869,9 @@ Please note that some manual configuration of these scripts may still be require <trans-unit id="repos.jsp.create.url"> <source>Repository URL</source> </trans-unit> + <trans-unit id="repos.jsp.create.metadataSigned"> + <source>Has Signed Metadata?</source> + </trans-unit> <trans-unit id="repos.jsp.create.submit"> <source>Create Repository</source> </trans-unit> diff --git a/java/code/src/com/redhat/rhn/manager/channel/repo/BaseRepoCommand.java b/java/code/src/com/redhat/rhn/manager/channel/repo/BaseRepoCommand.java index 849459e..0f6be98 100644 --- a/java/code/src/com/redhat/rhn/manager/channel/repo/BaseRepoCommand.java +++ b/java/code/src/com/redhat/rhn/manager/channel/repo/BaseRepoCommand.java @@ -12,6 +12,9 @@ * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ +/* + * Copyright (c) 2010 SUSE LINUX Products GmbH, Nuernberg, Germany. + */ package com.redhat.rhn.manager.channel.repo; import java.util.List; @@ -34,6 +37,7 @@ public class BaseRepoCommand { private String label; private String url; private Org org; + private boolean metadata_signed; private List<ValidatorError> errors; /** @@ -91,6 +95,22 @@ public class BaseRepoCommand { } /** + * + * @return true if metadata should be signed + */ + public boolean getMetadataSigned() { + return metadata_signed; + } + + /** + * + * @param md set if metadata are signed + */ + public void setMetadataSigned(boolean md) { + this.metadata_signed = md; + } + + /** * Check for errors and store Org to db. * @throws InvalidRepoUrlException in case repo wih given url already exists * in the org @@ -115,6 +135,7 @@ public class BaseRepoCommand { } repo.setSourceUrl(this.url); } + repo.setMetadataSigned(this.metadata_signed); ChannelFactory.save(repo); } diff --git a/java/code/src/com/redhat/rhn/taskomatic/task/RepoSyncTask.java b/java/code/src/com/redhat/rhn/taskomatic/task/RepoSyncTask.java index dfc6ad3..32e4dd6 100644 --- a/java/code/src/com/redhat/rhn/taskomatic/task/RepoSyncTask.java +++ b/java/code/src/com/redhat/rhn/taskomatic/task/RepoSyncTask.java @@ -75,6 +75,7 @@ public class RepoSyncTask extends RhnJavaJob { cmd.add(c.getLabel()); cmd.add("--type"); cmd.add(ChannelFactory.CONTENT_SOURCE_TYPE_YUM.getLabel()); + cmd.add("--non-interactive"); return cmd; } } diff --git a/java/code/webapp/WEB-INF/pages/channel/manage/repo/repodetails.jsp b/java/code/webapp/WEB-INF/pages/channel/manage/repo/repodetails.jsp index 28ac129..da600ee 100644 --- a/java/code/webapp/WEB-INF/pages/channel/manage/repo/repodetails.jsp +++ b/java/code/webapp/WEB-INF/pages/channel/manage/repo/repodetails.jsp @@ -55,6 +55,14 @@ <html:text property="url"/> </td> </tr> + <tr> + <th> + <bean:message key="repos.jsp.create.metadataSigned"/> + </th> + <td> + <html:checkbox property="metadataSigned" /> + </td> + </tr> </table> diff --git a/java/code/webapp/WEB-INF/struts-config.xml b/java/code/webapp/WEB-INF/struts-config.xml index 3d43fe0..6f4e735 100644 --- a/java/code/webapp/WEB-INF/struts-config.xml +++ b/java/code/webapp/WEB-INF/struts-config.xml @@ -865,6 +865,7 @@ <form-property name="url" type="java.lang.String"/> <form-property name="sourceid" type="java.lang.Long" /> <form-property name="label" type="java.lang.String"/> + <form-property name="metadataSigned" type="java.lang.Boolean"/> </form-bean> <form-bean name="cobblerSnippetsForm" -- 1.7.3.4
From f6dc66e675320d3f45785badc66cc40caee68251 Mon Sep 17 00:00:00 2001 From: Michael Calmer <m...@suse.de> Date: Sat, 21 May 2011 18:23:45 +0200 Subject: [PATCH 3/3] implement check of signed metadata before download if metadata_signed in DB is set to Y, download the signature of repomd.xml and check it against a keyring with trusted keys. If the key is not in this keyring, try to download the key and ask the user if he trust the key and import it. If the new option --non-interactive is used, the key is not imported - the sync fail. --- backend/satellite_tools/repo_plugins/yum_src.py | 130 ++++++++++++++++++++++- backend/satellite_tools/reposync.py | 44 +++++++-- 2 files changed, 164 insertions(+), 10 deletions(-) diff --git a/backend/satellite_tools/repo_plugins/yum_src.py b/backend/satellite_tools/repo_plugins/yum_src.py index bfc6161..f89f03b 100644 --- a/backend/satellite_tools/repo_plugins/yum_src.py +++ b/backend/satellite_tools/repo_plugins/yum_src.py @@ -16,9 +16,12 @@ import yum import shutil import sys +import os import gzip from yum.update_md import UpdateMetadata, UpdateNoticeException, UpdateNotice from yum.yumRepo import YumRepository +from yum import misc +from yum.i18n import to_unicode try: from yum.misc import cElementTree_iterparse as iterparse except ImportError: @@ -27,8 +30,10 @@ except ImportError: except ImportError: import cElementTree iterparse = cElementTree.iterparse -from spacewalk.satellite_tools.reposync import ContentPackage +from spacewalk.satellite_tools.reposync import ContentPackage, ChannelException from spacewalk.common.rhnConfig import CFG, initCFG +import urlgrabber +from urlgrabber.grabber import URLGrabber, default_grabber class YumWarnings: def write(self, s): @@ -79,9 +84,11 @@ class ContentSource: name = None repo = None cache_dir = '/var/cache/rhn/reposync/' - def __init__(self, url, name): + def __init__(self, url, name, insecure=False, interactive=True): self.url = url self.name = name + self.insecure = insecure + self.interactive = interactive self._clean_cache(self.cache_dir + name) # read the proxy configuration in /etc/rhn/rhn.conf @@ -109,14 +116,24 @@ class ContentSource: if self.proxy_url is not None: repo.proxy = self.proxy_url + if self.insecure: + repo.repo_gpgcheck = False + else: + repo.repo_gpgcheck = True warnings = YumWarnings() warnings.disable() repo.baseurlSetup() warnings.restore() + for burl in repo.baseurl: + repo.gpgkey = [burl + '/repodata/repomd.xml.key'] - repo.setup(False) + repo.setup(False, None, gpg_import_func=self.getKeyForRepo, confirm_func=self.askImportKey) + # use a fix dir for repo metadata sig checks + repo.gpgdir = "/var/lib/spacewalk/gpgdir" + self.initgpgdir( repo.gpgdir ) sack = repo.getPackageSack() sack.populate(repo, 'metadata', None, 0) + list = sack.returnPackages() to_return = [] for pack in list: @@ -150,3 +167,110 @@ class ContentSource: um = YumUpdateMetadata() um.add(self.repo, all=True) return um.notices + + 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. + """ + keyurls = repo.gpgkey + key_installed = False + for keyurl in keyurls: + keys = self._retrievePublicKey(keyurl, repo) + for info in keys: + # Check if the retrived key is already installed + if info['keyid'] in misc.return_keyids_from_pubring(repo.gpgdir): + #print('GPG key at %s (0x%s) is already imported' % ( + # keyurl, info['hexkeyid'])) + continue + + rc = False + if callback: + rc = callback({'repo':repo, 'userid':info['userid'], + 'hexkeyid':info['hexkeyid'], 'keyurl':keyurl, + 'fingerprint':info['fingerprint'], + 'timestamp':info['timestamp']}) + + + if not rc: + raise ChannelException, "GPG key (0x%s '%s') for repo %s rejected." % ( + info['hexkeyid'], info['userid'], repo) + + # Import the key + result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=repo.gpgdir) + if not result: + raise ChannelException, 'Key import failed' + + key_installed = True + + if not key_installed: + raise ChannelException, 'The GPG keys listed for the "%s" repository are ' \ + 'already installed but they are not correct.\n' \ + 'Check that the correct key URLs are configured for ' \ + 'this repository.' % (repo) + + + def _retrievePublicKey(self, keyurl, repo=None): + """ + Retrieve a key file + @param keyurl: url to the key to retrieve + Returns a list of dicts with all the keyinfo + """ + key_installed = False + + # Go get the GPG key from the given URL + try: + url = misc.to_utf8(keyurl) + if repo is None: + rawkey = urlgrabber.urlread(url, limit=9999) + else: + # If we have a repo. use the proxy etc. configuration for it. + # In theory we have a global proxy config. too, but meh... + # external callers should just update. + ug = URLGrabber(bandwidth = repo.bandwidth, + retry = repo.retries, + throttle = repo.throttle, + progress_obj = repo.callback, + proxies=repo.proxy_dict) + ug.opts.user_agent = default_grabber.opts.user_agent + rawkey = ug.urlread(url, text=repo.id + "/gpgkey") + + except urlgrabber.grabber.URLGrabError, e: + raise ChannelException('GPG key retrieval failed: ' + + to_unicode(str(e))) + # Parse the key + keys_info = misc.getgpgkeyinfo(rawkey, multiple=True) + keys = [] + for keyinfo in keys_info: + thiskey = {} + for info in ('keyid', 'timestamp', 'userid', + 'fingerprint', 'raw_key'): + if not keyinfo.has_key(info): + raise ChannelException, \ + 'GPG key parsing failed: key does not have value %s' % info + thiskey[info] = keyinfo[info] + thiskey['keyid'] = str("%16x" % (thiskey['keyid'] & 0xffffffffffffffffL)).upper() + thiskey['hexkeyid'] = misc.keyIdToRPMVer(keyinfo['keyid']).upper() + keys.append(thiskey) + + return keys + + def askImportKey(self, d ): + if self.interactive: + print 'Do you want to import the GPG key 0x%s "%s" from %s? [y/n]:' % (d['hexkeyid'], to_unicode(d['userid']), d['keyurl'],) + yn = sys.stdin.readline() + yn = yn.strip() + + if yn in ['y', 'Y', 'j', 'J']: + return True + + return False + + def initgpgdir(self, gpgdir): + if not os.path.exists(gpgdir): + os.makedirs(gpgdir) + diff --git a/backend/satellite_tools/reposync.py b/backend/satellite_tools/reposync.py index 6834adb..53291b2 100644 --- a/backend/satellite_tools/reposync.py +++ b/backend/satellite_tools/reposync.py @@ -30,10 +30,25 @@ from spacewalk.server.importlib.packageImport import ChannelPackageSubscription from spacewalk.server.importlib.backendOracle import SQLBackend from spacewalk.server.importlib.errataImport import ErrataImport from spacewalk.server import taskomatic +from yum import Errors +from yum.i18n import to_unicode, to_utf8 default_log_location = '/var/log/rhn/reposync/' default_hash = 'sha256' +class ChannelException(Exception): + """ + Channel Error. + """ + def __init__(self, value=None): + Exception.__init__(self) + self.value = value + def __str__(self): + return "%s" %(self.value,) + + def __unicode__(self): + return '%s' % to_unicode(self.value) + class RepoSync: parser = None @@ -44,6 +59,7 @@ class RepoSync: fail = False quiet = False regen = False + noninteractive = False def main(self): initCFG('server') @@ -69,7 +85,7 @@ class RepoSync: if not options.url: if options.channel_label: # TODO:need to look at user security across orgs - h = rhnSQL.prepare("""select s.source_url + h = rhnSQL.prepare("""select s.source_url, s.metadata_signed from rhnContentSource s, rhnChannelContentSource cs, rhnChannel c @@ -79,12 +95,12 @@ class RepoSync: h.execute(label=options.channel_label) source_urls = h.fetchall_dict() or [] if source_urls: - self.urls = [row['source_url'] for row in source_urls] + self.urls = source_urls else: quit = True self.error_msg("Channel has no URL associated") else: - self.urls = [options.url] + self.urls = [{'source_url':options.url, 'metadata_signed' : 'N'}] if not options.channel_label: quit = True self.error_msg("--channel must be specified") @@ -101,15 +117,28 @@ class RepoSync: self.fail = options.fail self.quiet = options.quiet self.channel = self.load_channel() + self.noninteractive = options.noninteractive if not self.channel or not rhnChannel.isCustomChannel(self.channel['id']): print "Channel does not exist or is not custom" sys.exit(1) - for url in self.urls: - plugin = self.load_plugin()(url, self.channel_label) - self.import_packages(plugin, url) - self.import_updates(plugin, url) + for data in self.urls: + url = data['source_url'] + insecure = False; + if data['metadata_signed'] == 'N': + insecure = True; + try: + plugin = self.load_plugin()(url, self.channel_label, insecure, (not self.noninteractive)) + self.import_packages(plugin, url) + self.import_updates(plugin, url) + except ChannelException, e: + self.print_msg("ChannelException: %s" % e) + sys.exit(1) + except Errors.YumGPGCheckError, e: + self.print_msg("YumGPGCheckError: : %s" % e) + sys.exit(1) + if self.regen: taskomatic.add_to_repodata_queue_for_channel_package_subscription( [self.channel_label], [], "server.app.yumreposync") @@ -132,6 +161,7 @@ class RepoSync: self.parser.add_option('-t', '--type', action='store', dest='type', help='The type of repo, currently only "yum" is supported', default='yum') self.parser.add_option('-f', '--fail', action='store_true', dest='fail', default=False , help="If a package import fails, fail the entire operation") self.parser.add_option('-q', '--quiet', action='store_true', dest='quiet', default=False, help="Print no output, still logs output") + self.parser.add_option('-n', '--non-interactive', action='store_true', dest='noninteractive', default=False, help="Do not ask anything, use default answers automatically.") return self.parser.parse_args() def load_plugin(self): -- 1.7.3.4
_______________________________________________ Spacewalk-devel mailing list Spacewalk-devel@redhat.com https://www.redhat.com/mailman/listinfo/spacewalk-devel