SSL Certificate Validation

This adds support for validation of ssl certs returned by remote
servers over SSL. The --ca-file param represents the CA cert used
to sign the remote server's cert. Use --insecure if the remote
server is using a self-signed cert or you don't have the CA cert.

Related to bp glance-client-parity

Change-Id: I45253a6e2d88da599addfcc464571e62ae920166
This commit is contained in:
Brian Waldon 2012-08-02 14:16:13 -07:00
parent 18543b1a46
commit ff34cfc50f
4 changed files with 86 additions and 25 deletions
glanceclient

@ -5,9 +5,15 @@ OpenStack Client interface. Handles the REST calls and responses.
import copy import copy
import httplib import httplib
import logging import logging
import socket
import StringIO import StringIO
import urlparse import urlparse
try:
import ssl
except ImportError:
#TODO(bcwaldon): Handle this failure more gracefully
pass
try: try:
import json import json
@ -30,23 +36,33 @@ CHUNKSIZE = 1024 * 64 # 64kB
class HTTPClient(object): class HTTPClient(object):
def __init__(self, endpoint, token=None, timeout=600, insecure=False): def __init__(self, endpoint, **kwargs):
parts = urlparse.urlparse(endpoint) self.endpoint = endpoint
self.connection_class = self.get_connection_class(parts.scheme) self.auth_token = kwargs.get('token')
self.endpoint = (parts.hostname, parts.port) self.connection_params = self.get_connection_params(endpoint, **kwargs)
self.scheme = parts.scheme
self.auth_token = token
@staticmethod @staticmethod
def get_connection_class(scheme): def get_connection_params(endpoint, **kwargs):
try: parts = urlparse.urlparse(endpoint)
return getattr(httplib, '%sConnection' % scheme.upper())
except AttributeError: _args = (parts.hostname, parts.port)
msg = 'Unsupported scheme: %s' % scheme _kwargs = {'timeout': float(kwargs.get('timeout', 600))}
if parts.scheme == 'https':
_class = VerifiedHTTPSConnection
_kwargs['ca_file'] = kwargs.get('ca_file', None)
_kwargs['insecure'] = kwargs.get('insecure', False)
elif parts.scheme == 'http':
_class = httplib.HTTPConnection
else:
msg = 'Unsupported scheme: %s' % parts.scheme
raise exc.InvalidEndpoint(msg) raise exc.InvalidEndpoint(msg)
return (_class, _args, _kwargs)
def get_connection(self): def get_connection(self):
return self.connection_class(*self.endpoint) _class = self.connection_params[0]
return _class(*self.connection_params[1], **self.connection_params[2])
def log_curl_request(self, method, url, kwargs): def log_curl_request(self, method, url, kwargs):
curl = ['curl -i -X %s' % method] curl = ['curl -i -X %s' % method]
@ -58,8 +74,7 @@ class HTTPClient(object):
if 'body' in kwargs: if 'body' in kwargs:
curl.append('-d \'%s\'' % kwargs['body']) curl.append('-d \'%s\'' % kwargs['body'])
endpoint_parts = (self.scheme, self.endpoint[0], self.endpoint[1], url) curl.append('%s%s' % (self.endpoint, url))
curl.append('%s://%s:%s%s' % endpoint_parts)
LOG.debug(' '.join(curl)) LOG.debug(' '.join(curl))
@staticmethod @staticmethod
@ -136,6 +151,47 @@ class HTTPClient(object):
return self._http_request(url, method, **kwargs) return self._http_request(url, method, **kwargs)
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
"""httplib-compatibile connection using client-side SSL authentication
:see http://code.activestate.com/recipes/
577548-https-httplib-client-connection-with-certificate-v/
"""
def __init__(self, host, port, key_file=None, cert_file=None,
ca_file=None, timeout=None, insecure=False):
httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file,
cert_file=cert_file)
self.key_file = key_file
self.cert_file = cert_file
self.ca_file = ca_file
self.timeout = timeout
self.insecure = insecure
def connect(self):
"""
Connect to a host on a given (SSL) port.
If ca_file is pointing somewhere, use it to check Server Certificate.
Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
ssl.wrap_socket(), which forces SSL to check server certificate against
our client certificate.
"""
sock = socket.create_connection((self.host, self.port), self.timeout)
if self._tunnel_host:
self.sock = sock
self._tunnel()
if self.insecure is True:
kwargs = {'cert_reqs': ssl.CERT_NONE}
else:
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
self.sock = ssl.wrap_socket(sock, **kwargs)
class ResponseBodyIterator(object): class ResponseBodyIterator(object):
"""A class that acts as an iterator over an HTTP response.""" """A class that acts as an iterator over an HTTP response."""

@ -64,6 +64,10 @@ class OpenStackImagesShell(object):
"not be verified against any certificate authorities. " "not be verified against any certificate authorities. "
"This option should be used with caution.") "This option should be used with caution.")
parser.add_argument('--ca-file',
help='Path of CA SSL certificate(s) used to sign the remote '
'server\'s certificate.')
parser.add_argument('--timeout', parser.add_argument('--timeout',
default=600, default=600,
help='Number of seconds to wait for a response') help='Number of seconds to wait for a response')
@ -375,11 +379,14 @@ class OpenStackImagesShell(object):
endpoint = args.os_image_url or \ endpoint = args.os_image_url or \
self._get_endpoint(_ksclient, **kwargs) self._get_endpoint(_ksclient, **kwargs)
client = glanceclient.Client(api_version, kwargs = {
endpoint, 'token': token,
token, 'insecure': args.insecure,
insecure=args.insecure, 'timeout': args.timeout,
timeout=args.timeout) 'ca_file': args.ca_file,
}
client = glanceclient.Client(api_version, endpoint, **kwargs)
try: try:
args.func(client, args) args.func(client, args)

@ -28,9 +28,8 @@ class Client(http.HTTPClient):
http requests. (optional) http requests. (optional)
""" """
def __init__(self, endpoint, token=None, timeout=600, insecure=False): def __init__(self, *args, **kwargs):
""" Initialize a new client for the Images v1 API. """ """ Initialize a new client for the Images v1 API. """
super(Client, self).__init__(endpoint, token=token, super(Client, self).__init__(*args, **kwargs)
timeout=timeout, insecure=insecure)
self.images = images.ImageManager(self) self.images = images.ImageManager(self)
self.image_members = image_members.ImageMemberManager(self) self.image_members = image_members.ImageMemberManager(self)

@ -30,9 +30,8 @@ class Client(object):
http requests. (optional) http requests. (optional)
""" """
def __init__(self, endpoint, token=None, timeout=600, **kwargs): def __init__(self, *args, **kwargs):
self.http_client = http.HTTPClient( self.http_client = http.HTTPClient(*args, **kwargs)
endpoint, token=token, timeout=timeout)
self.schemas = schemas.Controller(self.http_client) self.schemas = schemas.Controller(self.http_client)
self.images = images.Controller(self.http_client, self.images = images.Controller(self.http_client,
self._get_image_model()) self._get_image_model())