From dca09871e61186deb216ce5cb1fc5db3b69c9fc2 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 09:18:17 +0200 Subject: pythonify getkeybyid and move it to uitl.py --- cloudinit/config/cc_apt_configure.py | 27 +------------------------ cloudinit/util.py | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 26 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index 7a9777c0..2f270662 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -34,21 +34,6 @@ APT_PROXY_FN = "/etc/apt/apt.conf.d/95cloud-init-proxy" # this will match 'XXX:YYY' (ie, 'cloud-archive:foo' or 'ppa:bar') ADD_APT_REPO_MATCH = r"^[\w-]+:\w" -# A temporary shell program to get a given gpg key -# from a given keyserver -EXPORT_GPG_KEYID = """ - k=${1} ks=${2}; - exec 2>/dev/null - [ -n "$k" ] || exit 1; - armour=$(gpg --export --armour "${k}") - if [ -z "${armour}" ]; then - gpg --keyserver ${ks} --recv "${k}" >/dev/null && - armour=$(gpg --export --armour "${k}") && - gpg --batch --yes --delete-keys "${k}" - fi - [ -n "${armour}" ] && echo "${armour}" -""" - def handle(name, cfg, cloud, log, _args): if util.is_false(cfg.get('apt_configure_enabled', True)): @@ -108,16 +93,6 @@ def handle(name, cfg, cloud, log, _args): util.logexc(log, "Failed to run debconf-set-selections") -# get gpg keyid from keyserver -def getkeybyid(keyid, keyserver): - with util.ExtendedTemporaryFile(suffix='.sh', mode="w+", ) as fh: - fh.write(EXPORT_GPG_KEYID) - fh.flush() - cmd = ['/bin/sh', fh.name, keyid, keyserver] - (stdout, _stderr) = util.subp(cmd) - return stdout.strip() - - def mirror2lists_fileprefix(mirror): string = mirror # take off http:// or ftp:// @@ -192,7 +167,7 @@ def add_key(ent): keyserver = "keyserver.ubuntu.com" if 'keyserver' in ent: keyserver = ent['keyserver'] - ent['key'] = getkeybyid(ent['keyid'], keyserver) + ent['key'] = util.getkeybyid(ent['keyid'], keyserver) if 'key' in ent: add_key_raw(ent['key']) diff --git a/cloudinit/util.py b/cloudinit/util.py index d6b80dbe..d3b14f72 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2234,3 +2234,41 @@ def message_from_string(string): if sys.version_info[:2] < (2, 7): return email.message_from_file(six.StringIO(string)) return email.message_from_string(string) + + +def gpg_export_armour(key): + """Export gpg key, armoured key gets returned""" + (armour, _) = subp(["gpg", "--export", "--armour", key], capture=True) + return armour + + +def gpg_recv_key(key, keyserver): + """Receive gpg key from the specified keyserver""" + try: + subp(["gpg", "--keyserver", keyserver, "--recv", key], + capture=True) + except ProcessExecutionError as error: + raise ValueError('Failed to import key %s from server %s - error %s' % + (key, keyserver, error)) + + +def gpg_delete_key(key): + """Delete the specified key from the local gpg ring""" + subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=False) + + +def getkeybyid(keyid, keyserver): + """get gpg keyid from keyserver""" + armour = gpg_export_armour(keyid) + if not armour: + try: + gpg_recv_key(keyid, keyserver=keyserver) + except ValueError: + LOG.exception('Failed to obtain gpg key %s', keyid) + raise + + armour = gpg_export_armour(keyid) + # delete just imported key to leave environment as it was before + gpg_delete_key(keyid) + + return armour.rstrip('\n') -- cgit v1.2.3 From 89688593e9358433cd9383d1bdfae12dbcd58f72 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 13:36:23 +0200 Subject: add_key_raw - make exceptions more specific --- cloudinit/config/cc_apt_configure.py | 4 +-- cloudinit/util.py | 1 + .../test_handler/test_handler_apt_source.py | 38 +++++++++++++++++++--- 3 files changed, 36 insertions(+), 7 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index 2f270662..a9ac6ea8 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -154,7 +154,7 @@ def add_key_raw(key): try: util.subp(('apt-key', 'add', '-'), key) except util.ProcessExecutionError: - raise Exception('failed add key') + raise ValueError('failed to add apt GPG Key to apt keyring') def add_key(ent): @@ -221,7 +221,7 @@ def add_sources(srclist, template_params=None, aa_repo_match=None): # keys can be added without specifying a source try: add_key(ent) - except Exception as detail: + except ValueError as detail: errorlist.append([ent, detail]) if 'source' not in ent: diff --git a/cloudinit/util.py b/cloudinit/util.py index d3b14f72..a1622946 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2244,6 +2244,7 @@ def gpg_export_armour(key): def gpg_recv_key(key, keyserver): """Receive gpg key from the specified keyserver""" + print("ORIGINAL gpg_recv_key") try: subp(["gpg", "--keyserver", keyserver, "--recv", key], capture=True) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index a4878124..65e375a0 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -4,6 +4,7 @@ Testing various config variations of the apt_source config import os import re import shutil +import socket import tempfile try: @@ -56,6 +57,7 @@ class TestAptSourceConfig(TestCase): # mock fallback filename into writable tmp dir self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") + self.orig_gpg_recv_key = util.gpg_recv_key patcher = mock.patch("cloudinit.config.cc_apt_configure.get_release") get_rel = patcher.start() @@ -445,14 +447,40 @@ class TestAptSourceConfig(TestCase): def apt_src_keyid_real(self, cfg, expectedkey): """apt_src_keyid_real Test specification of a keyid without source including - up to addition of the key (nothing but add_key_raw mocked to keep the + up to addition of the key (add_key_raw mocked to keep the environment as is) """ params = self._get_default_params() - with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockobj: - cc_apt_configure.add_sources([cfg], params) - mockobj.assert_called_with(expectedkey) + def fake_gpg_recv_key(self, key, keyserver): + """try original gpg_recv_key, but allow fall back""" + try: + print("Try orig orig_gpg_recv_key") + self.orig_gpg_recv_key(key, keyserver) + except ValueError: + print("Fail, test net") + # if this is a networking issue mock it's effect + testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + testsock.connect((keyserver, 80)) + testsock.close() + except socket.error: + # as fallback add the known key as a working recv would + print("Fallback import expectedkey") + util.subp(("gpg", "--import", "-"), EXPECTEDKEY) + + print("FOO") + with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockkey: + with mock.patch.object(util, 'gpg_recv_key', + side_effect=fake_gpg_recv_key) as mockrecv: + cc_apt_configure.add_sources([cfg], params) + + # since we might mock the recv path ensure it is called right + mockrecv.assert_called_with(cfg['keyid'], + keyserver=cfg.get('keyserver', + 'keyserver.ubuntu.com')) + # no matter if really imported or faked, ensure we add the right key + mockkey.assert_called_with(expectedkey) # filename should be ignored on key only self.assertFalse(os.path.isfile(self.aptlistfile)) @@ -477,7 +505,7 @@ class TestAptSourceConfig(TestCase): """test_apt_src_longkeyid_ks_real - Test long keyid from other ks""" keyid = "B59D 5F15 97A5 04B7 E230 6DCA 0620 BBCF 0368 3F77" cfg = {'keyid': keyid, - 'keyserver': 'keys.gnupg.net', + 'keyserver': 'knorz.gnupg.net', 'filename': self.aptlistfile} self.apt_src_keyid_real(cfg, EXPECTEDKEY) -- cgit v1.2.3 From e9b333b49954d5863f8b53581454179b0363d978 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 13:52:31 +0200 Subject: fixup key tests for cases where network isn't available --- cloudinit/util.py | 1 - tests/unittests/test_handler/test_handler_apt_source.py | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/util.py b/cloudinit/util.py index a1622946..d3b14f72 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2244,7 +2244,6 @@ def gpg_export_armour(key): def gpg_recv_key(key, keyserver): """Receive gpg key from the specified keyserver""" - print("ORIGINAL gpg_recv_key") try: subp(["gpg", "--keyserver", keyserver, "--recv", key], capture=True) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 65e375a0..c7eeb64c 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -452,13 +452,11 @@ class TestAptSourceConfig(TestCase): """ params = self._get_default_params() - def fake_gpg_recv_key(self, key, keyserver): + def fake_gpg_recv_key(key, keyserver): """try original gpg_recv_key, but allow fall back""" try: - print("Try orig orig_gpg_recv_key") self.orig_gpg_recv_key(key, keyserver) - except ValueError: - print("Fail, test net") + except ValueError as error: # if this is a networking issue mock it's effect testsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: @@ -466,10 +464,8 @@ class TestAptSourceConfig(TestCase): testsock.close() except socket.error: # as fallback add the known key as a working recv would - print("Fallback import expectedkey") util.subp(("gpg", "--import", "-"), EXPECTEDKEY) - print("FOO") with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', side_effect=fake_gpg_recv_key) as mockrecv: -- cgit v1.2.3 From c1ad09609805c6c4f5262eb21533799af814379a Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 14:23:55 +0200 Subject: rename add_key / add_source to add_apt_key / add_apt_source The functions clearly are apt specific so the name should reflect that. --- cloudinit/config/cc_apt_configure.py | 14 ++++++------ .../test_handler/test_handler_apt_source.py | 25 +++++++++++----------- 2 files changed, 20 insertions(+), 19 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index a9ac6ea8..91a117f5 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -79,8 +79,8 @@ def handle(name, cfg, cloud, log, _args): def matcher(x): return False - errors = add_sources(cfg['apt_sources'], params, - aa_repo_match=matcher) + errors = add_apt_sources(cfg['apt_sources'], params, + aa_repo_match=matcher) for e in errors: log.warn("Add source error: %s", ':'.join(e)) @@ -146,7 +146,7 @@ def generate_sources_list(cfg, codename, mirrors, cloud, log): templater.render_to_file(template_fn, '/etc/apt/sources.list', params) -def add_key_raw(key): +def add_apt_key_raw(key): """ actual adding of a key as defined in key argument to the system @@ -157,7 +157,7 @@ def add_key_raw(key): raise ValueError('failed to add apt GPG Key to apt keyring') -def add_key(ent): +def add_apt_key(ent): """ add key to the system as defined in ent (if any) supports raw keys or keyid's @@ -170,7 +170,7 @@ def add_key(ent): ent['key'] = util.getkeybyid(ent['keyid'], keyserver) if 'key' in ent: - add_key_raw(ent['key']) + add_apt_key_raw(ent['key']) def convert_to_new_format(srclist): @@ -197,7 +197,7 @@ def convert_to_new_format(srclist): return srcdict -def add_sources(srclist, template_params=None, aa_repo_match=None): +def add_apt_sources(srclist, template_params=None, aa_repo_match=None): """ add entries in /etc/apt/sources.list.d for each abbreviated sources.list entry in 'srclist'. When rendering template, also @@ -220,7 +220,7 @@ def add_sources(srclist, template_params=None, aa_repo_match=None): # keys can be added without specifying a source try: - add_key(ent) + add_apt_key(ent) except ValueError as detail: errorlist.append([ent, detail]) diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index c7eeb64c..4a720213 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -89,7 +89,7 @@ class TestAptSourceConfig(TestCase): """ params = self._get_default_params() - cc_apt_configure.add_sources(cfg, params) + cc_apt_configure.add_apt_sources(cfg, params) self.assertTrue(os.path.isfile(filename)) @@ -200,7 +200,7 @@ class TestAptSourceConfig(TestCase): Test Autoreplacement of MIRROR and RELEASE in source specs """ params = self._get_default_params() - cc_apt_configure.add_sources(cfg, params) + cc_apt_configure.add_apt_sources(cfg, params) self.assertTrue(os.path.isfile(filename)) @@ -283,7 +283,7 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp', return_value=('fakekey 1234', '')) as mockobj: - cc_apt_configure.add_sources(cfg, params) + cc_apt_configure.add_apt_sources(cfg, params) # check if it added the right ammount of keys calls = [] @@ -372,7 +372,7 @@ class TestAptSourceConfig(TestCase): params = self._get_default_params() with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 4321') @@ -419,7 +419,7 @@ class TestAptSourceConfig(TestCase): 'filename': self.aptlistfile} with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) mockobj.assert_called_once_with(('apt-key', 'add', '-'), 'fakekey 4242') @@ -437,7 +437,7 @@ class TestAptSourceConfig(TestCase): with mock.patch.object(util, 'subp', return_value=('fakekey 1212', '')) as mockobj: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) mockobj.assert_called_with(('apt-key', 'add', '-'), 'fakekey 1212') @@ -447,7 +447,7 @@ class TestAptSourceConfig(TestCase): def apt_src_keyid_real(self, cfg, expectedkey): """apt_src_keyid_real Test specification of a keyid without source including - up to addition of the key (add_key_raw mocked to keep the + up to addition of the key (add_apt_key_raw mocked to keep the environment as is) """ params = self._get_default_params() @@ -466,10 +466,10 @@ class TestAptSourceConfig(TestCase): # as fallback add the known key as a working recv would util.subp(("gpg", "--import", "-"), EXPECTEDKEY) - with mock.patch.object(cc_apt_configure, 'add_key_raw') as mockkey: + with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', side_effect=fake_gpg_recv_key) as mockrecv: - cc_apt_configure.add_sources([cfg], params) + cc_apt_configure.add_apt_sources([cfg], params) # since we might mock the recv path ensure it is called right mockrecv.assert_called_with(cfg['keyid'], @@ -518,7 +518,8 @@ class TestAptSourceConfig(TestCase): matcher = re.compile(r'^[\w-]+:\w').search with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg], params, aa_repo_match=matcher) + cc_apt_configure.add_apt_sources([cfg], params, + aa_repo_match=matcher) mockobj.assert_called_once_with(['add-apt-repository', 'ppa:smoser/cloud-init-test']) @@ -541,8 +542,8 @@ class TestAptSourceConfig(TestCase): matcher = re.compile(r'^[\w-]+:\w').search with mock.patch.object(util, 'subp') as mockobj: - cc_apt_configure.add_sources([cfg1, cfg2, cfg3], params, - aa_repo_match=matcher) + cc_apt_configure.add_apt_sources([cfg1, cfg2, cfg3], params, + aa_repo_match=matcher) calls = [call(['add-apt-repository', 'ppa:smoser/cloud-init-test']), call(['add-apt-repository', 'ppa:smoser/cloud-init-test2']), call(['add-apt-repository', 'ppa:smoser/cloud-init-test3'])] -- cgit v1.2.3 From 71ea8d200da164df6f5e28d28aea424b57fd8f1f Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 15:18:59 +0200 Subject: rename fucntion to mirrorurl_to_apt_fileprefix to reflect what it actually does --- cloudinit/config/cc_apt_configure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index 91a117f5..be6324a2 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -93,7 +93,7 @@ def handle(name, cfg, cloud, log, _args): util.logexc(log, "Failed to run debconf-set-selections") -def mirror2lists_fileprefix(mirror): +def mirrorurl_to_apt_fileprefix(mirror): string = mirror # take off http:// or ftp:// if string.endswith("/"): @@ -110,8 +110,8 @@ def rename_apt_lists(old_mirrors, new_mirrors, lists_d="/var/lib/apt/lists"): nmirror = new_mirrors.get(name) if not nmirror: continue - oprefix = os.path.join(lists_d, mirror2lists_fileprefix(omirror)) - nprefix = os.path.join(lists_d, mirror2lists_fileprefix(nmirror)) + oprefix = os.path.join(lists_d, mirrorurl_to_apt_fileprefix(omirror)) + nprefix = os.path.join(lists_d, mirrorurl_to_apt_fileprefix(nmirror)) if oprefix == nprefix: continue olen = len(oprefix) -- cgit v1.2.3 From bb0bf81cb373fde0c59464127633ebc7527f2be5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Mon, 6 Jun 2016 15:48:25 +0200 Subject: capture output of gpg calls to avoid messing up stdout/stderr --- cloudinit/util.py | 2 +- tests/unittests/test_handler/test_handler_apt_source.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/util.py b/cloudinit/util.py index d3b14f72..6d16532d 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2254,7 +2254,7 @@ def gpg_recv_key(key, keyserver): def gpg_delete_key(key): """Delete the specified key from the local gpg ring""" - subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=False) + subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) def getkeybyid(keyid, keyserver): diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 9aa6ff71..ea8aa17a 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -469,7 +469,8 @@ class TestAptSourceConfig(TestCase): testsock.close() except socket.error: # as fallback add the known key as a working recv would - util.subp(("gpg", "--import", "-"), EXPECTEDKEY) + util.subp(("gpg", "--import", "-"), EXPECTEDKEY, + capture=True) with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: with mock.patch.object(util, 'gpg_recv_key', -- cgit v1.2.3 From b89ac6b7caeeade5ad21137773ac4496cdaea2c5 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 09:18:35 +0200 Subject: move gpg functions into gpg.py This helps for cleaner code structuring. ALong that makeing sure all these functions have a gpg_prefix. --- cloudinit/config/cc_apt_configure.py | 3 +- cloudinit/gpg.py | 64 ++++++++++++++++++++++ cloudinit/util.py | 38 ------------- .../test_handler/test_handler_apt_source.py | 4 +- 4 files changed, 68 insertions(+), 41 deletions(-) create mode 100644 cloudinit/gpg.py (limited to 'cloudinit') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index be6324a2..ba080930 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -24,6 +24,7 @@ import re from cloudinit import templater from cloudinit import util +from cloudinit import gpg distros = ['ubuntu', 'debian'] @@ -167,7 +168,7 @@ def add_apt_key(ent): keyserver = "keyserver.ubuntu.com" if 'keyserver' in ent: keyserver = ent['keyserver'] - ent['key'] = util.getkeybyid(ent['keyid'], keyserver) + ent['key'] = gpg.gpg_getkeybyid(ent['keyid'], keyserver) if 'key' in ent: add_apt_key_raw(ent['key']) diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py new file mode 100644 index 00000000..620dfb19 --- /dev/null +++ b/cloudinit/gpg.py @@ -0,0 +1,64 @@ +"""gpg.py - Collection of gpg key related functions""" +# vi: ts=4 expandtab +# +# Copyright (C) 2016 Canonical Ltd. +# +# Author: Scott Moser +# Author: Juerg Haefliger +# Author: Joshua Harlow +# Author: Christian Ehrhardt +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# +# 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from cloudinit import util +from cloudinit import log as logging + +LOG = logging.getLogger(__name__) + + +def gpg_export_armour(key): + """Export gpg key, armoured key gets returned""" + (armour, _) = util.subp(["gpg", "--export", "--armour", key], capture=True) + return armour + + +def gpg_recv_key(key, keyserver): + """Receive gpg key from the specified keyserver""" + try: + util.subp(["gpg", "--keyserver", keyserver, "--recv", key], + capture=True) + except util.ProcessExecutionError as error: + raise ValueError('Failed to import key %s from server %s - error %s' % + (key, keyserver, error)) + + +def gpg_delete_key(key): + """Delete the specified key from the local gpg ring""" + util.subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) + + +def gpg_getkeybyid(keyid, keyserver): + """get gpg keyid from keyserver""" + armour = gpg_export_armour(keyid) + if not armour: + try: + gpg_recv_key(keyid, keyserver=keyserver) + except ValueError: + LOG.exception('Failed to obtain gpg key %s', keyid) + raise + + armour = gpg_export_armour(keyid) + # delete just imported key to leave environment as it was before + gpg_delete_key(keyid) + + return armour.rstrip('\n') diff --git a/cloudinit/util.py b/cloudinit/util.py index 6d16532d..d6b80dbe 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -2234,41 +2234,3 @@ def message_from_string(string): if sys.version_info[:2] < (2, 7): return email.message_from_file(six.StringIO(string)) return email.message_from_string(string) - - -def gpg_export_armour(key): - """Export gpg key, armoured key gets returned""" - (armour, _) = subp(["gpg", "--export", "--armour", key], capture=True) - return armour - - -def gpg_recv_key(key, keyserver): - """Receive gpg key from the specified keyserver""" - try: - subp(["gpg", "--keyserver", keyserver, "--recv", key], - capture=True) - except ProcessExecutionError as error: - raise ValueError('Failed to import key %s from server %s - error %s' % - (key, keyserver, error)) - - -def gpg_delete_key(key): - """Delete the specified key from the local gpg ring""" - subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) - - -def getkeybyid(keyid, keyserver): - """get gpg keyid from keyserver""" - armour = gpg_export_armour(keyid) - if not armour: - try: - gpg_recv_key(keyid, keyserver=keyserver) - except ValueError: - LOG.exception('Failed to obtain gpg key %s', keyid) - raise - - armour = gpg_export_armour(keyid) - # delete just imported key to leave environment as it was before - gpg_delete_key(keyid) - - return armour.rstrip('\n') diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 3f87fad3..b09dd8ab 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -14,6 +14,7 @@ from mock import call from cloudinit.config import cc_apt_configure from cloudinit import util +from cloudinit import gpg from ..helpers import TestCase @@ -58,7 +59,6 @@ class TestAptSourceConfig(TestCase): # mock fallback filename into writable tmp dir self.fallbackfn = os.path.join(self.tmp, "etc/apt/sources.list.d/", "cloud_config_sources.list") - self.orig_gpg_recv_key = util.gpg_recv_key patcher = mock.patch("cloudinit.config.cc_apt_configure.get_release") get_rel = patcher.start() @@ -407,7 +407,7 @@ class TestAptSourceConfig(TestCase): params = self._get_default_params() with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(util, 'getkeybyid', + with mock.patch.object(gpg, 'gpg_getkeybyid', return_value=expectedkey) as mockgetkey: cc_apt_configure.add_apt_sources([cfg], params) -- cgit v1.2.3 From e6cfca0c898f0482f37216cf58e61ff1581aaaf6 Mon Sep 17 00:00:00 2001 From: Christian Ehrhardt Date: Thu, 9 Jun 2016 09:35:09 +0200 Subject: improve error handling and reporting in gpg functions --- cloudinit/gpg.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py index 620dfb19..baa8b534 100644 --- a/cloudinit/gpg.py +++ b/cloudinit/gpg.py @@ -28,23 +28,35 @@ LOG = logging.getLogger(__name__) def gpg_export_armour(key): """Export gpg key, armoured key gets returned""" - (armour, _) = util.subp(["gpg", "--export", "--armour", key], capture=True) + try: + (armour, _) = util.subp(["gpg", "--export", "--armour", key], + capture=True) + except util.ProcessExecutionError as error: + # debug, since it happens for any key not on the system initially + LOG.debug('Failed to export armoured key "%s": %s', key, error) + armour = None return armour def gpg_recv_key(key, keyserver): """Receive gpg key from the specified keyserver""" + LOG.debug('Receive gpg key "%s"', key) try: util.subp(["gpg", "--keyserver", keyserver, "--recv", key], capture=True) except util.ProcessExecutionError as error: - raise ValueError('Failed to import key %s from server %s - error %s' % + raise ValueError(('Failed to import key "%s" ' + 'from server "%s" - error %s') % (key, keyserver, error)) def gpg_delete_key(key): """Delete the specified key from the local gpg ring""" - util.subp(["gpg", "--batch", "--yes", "--delete-keys", key], capture=True) + try: + util.subp(["gpg", "--batch", "--yes", "--delete-keys", key], + capture=True) + except util.ProcessExecutionError as error: + LOG.warn('Failed delete key "%s": %s', key, error) def gpg_getkeybyid(keyid, keyserver): @@ -53,12 +65,12 @@ def gpg_getkeybyid(keyid, keyserver): if not armour: try: gpg_recv_key(keyid, keyserver=keyserver) + armour = gpg_export_armour(keyid) except ValueError: LOG.exception('Failed to obtain gpg key %s', keyid) raise - - armour = gpg_export_armour(keyid) - # delete just imported key to leave environment as it was before - gpg_delete_key(keyid) + finally: + # delete just imported key to leave environment as it was before + gpg_delete_key(keyid) return armour.rstrip('\n') -- cgit v1.2.3 From 7e527b1b2f3fda558fb0f3a6958c42dde4716079 Mon Sep 17 00:00:00 2001 From: Scott Moser Date: Fri, 10 Jun 2016 13:22:59 -0400 Subject: minor changes prior to merge a.) remove 'gpg_' from function names in new gpg module. b.) use --recv-keys rather than --recv --recv-keys is more obvious and works back to precise at least. c.) do not trim trailing '\n' from a armour'd key. --- cloudinit/config/cc_apt_configure.py | 4 ++-- cloudinit/gpg.py | 24 ++++++++++------------ .../test_handler/test_handler_apt_source.py | 4 ++-- 3 files changed, 15 insertions(+), 17 deletions(-) (limited to 'cloudinit') diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py index ba080930..96c4a43d 100644 --- a/cloudinit/config/cc_apt_configure.py +++ b/cloudinit/config/cc_apt_configure.py @@ -22,9 +22,9 @@ import glob import os import re +from cloudinit import gpg from cloudinit import templater from cloudinit import util -from cloudinit import gpg distros = ['ubuntu', 'debian'] @@ -168,7 +168,7 @@ def add_apt_key(ent): keyserver = "keyserver.ubuntu.com" if 'keyserver' in ent: keyserver = ent['keyserver'] - ent['key'] = gpg.gpg_getkeybyid(ent['keyid'], keyserver) + ent['key'] = gpg.get_key_by_id(ent['keyid'], keyserver) if 'key' in ent: add_apt_key_raw(ent['key']) diff --git a/cloudinit/gpg.py b/cloudinit/gpg.py index baa8b534..6a76d785 100644 --- a/cloudinit/gpg.py +++ b/cloudinit/gpg.py @@ -4,8 +4,6 @@ # Copyright (C) 2016 Canonical Ltd. # # Author: Scott Moser -# Author: Juerg Haefliger -# Author: Joshua Harlow # Author: Christian Ehrhardt # # This program is free software: you can redistribute it and/or modify @@ -20,13 +18,13 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from cloudinit import util from cloudinit import log as logging +from cloudinit import util LOG = logging.getLogger(__name__) -def gpg_export_armour(key): +def export_armour(key): """Export gpg key, armoured key gets returned""" try: (armour, _) = util.subp(["gpg", "--export", "--armour", key], @@ -38,11 +36,11 @@ def gpg_export_armour(key): return armour -def gpg_recv_key(key, keyserver): +def receive_key(key, keyserver): """Receive gpg key from the specified keyserver""" LOG.debug('Receive gpg key "%s"', key) try: - util.subp(["gpg", "--keyserver", keyserver, "--recv", key], + util.subp(["gpg", "--keyserver", keyserver, "--recv-keys", key], capture=True) except util.ProcessExecutionError as error: raise ValueError(('Failed to import key "%s" ' @@ -50,7 +48,7 @@ def gpg_recv_key(key, keyserver): (key, keyserver, error)) -def gpg_delete_key(key): +def delete_key(key): """Delete the specified key from the local gpg ring""" try: util.subp(["gpg", "--batch", "--yes", "--delete-keys", key], @@ -59,18 +57,18 @@ def gpg_delete_key(key): LOG.warn('Failed delete key "%s": %s', key, error) -def gpg_getkeybyid(keyid, keyserver): +def get_key_by_id(keyid, keyserver="keyserver.ubuntu.com"): """get gpg keyid from keyserver""" - armour = gpg_export_armour(keyid) + armour = export_armour(keyid) if not armour: try: - gpg_recv_key(keyid, keyserver=keyserver) - armour = gpg_export_armour(keyid) + receive_key(keyid, keyserver=keyserver) + armour = export_armour(keyid) except ValueError: LOG.exception('Failed to obtain gpg key %s', keyid) raise finally: # delete just imported key to leave environment as it was before - gpg_delete_key(keyid) + delete_key(keyid) - return armour.rstrip('\n') + return armour diff --git a/tests/unittests/test_handler/test_handler_apt_source.py b/tests/unittests/test_handler/test_handler_apt_source.py index 5b52f709..99a4d860 100644 --- a/tests/unittests/test_handler/test_handler_apt_source.py +++ b/tests/unittests/test_handler/test_handler_apt_source.py @@ -13,8 +13,8 @@ except ImportError: from mock import call from cloudinit.config import cc_apt_configure -from cloudinit import util from cloudinit import gpg +from cloudinit import util from ..helpers import TestCase @@ -405,7 +405,7 @@ class TestAptSourceConfig(TestCase): params = self._get_default_params() with mock.patch.object(cc_apt_configure, 'add_apt_key_raw') as mockkey: - with mock.patch.object(gpg, 'gpg_getkeybyid', + with mock.patch.object(gpg, 'get_key_by_id', return_value=expectedkey) as mockgetkey: cc_apt_configure.add_apt_sources([cfg], params) -- cgit v1.2.3