Package: juju Version: 0.5.1+bzr563-0juju2~quantal1 Severity: grave Tags: security patch upstream Justification: user security hole
This problem with juju has been fixed in upstream trunk and so can be considered "disclosed". When using juju with the built in "charm store" at store.juju.ubuntu.com, the SSL certificate is not verified. This could lead to a man in the middle attack where an attacker could have trojaned "charms" installed instead of the official charms. -- System Information: Debian Release: wheezy/sid APT prefers quantal-updates APT policy: (500, 'quantal-updates'), (500, 'quantal-security'), (500, 'quantal'), (400, 'precise-proposed') Architecture: amd64 (x86_64) Foreign Architectures: i386 Kernel: Linux 3.5.0-10-generic (SMP w/2 CPU cores) Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8) Shell: /bin/sh linked to /bin/dash Versions of packages juju depends on: ii openssh-client 1:6.0p1-2ubuntu1 ii python 2.7.3-0ubuntu5 ii python-oauth 1.0.1-3build1 ii python-twisted 12.0.0-1ubuntu1 ii python-txaws 0.2.3-1ubuntu1 ii python-txzookeeper 0.9.5-1 ii python-yaml 3.10-4 ii python2.7 2.7.3-0ubuntu4 ii tmux 1.6-2 Versions of packages juju recommends: ii byobu 5.21-0ubuntu1 ii python-pydot 1.0.2-1 Versions of packages juju suggests: ii apt-cacher-ng 0.7.7-1ubuntu1 ii libvirt-bin 0.9.13-0ubuntu7 ii lxc 0.8.0~rc1-4ubuntu24 ii zookeeper 3.3.6+dfsg-0ubuntu1 -- no debconf information
Origin: http://bazaar.launchpad.net/~juju/juju/trunk/revision/565 Bug: http://pad.lv/992447 === modified file 'juju/charm/repository.py' --- a/juju/charm/repository.py 2012-05-03 18:42:09 +0000 +++ b/juju/charm/repository.py 2012-08-23 23:57:09 +0000 @@ -3,11 +3,13 @@ import os import tempfile import urllib +import urlparse import yaml from twisted.internet.defer import fail, inlineCallbacks, returnValue, succeed from twisted.web.client import downloadPage, getPage from twisted.web.error import Error +from txaws.client.ssl import VerifyingContextFactory from juju.charm.provider import get_charm_from_path from juju.charm.url import CharmURL @@ -126,7 +128,8 @@ url = "%s/charm-info?charms=%s" % ( self.url_base, urllib.quote(charm_id)) try: - all_info = json.loads((yield getPage(url))) + host = urlparse.urlparse(url).hostname + all_info = json.loads((yield getPage(url, contextFactory=VerifyingContextFactory(host)))) charm_info = all_info[charm_id] for warning in charm_info.get("warnings", []): log.warning("%s: %s", charm_id, warning) @@ -147,8 +150,9 @@ delete=False) f.close() downloading_path = f.name + host = urlparse.urlparse(url).hostname try: - yield downloadPage(url, downloading_path) + yield downloadPage(url, downloading_path, contextFactory=VerifyingContextFactory(host)) except Error: raise CharmNotFound(self.url_base, charm_url) os.rename(downloading_path, cache_path) === modified file 'juju/charm/tests/test_repository.py' --- a/juju/charm/tests/test_repository.py 2012-07-01 22:20:22 +0000 +++ b/juju/charm/tests/test_repository.py 2012-08-06 20:06:37 +0000 @@ -5,6 +5,8 @@ from twisted.internet.defer import fail, inlineCallbacks, succeed from twisted.web.error import Error +from txaws.client.ssl import VerifyingContextFactory + from juju.charm.directory import CharmDirectory from juju.charm.errors import CharmNotFound, CharmURLError, RepositoryNotFound @@ -16,7 +18,7 @@ from juju.lib import under from juju.charm import tests -from juju.lib.mocker import ANY +from juju.lib.mocker import ANY, MATCH from juju.lib.testing import TestCase @@ -280,15 +282,19 @@ return json.dumps({url_str: info}) def mock_charm_info(self, url, result): - self.getPage(url) + def match_context(value): + return isinstance(value, VerifyingContextFactory) + self.getPage(url, contextFactory=MATCH(match_context)) self.mocker.result(result) def mock_download(self, url, error=None): - self.downloadPage(url, ANY) + def match_context(value): + return isinstance(value, VerifyingContextFactory) + self.downloadPage(url, ANY, contextFactory=MATCH(match_context)) if error: return self.mocker.result(fail(error)) - def download(_, path): + def download(_, path, contextFactory): self.assertTrue(path.startswith(self.download_path)) with open(path, "wb") as f: f.write(self.bundle_data) === modified file 'juju/control/tests/test_upgrade_charm.py' --- a/juju/control/tests/test_upgrade_charm.py 2012-05-04 22:43:40 +0000 +++ b/juju/control/tests/test_upgrade_charm.py 2012-08-06 19:29:30 +0000 @@ -12,6 +12,7 @@ from juju.errors import FileNotFound from juju.environment.environment import Environment from juju.unit.workflow import UnitWorkflowState +from juju.lib.mocker import ANY from .common import MachineControlToolTest @@ -481,7 +482,8 @@ self.setup_exit(0) getPage = self.mocker.replace("twisted.web.client.getPage") getPage( - CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql") + CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql", + contextFactory=ANY) self.mocker.result(succeed(json.dumps( {"cs:series/mysql": {"revision": 1, "sha256": "whatever"}}))) self.mocker.replay() @@ -502,7 +504,8 @@ self.setup_exit(0) getPage = self.mocker.replace("twisted.web.client.getPage") - getPage(CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql") + getPage(CS_STORE_URL + "/charm-info?charms=cs%3Aseries/mysql", + contextFactory=ANY) self.mocker.result(succeed(json.dumps( {"cs:series/mysql": {"revision": 1, "sha256": "whatever"}})))