Merge lp:~vila/bzr/920455-ssl-defaults into lp:bzr/2.5

Proposed by Vincent Ladeuil
Status: Superseded
Proposed branch: lp:~vila/bzr/920455-ssl-defaults
Merge into: lp:bzr/2.5
Diff against target: 173 lines (+49/-36) (has conflicts)
3 files modified
bzrlib/tests/test_https_urllib.py (+4/-20)
bzrlib/transport/http/_urllib2_wrappers.py (+38/-16)
doc/en/release-notes/bzr-2.5.txt (+7/-0)
Text conflict in doc/en/release-notes/bzr-2.5.txt
To merge this branch: bzr merge lp:~vila/bzr/920455-ssl-defaults
Reviewer Review Type Date Requested Status
Jelmer Vernooij (community) Approve
Gordon Tyler Pending
Martin Packman Pending
Review via email: mp+90123@code.launchpad.net

This proposal has been superseded by a proposal from 2012-01-30.

Commit message

Provide platform specific default values for ssl.ca_certs

Description of the change

This add default values for the ssl.ca_certs config option pointing to the
most probable place where the certificates are place for supported
platforms.

Feedback needed from windows and osx packagers unless we rely on them to fix
it when building 2.5.0...

I've changed the tests so at least test_default_exists
bzrlib/tests/test_https_urllib.py fails if the default value is wrong.

I've also change the option to fail if a non-existing path is used and
changed the code to check the config option only if ssl.cert_reqs is not none.

With these changes, either:

- the certificates are there and they will be checked by default

- they are not but ssl.cert_reqs is none, no need to bother the user
  especially since that is the obvious workaround for now if something goes
  wrong with the verification,

- an error is raised if the user ask for verification but we can't find the
  CAs.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

@gz, Gordon: Feedback on where you expect to install the bundled ca certs expected ;)

Revision history for this message
Martin Packman (gz) wrote :

So, on windows we're mostly stuffed because ideally we'd use the Internet Explorer certificate store, but that's a completely different interface to openssl and the cert dir used on nix systems.

The best we can do is bundle curl-ca-bundle.crt with the all-in-one installer like we did previously:

<http://wiki.bazaar.canonical.com/BzrWin32Installer#bzr-dependencies>

Then accept the fact branching over https will be broken for everyone else using a python installer or running setup.py themselves.

This is why I was picky about the error message given for the original change... unfortunately it's now just a ValueError and traceback due to the default config value being bad. This branch changes one of those but not the other.

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

32 def test_specified_doesnt_exist(self):
33 path = os.path.join(self.test_dir, "nonexisting.pem")
34 stack = self.get_stack("ssl.ca_certs = %s\n" % path)
35 - self.warnings = []
36 - def warning(*args):
37 - self.warnings.append(args[0] % args[1:])
38 - self.overrideAttr(trace, 'warning', warning)
39 - self.assertEquals(_urllib2_wrappers.DEFAULT_CA_PATH,
40 - stack.get('ssl.ca_certs'))
41 - self.assertLength(1, self.warnings)
42 - self.assertContainsRe(self.warnings[0],
43 - "is not valid for \"ssl.ca_certs\"")
44 + self.assertRaises(ConfigOptionValueError, stack.get, 'ssl.ca_certs')
Should it really be an error if the ssl.ca_certs path doesn't exist? What if e.g. "ssl.ca_reqs = optional", it doesn't seem like it should be a problem if the ca_certs are missing.

default_ca_cert() seems to return "/etc/ssl/..." on Windows. This seems wrong in any case. I realize you've asked for feedback from the packagers, but I think we should raise ValueError or return None for now at least.

review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

> This is why I was picky about the error message given for the original
> change... unfortunately it's now just a ValueError and traceback due to the
> default config value being bad. This branch changes one of those but not the
> other.

No, please do try again (with this proposal):

  bzr config --remove launchpad_username
  bzr config ssl.ca_certs=/I-dont-exist

