Merge lp:~rvb/maas/ssl-skip-check into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 1213
Proposed branch: lp:~rvb/maas/ssl-skip-check
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 125 lines (+46/-10)
3 files modified
src/maascli/api.py (+23/-7)
src/maascli/cli.py (+5/-1)
src/maascli/tests/test_api.py (+18/-2)
To merge this branch: bzr merge lp:~rvb/maas/ssl-skip-check
Reviewer Review Type Date Requested Status
Julian Edwards (community) Approve
Review via email: mp+128389@code.launchpad.net

Commit message

Make the cli raise a proper CommandError if the certificate verification failed. Add an option to disable the certificate verification check.

Description of the change

This branch:
- makes the cli raise a proper CommandError if the certificate verification failed.
- adds an option to disable the certificate verification check.

= Notes =

Another solution would have been, when a certificate verification fails, to offer to the user an option to permanently skip the certificate verification (and store that information in the profile). But this solution is simpler so I decided to go with it. This can be improved later if we choose to do it.

This change is a bit light on the testing front… but so is this whole module. This is another reason why I choose to implement a very simple solution, especially this close to the feature freeze deadline.

Drive-by fix: fix lint.

To post a comment you must log in.
Revision history for this message
Julian Edwards (julian-edwards) wrote :

Looks good! Just a couple of small things:

16 + except httplib2.SSLHandshakeError:
17 + raise CommandError(
18 + "Certificate verify failed, use --disable-cert-check to disable "
19 + "the certificate check.")

s/verify/verification/

37 + parser.add_argument(
38 + '--disable-cert-check', action='store_true', help=(
39 + "Disable SSL certificate check"), default=False)

This is a long option and will get a bit annoying if someone needs to repeatedly specify it (e.g. on a dev system), can you make a short one too please, e.g. -d

Thanks!

review: Approve
Revision history for this message
Raphaël Badin (rvb) wrote :

Thanks for the review!

> 16 + except httplib2.SSLHandshakeError:
> 17 + raise CommandError(
> 18 + "Certificate verify failed, use --disable-cert-check to disable "
> 19 + "the certificate check.")
>
> s/verify/verification/

I'll trust you on this one… but be aware that "Certificate verify failed" is a wording I took from the existing exception.

> 37 + parser.add_argument(
> 38 + '--disable-cert-check', action='store_true', help=(
> 39 + "Disable SSL certificate check"), default=False)
>
> This is a long option and will get a bit annoying if someone needs to
> repeatedly specify it (e.g. on a dev system), can you make a short one too
> please, e.g. -d

Good point. But '-d' is already taken (--debug) so I'll add '-di'.

Revision history for this message
Julian Edwards (julian-edwards) wrote :

On Monday 08 October 2012 07:28:22 you wrote:
> Thanks for the review!
>
> > 16 + except httplib2.SSLHandshakeError:
> > 17 + raise CommandError(
> > 18 + "Certificate verify failed, use --disable-cert-check to disable
> > "
> > 19 + "the certificate check.")
> >
> > s/verify/verification/
>
> I'll trust you on this one… but be aware that "Certificate verify failed" is
> a wording I took from the existing exception.

It's wrong then! Verify is a verb, and a noun is needed here. It's really
lazy English (mostly Americanised) to use a verb.

> > 37 + parser.add_argument(
> > 38 + '--disable-cert-check', action='store_true', help=(
> > 39 + "Disable SSL certificate check"), default=False)
> >
> > This is a long option and will get a bit annoying if someone needs to
> > repeatedly specify it (e.g. on a dev system), can you make a short one too
> > please, e.g. -d
>
> Good point. But '-d' is already taken (--debug) so I'll add '-di'.

Ah bugger, I was hoping a single letter opt could be used but I guess -du is
not so bad.

Cheers.

Revision history for this message
MAAS Lander (maas-lander) wrote :

Attempt to merge into lp:maas failed due to conflicts:

text conflict in src/maascli/api.py

Revision history for this message
MAAS Lander (maas-lander) wrote :

Attempt to merge into lp:maas failed due to conflicts:

text conflict in src/maascli/api.py

Revision history for this message
Raphaël Badin (rvb) wrote :

