Merge lp:~stub/charm-helpers/fix-configure_sources into lp:charm-helpers

Proposed by Stuart Bishop
Status: Merged
Merged at revision: 196
Proposed branch: lp:~stub/charm-helpers/fix-configure_sources
Merge into: lp:charm-helpers
Diff against target: 118 lines (+64/-21)
2 files modified
charmhelpers/fetch/__init__.py (+17/-3)
tests/fetch/test_fetch.py (+47/-18)
To merge this branch: bzr merge lp:~stub/charm-helpers/fix-configure_sources
Reviewer Review Type Date Requested Status
David Britton (community) Approve
Review via email: mp+226684@code.launchpad.net

Description of the change

fetch.add_source() is blindly retrieving a key by GPG key id from a remote keyserver, which is in most cases insecure.

This branch addresses this security problem by allowing add_source() to accept a full GPG key, rather than just a GPG key id. This is Bug #1341527.

I've also fixed Bug #1269718 while I'm here. I elected to raise an exception rather than log an error and continue, as other parts of the code where already raising exceptions when invalid sources are requested.

To post a comment you must log in.
Revision history for this message
Chris Glass (tribaal) wrote :

That is a great addition to the mechanism IMO.

Could you please add a short docstring to the function explaining what the key parameter should be like? Example:

"""This function adds an entry to the sources.list file.

@param source: The archive's URL. Example: "http://archive.ubuntu.com"
@param key: The GPG key to verify the archive packages with. Can be either a
    key ID or a full public key block. If only the key ID is specified the
    key will be fetched from the network via HKP."""

Revision history for this message
David Britton (dpb) wrote :

+1, tested and works great. Thanks for adding this.

Agreed with @tribaal on the docstring. Now that the functionality is expanding, I think it sort of needs the docstring, and it's a good chance to add it.

Marking as approved since this is a minor non-functional change suggested.

review: Approve
165. By Stuart Bishop

Add docstrings

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'charmhelpers/fetch/__init__.py'
--- charmhelpers/fetch/__init__.py 2014-06-27 08:11:50 +0000
+++ charmhelpers/fetch/__init__.py 2014-07-14 14:54:10 +0000
@@ -1,4 +1,5 @@
1import importlib1import importlib
2from tempfile import NamedTemporaryFile
2import time3import time
3from yaml import safe_load4from yaml import safe_load
4from charmhelpers.core.host import (5from charmhelpers.core.host import (
@@ -225,10 +226,23 @@
225 release = lsb_release()['DISTRIB_CODENAME']226 release = lsb_release()['DISTRIB_CODENAME']
226 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:227 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
227 apt.write(PROPOSED_POCKET.format(release))228 apt.write(PROPOSED_POCKET.format(release))
229 else:
230 raise SourceConfigError("Unknown source: {!r}".format(source))
231
228 if key:232 if key:
229 subprocess.check_call(['apt-key', 'adv', '--keyserver',233 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
230 'hkp://keyserver.ubuntu.com:80', '--recv',234 with NamedTemporaryFile() as key_file:
231 key])235 key_file.write(key)
236 key_file.flush()
237 key_file.seek(0)
238 subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
239 else:
240 # Note that hkp: is in no way a secure protocol. Using a
241 # GPG key id is pointless from a security POV unless you
242 # absolutely trust your network and DNS.
243 subprocess.check_call(['apt-key', 'adv', '--keyserver',
244 'hkp://keyserver.ubuntu.com:80', '--recv',
245 key])
232246
233247
234def configure_sources(update=False,248def configure_sources(update=False,
235249
=== modified file 'tests/fetch/test_fetch.py'
--- tests/fetch/test_fetch.py 2014-06-05 10:47:46 +0000
+++ tests/fetch/test_fetch.py 2014-07-14 14:54:10 +0000
@@ -1,3 +1,4 @@
1from cStringIO import StringIO
1import subprocess2import subprocess
23
3from tests.helpers import patch_open4from tests.helpers import patch_open
@@ -177,26 +178,54 @@
177 mock_file.write.assert_called_with(result)178 mock_file.write.assert_called_with(result)
178179
179 @patch('subprocess.check_call')180 @patch('subprocess.check_call')
181 def test_add_source_http_and_key_id(self, check_call):
182 source = "http://archive.ubuntu.com/ubuntu raring-backports main"
183 key_id = "akey"
184 fetch.add_source(source=source, key=key_id)
185 check_call.assert_has_calls([
186 call(['add-apt-repository', '--yes', source]),
187 call(['apt-key', 'adv', '--keyserver',
188 'hkp://keyserver.ubuntu.com:80', '--recv', key_id])
189 ])
190
191 @patch('subprocess.check_call')
192 def test_add_source_https_and_key_id(self, check_call):
193 source = "https://USER:PASS@private-ppa.launchpad.net/project/awesome"
194 key_id = "GPGPGP"
195 fetch.add_source(source=source, key=key_id)
196 check_call.assert_has_calls([
197 call(['add-apt-repository', '--yes', source]),
198 call(['apt-key', 'adv', '--keyserver',
199 'hkp://keyserver.ubuntu.com:80', '--recv', key_id])
200 ])
201
202 @patch('subprocess.check_call')
180 def test_add_source_http_and_key(self, check_call):203 def test_add_source_http_and_key(self, check_call):
181 source = "http://archive.ubuntu.com/ubuntu raring-backports main"204 source = "http://archive.ubuntu.com/ubuntu raring-backports main"
182 key = "akey"205 key = '''
183 fetch.add_source(source=source, key=key)206 -----BEGIN PGP PUBLIC KEY BLOCK-----
184 check_call.assert_has_calls([207 [...]
185 call(['add-apt-repository', '--yes', source]),208 -----END PGP PUBLIC KEY BLOCK-----
186 call(['apt-key', 'adv', '--keyserver', 'hkp://keyserver.ubuntu.com:80',209 '''
187 '--recv', key])210
188 ])211 received_args = []
189212 received_key = StringIO()
190 @patch('subprocess.check_call')213 def _check_call(arg, stdin=None):
191 def test_add_source_https_and_key(self, check_call):214 '''side_effect to store the stdin passed to check_call process.'''
192 source = "http://USER:PASS@private-ppa.launchpad.net/project/awesome"215 if stdin is not None:
193 key = "GPGPGP"216 received_args.extend(arg)
194 fetch.add_source(source=source, key=key)217 received_key.write(stdin.read())
195 check_call.assert_has_calls([218
196 call(['add-apt-repository', '--yes', source]),219 with patch('subprocess.check_call',
197 call(['apt-key', 'adv', '--keyserver', 'hkp://keyserver.ubuntu.com:80',220 side_effect=_check_call) as check_call:
198 '--recv', key])221 fetch.add_source(source=source, key=key)
199 ])222 check_call.assert_any_call(['add-apt-repository', '--yes', source])
223 self.assertEqual(['apt-key', 'add', '-'], received_args)
224 self.assertEqual(key, received_key.getvalue())
225
226 def test_add_unparsable_source(self):
227 source = "propsed" # Minor typo
228 self.assertRaises(fetch.SourceConfigError, fetch.add_source, source)
200229
201 @patch.object(fetch, 'config')230 @patch.object(fetch, 'config')
202 @patch.object(fetch, 'add_source')231 @patch.object(fetch, 'add_source')

Subscribers

People subscribed via source and target branches