vila:~/src/bzr/bugs/920455-ssl-defaults :) $ ./bzr info lp:bzr
bzr: ERROR: Bad value "/I-dont-exist" for option "ssl.ca_certs".

So you get a proper error message that we may want to clarify but you don't
get a traceback.

You got a traceback prior to this change because default_ca_certs() was
raising it which is why I changed the implementation as ValueError is caught
for the *_from_store() functions, not the _default() functions.

I could change the ConfigValueError message to include 'See bzr help
ssl.ca_certs' but we froze the strings for 2.5 so I'd rather do that for
trunk.

I'll file bugs for windows and osx installers to make sure a bundle is
included.

Revision history for this message
Vincent Ladeuil (vila) wrote :

    > Review: Approve
    > 32 def test_specified_doesnt_exist(self):
    > 33 path = os.path.join(self.test_dir, "nonexisting.pem")
    > 34 stack = self.get_stack("ssl.ca_certs = %s\n" % path)
    > 35 - self.warnings = []
    > 36 - def warning(*args):
    > 37 - self.warnings.append(args[0] % args[1:])
    > 38 - self.overrideAttr(trace, 'warning', warning)
    > 39 - self.assertEquals(_urllib2_wrappers.DEFAULT_CA_PATH,
    > 40 - stack.get('ssl.ca_certs'))
    > 41 - self.assertLength(1, self.warnings)
    > 42 - self.assertContainsRe(self.warnings[0],
    > 43 - "is not valid for \"ssl.ca_certs\"")
    > 44 + self.assertRaises(ConfigOptionValueError, stack.get, 'ssl.ca_certs')

    > Should it really be an error if the ssl.ca_certs path doesn't
    > exist? What if e.g. "ssl.ca_reqs = optional", it doesn't seem like
    > it should be a problem if the ca_certs are missing.

Indeed ! That's why I also change the code to query the option only if
required ;)

    > default_ca_cert() seems to return "/etc/ssl/..." on Windows. This
    > seems wrong in any case.

Yes.

    > I realize you've asked for feedback from the packagers, but I
    > think we should raise ValueError or return None for now at least.

That's what will happen if the option is needed, see
ca_certs_from_store() and invalid='error' ! ;)

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

On 01/30/2012 03:19 PM, Vincent Ladeuil wrote:
> > Review: Approve
> > 32 def test_specified_doesnt_exist(self):
> > 33 path = os.path.join(self.test_dir, "nonexisting.pem")
> > 34 stack = self.get_stack("ssl.ca_certs = %s\n" % path)
> > 35 - self.warnings = []
> > 36 - def warning(*args):
> > 37 - self.warnings.append(args[0] % args[1:])
> > 38 - self.overrideAttr(trace, 'warning', warning)
> > 39 - self.assertEquals(_urllib2_wrappers.DEFAULT_CA_PATH,
> > 40 - stack.get('ssl.ca_certs'))
> > 41 - self.assertLength(1, self.warnings)
> > 42 - self.assertContainsRe(self.warnings[0],
> > 43 - "is not valid for \"ssl.ca_certs\"")
> > 44 + self.assertRaises(ConfigOptionValueError, stack.get, 'ssl.ca_certs')
>
> > Should it really be an error if the ssl.ca_certs path doesn't
> > exist? What if e.g. "ssl.ca_reqs = optional", it doesn't seem like
> > it should be a problem if the ca_certs are missing.
>
> Indeed ! That's why I also change the code to query the option only if
> required ;)
You're (correctly) trying to retrieve the ca certs too if
ssl.ca_reqs=optional, but if the ca certs path doesn't exist, that
becomes a terminal error. It shouldn't be if ssl.ca_reqs=optional, only
if ssl.ca_reqs=required.
>
>
> > I realize you've asked for feedback from the packagers, but I
> > think we should raise ValueError or return None for now at least.
>
> That's what will happen if the option is needed, see
> ca_certs_from_store() and invalid='error' ! ;)
I don't see how that's the case. We'll be trying to retrieve the ca
certs in that case and it'll cause ConfigOptionValueError to be raised
(and bzr to be aborted), right?