I'll follow Gavin's suggestion and use the same option names that curl uses: '-k/--insecure'. This avoids the confusing that using a double letter short option introduces: one might wonder if '-di' does not mean '-d' + '-i'.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maascli/api.py'
--- src/maascli/api.py 2012-10-08 06:30:25 +0000
+++ src/maascli/api.py 2012-10-08 10:29:19 +0000
@@ -41,12 +41,24 @@
41 )41 )
4242
4343
44def fetch_api_description(url):44def http_request(url, method, body=None, headers=None,
45 insecure=False):
46 """Issue an http request."""
47 http = httplib2.Http(
48 disable_ssl_certificate_validation=insecure)
49 try:
50 return http.request(url, method, body=body, headers=headers)
51 except httplib2.SSLHandshakeError:
52 raise CommandError(
53 "Certificate verification failed, use --insecure/-k to "
54 "disable the certificate check.")
55
56
57def fetch_api_description(url, insecure=False):
45 """Obtain the description of remote API given its base URL."""58 """Obtain the description of remote API given its base URL."""
46 url_describe = urljoin(url, "describe/")59 url_describe = urljoin(url, "describe/")
47 http = httplib2.Http()60 response, content = http_request(
48 response, content = http.request(61 ascii_url(url_describe), "GET", insecure=insecure)
49 ascii_url(url_describe), "GET")
50 if response.status != httplib.OK:62 if response.status != httplib.OK:
51 raise CommandError(63 raise CommandError(
52 "{0.status} {0.reason}:\n{1}".format(response, content))64 "{0.status} {0.reason}:\n{1}".format(response, content))
@@ -129,6 +141,9 @@
129 parser.add_argument(141 parser.add_argument(
130 "-d", "--debug", action="store_true", default=False,142 "-d", "--debug", action="store_true", default=False,
131 help="Display more information about API responses.")143 help="Display more information about API responses.")
144 parser.add_argument(
145 '-k', '--insecure', action='store_true', help=(
146 "Disable SSL certificate check"), default=False)
132147
133 def __call__(self, options):148 def __call__(self, options):
134 # TODO: this is el-cheapo URI Template149 # TODO: this is el-cheapo URI Template
@@ -148,9 +163,10 @@
148 # on urllib2) so that we get full control over HTTP method. TODO:163 # on urllib2) so that we get full control over HTTP method. TODO:
149 # create custom MAASDispatcher to use httplib2 so that MAASClient can164 # create custom MAASDispatcher to use httplib2 so that MAASClient can
150 # be used.165 # be used.
151 http = httplib2.Http()166 insecure = options.insecure
152 response, content = http.request(167 response, content = http_request(
153 uri, self.method, body=body, headers=headers)168 uri, self.method, body=body, headers=headers,
169 insecure=insecure)
154170
155 # Output.171 # Output.
156 if options.debug:172 if options.debug:
157173
=== modified file 'src/maascli/cli.py'
--- src/maascli/cli.py 2012-10-08 06:30:25 +0000
+++ src/maascli/cli.py 2012-10-08 10:29:19 +0000
@@ -52,6 +52,9 @@
52 "a long random-looking string composed of three parts, "52 "a long random-looking string composed of three parts, "
53 "separated by colons."53 "separated by colons."
54 ))54 ))
55 parser.add_argument(
56 '-k', '--insecure', action='store_true', help=(
57 "Disable SSL certificate check"), default=False)
55 parser.set_defaults(credentials=None)58 parser.set_defaults(credentials=None)
5659
57 def __call__(self, options):60 def __call__(self, options):
@@ -61,7 +64,8 @@
61 # Normalise the remote service's URL.64 # Normalise the remote service's URL.
62 url = ensure_trailing_slash(options.url)65 url = ensure_trailing_slash(options.url)
63 # Get description of remote API.66 # Get description of remote API.
64 description = fetch_api_description(url)67 insecure = options.insecure
68 description = fetch_api_description(url, insecure)
65 # Save the config.69 # Save the config.
66 profile_name = options.profile_name70 profile_name = options.profile_name
67 with ProfileConfig.open() as config:71 with ProfileConfig.open() as config:
6872
=== modified file 'src/maascli/tests/test_api.py'
--- src/maascli/tests/test_api.py 2012-10-08 06:30:25 +0000
+++ src/maascli/tests/test_api.py 2012-10-08 10:29:19 +0000
@@ -26,7 +26,10 @@
26from maascli.config import ProfileConfig26from maascli.config import ProfileConfig
27from maastesting.factory import factory27from maastesting.factory import factory
28from maastesting.testcase import TestCase28from maastesting.testcase import TestCase
29from mock import sentinel29from mock import (
30 Mock,
31 sentinel,
32 )
30from testtools.matchers import (33from testtools.matchers import (
31 Equals,34 Equals,
32 IsInstance,35 IsInstance,
@@ -93,7 +96,8 @@
93 self.assertEqual(96 self.assertEqual(
94 content, api.fetch_api_description("http://example.com/api/1.0/"))97 content, api.fetch_api_description("http://example.com/api/1.0/"))
95 request.assert_called_once_with(98 request.assert_called_once_with(
96 b"http://example.com/api/1.0/describe/", "GET")99 b"http://example.com/api/1.0/describe/", "GET", body=None,
100 headers=None)
97101
98 def test_fetch_api_description_not_okay(self):102 def test_fetch_api_description_not_okay(self):
99 # If the response is not 200 OK, fetch_api_description throws toys.103 # If the response is not 200 OK, fetch_api_description throws toys.
@@ -159,6 +163,18 @@
159 " Two-Two: %(two-two)s\n") % headers,163 " Two-Two: %(two-two)s\n") % headers,
160 buf.getvalue())164 buf.getvalue())
161165
166 def test_http_request_raises_error_if_cert_verify_fails(self):
167 self.patch(
168 httplib2.Http, "request",
169 Mock(side_effect=httplib2.SSLHandshakeError()))
170 error = self.assertRaises(
171 CommandError, api.http_request, factory.make_name('fake_url'),
172 factory.make_name('fake_method'))
173 error_expected = (
174 "Certificate verification failed, use --insecure/-k to "
175 "disable the certificate check.")
176 self.assertEqual(error_expected, "%s" % error)
177
162178
163class TestIsResponseTextual(TestCase):179class TestIsResponseTextual(TestCase):
164 """Tests for `is_response_textual`."""180 """Tests for `is_response_textual`."""