Cheers,

Jelmer

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bzrlib/tests/test_https_urllib.py'
2--- bzrlib/tests/test_https_urllib.py 2012-01-19 15:27:47 +0000
3+++ bzrlib/tests/test_https_urllib.py 2012-01-30 14:24:24 +0000
4@@ -1,4 +1,4 @@
5-# Copyright (C) 2011 Canonical Ltd
6+# Copyright (C) 2011,2012 Canonical Ltd
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10@@ -41,18 +41,10 @@
11 def get_stack(self, content):
12 return config.MemoryStack(content.encode('utf-8'))
13
14- def test_default_raises_value_error(self):
15- stack = self.get_stack("")
16- self.overrideAttr(_urllib2_wrappers, "DEFAULT_CA_PATH",
17- "/i-do-not-exist")
18- self.assertRaises(ValueError, stack.get, 'ssl.ca_certs')
19-
20 def test_default_exists(self):
21- self.build_tree(['cacerts.pem'])
22+ """Check that the default we provide exists for the tested platform."""
23 stack = self.get_stack("")
24- path = os.path.join(self.test_dir, "cacerts.pem")
25- self.overrideAttr(_urllib2_wrappers, "DEFAULT_CA_PATH", path)
26- self.assertEquals(path, stack.get('ssl.ca_certs'))
27+ self.assertPathExists(stack.get('ssl.ca_certs'))
28
29 def test_specified(self):
30 self.build_tree(['cacerts.pem'])
31@@ -63,15 +55,7 @@
32 def test_specified_doesnt_exist(self):
33 path = os.path.join(self.test_dir, "nonexisting.pem")
34 stack = self.get_stack("ssl.ca_certs = %s\n" % path)
35- self.warnings = []
36- def warning(*args):
37- self.warnings.append(args[0] % args[1:])
38- self.overrideAttr(trace, 'warning', warning)
39- self.assertEquals(_urllib2_wrappers.DEFAULT_CA_PATH,
40- stack.get('ssl.ca_certs'))
41- self.assertLength(1, self.warnings)
42- self.assertContainsRe(self.warnings[0],
43- "is not valid for \"ssl.ca_certs\"")
44+ self.assertRaises(ConfigOptionValueError, stack.get, 'ssl.ca_certs')
45
46
47 class CertReqsConfigTests(TestCaseInTempDir):
48
49=== modified file 'bzrlib/transport/http/_urllib2_wrappers.py'
50--- bzrlib/transport/http/_urllib2_wrappers.py 2012-01-20 09:19:14 +0000
51+++ bzrlib/transport/http/_urllib2_wrappers.py 2012-01-30 14:24:24 +0000
52@@ -1,4 +1,4 @@
53-# Copyright (C) 2006-2011 Canonical Ltd
54+# Copyright (C) 2006-2012 Canonical Ltd
55 #
56 # This program is free software; you can redistribute it and/or modify
57 # it under the terms of the GNU General Public License as published by
58@@ -74,14 +74,39 @@
59 import ssl
60 """)
61
62-DEFAULT_CA_PATH = u"/etc/ssl/certs/ca-certificates.crt"
63
64+# Note for packagers: if there is no package providing certs for your platform,
65+# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
66+ssl_ca_certs_known_locations = [
67+ u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
68+ u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
69+ u'/etc/ssl/ca-bundle.pem', # OpenSuse
70+ u'/etc/ssl/cert.pem', # OpenSuse
71+ u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
72+ # XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
73+ u'/etc/openssl/certs/ca-certificates.crt', # Solaris
74+ ]
75
76 def default_ca_certs():
77- if not os.path.exists(DEFAULT_CA_PATH):
78- raise ValueError("default ca certs path %s does not exist" %
79- DEFAULT_CA_PATH)
80- return DEFAULT_CA_PATH
81+ if sys.platform == 'win32':
82+ # FIXME: We could reuse bzrlib.transport.http.ca_bundle but import that
83+ # here sounds... too hackish. Waiting for the windows installer guys
84+ # feedback on which path to use -- vila 2012-01-25
85+ pass
86+ elif sys.platform == 'darwin':
87+ # FIXME: Needs some default value for osx, waiting for osx installers
88+ # guys feedback -- vila 2012-01-25
89+ pass
90+ else:
91+ # Try known locations for friendly OSes providing the root certificates
92+ # without making them hard to use for any https client.
93+ for path in ssl_ca_certs_known_locations:
94+ if os.path.exists(path):
95+ # First found wins
96+ return path
97+ # A default path that makes sense and will be mentioned in the error
98+ # presented to the user, even if not correct for all platforms
99+ return ssl_ca_certs_known_locations[0]
100
101
102 def ca_certs_from_store(path):
103@@ -90,10 +115,6 @@
104 return path
105
106
107-def default_cert_reqs():
108- return u"required"
109-
110-
111 def cert_reqs_from_store(unicode_str):
112 import ssl
113 try:
114@@ -109,13 +130,13 @@
115 opt_ssl_ca_certs = config.Option('ssl.ca_certs',
116 from_unicode=ca_certs_from_store,
117 default=default_ca_certs,
118- invalid='warning',
119+ invalid='error',
120 help="""\
121 Path to certification authority certificates to trust.
122 """)
123
124 opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
125- default=default_cert_reqs,
126+ default=u"required",
127 from_unicode=cert_reqs_from_store,
128 invalid='error',
129 help="""\
130@@ -448,15 +469,16 @@
131 def connect_to_origin(self):
132 # FIXME JRV 2011-12-18: Use location config here?
133 config_stack = config.GlobalStack()
134- if self.ca_certs is None:
135- ca_certs = config_stack.get('ssl.ca_certs')
136- else:
137- ca_certs = self.ca_certs
138 cert_reqs = config_stack.get('ssl.cert_reqs')
139 if cert_reqs == ssl.CERT_NONE:
140 trace.warning("not checking SSL certificates for %s: %d",
141 self.host, self.port)
142+ ca_certs = None
143 else:
144+ if self.ca_certs is None:
145+ ca_certs = config_stack.get('ssl.ca_certs')
146+ else:
147+ ca_certs = self.ca_certs
148 if ca_certs is None:
149 trace.warning(
150 "no valid trusted SSL CA certificates file set. See "
151
152=== modified file 'doc/en/release-notes/bzr-2.5.txt'
153--- doc/en/release-notes/bzr-2.5.txt 2012-01-28 00:41:30 +0000
154+++ doc/en/release-notes/bzr-2.5.txt 2012-01-30 14:24:24 +0000
155@@ -56,12 +56,19 @@
156 * ``bzr branch`` now fetches revisions when branching into an empty
157 control directory. (Jelmer Vernooij, #905594)
158
159+<<<<<<< TREE
160 * ``bzr branch`` generates correct target branch locations again if not
161 specified. (Jelmer Vernooij, #919218)
162
163 * ``bzr send`` works on treeless branches again.
164 (Jelmer Vernooij, #921591)
165
166+=======
167+* A sane default is provided for ``ssl.ca_certs`` which should points to the
168+ Certificate Authority bundle for supported platforms.
169+ (Vincent Ladeuil, #920455)
170+
171+>>>>>>> MERGE-SOURCE
172 * Support scripts that don't call bzrlib.initialize() but still call run_bzr().
173 (Vincent Ladeuil, #917733)
174

Subscribers

People subscribed via source and target branches