Use SSL thumbprints for NFC transfer
There are cases when clients cannot build a trust chain in order to verify ESX certificate. The SSL thumbprint is always available and it is the preferred way for verifying the connection when doing NFC transfer. This patch replace the usage of truststore with SSL thumbprints in VmdkReadHandle and VmdkWriteHandle. Change-Id: I9573bada15aef2bf8d25fa824bbbc2b421586733
This commit is contained in:
parent
0a0e7e8bae
commit
c47cfdcf6a
@ -62,16 +62,47 @@ class FileHandle(object):
|
||||
self._last_logged_progress = 0
|
||||
self._last_progress_udpate = 0
|
||||
|
||||
def _create_read_connection(self, url, cookies=None, cacerts=False):
|
||||
def _create_connection(self, url, method, cacerts=False,
|
||||
ssl_thumbprint=None):
|
||||
_urlparse = urlparse.urlparse(url)
|
||||
scheme, netloc, path, params, query, fragment = _urlparse
|
||||
if scheme == 'http':
|
||||
conn = httplib.HTTPConnection(netloc)
|
||||
elif scheme == 'https':
|
||||
conn = httplib.HTTPSConnection(netloc)
|
||||
cert_reqs = None
|
||||
|
||||
# cacerts can be either True or False or contain
|
||||
# actual certificates. If it is a boolean, then
|
||||
# we need to set cert_reqs and clear the cacerts
|
||||
if isinstance(cacerts, bool):
|
||||
if cacerts:
|
||||
cert_reqs = ssl.CERT_REQUIRED
|
||||
else:
|
||||
cert_reqs = ssl.CERT_NONE
|
||||
cacerts = None
|
||||
conn.set_cert(ca_certs=cacerts, cert_reqs=cert_reqs,
|
||||
assert_fingerprint=ssl_thumbprint)
|
||||
else:
|
||||
excep_msg = _("Invalid scheme: %s.") % scheme
|
||||
LOG.error(excep_msg)
|
||||
raise ValueError(excep_msg)
|
||||
|
||||
if query:
|
||||
path = path + '?' + query
|
||||
conn.putrequest(method, path)
|
||||
return conn
|
||||
|
||||
def _create_read_connection(self, url, cookies=None, cacerts=False,
|
||||
ssl_thumbprint=None):
|
||||
LOG.debug("Opening URL: %s for reading.", url)
|
||||
try:
|
||||
headers = {'User-Agent': USER_AGENT}
|
||||
if cookies:
|
||||
headers.update({'Cookie':
|
||||
self._build_vim_cookie_header(cookies)})
|
||||
response = requests.get(url, headers=headers, stream=True,
|
||||
verify=cacerts)
|
||||
return response.raw
|
||||
conn = self._create_connection(url, 'GET', cacerts, ssl_thumbprint)
|
||||
vim_cookie = self._build_vim_cookie_header(cookies)
|
||||
conn.putheader('User-Agent', USER_AGENT)
|
||||
conn.putheader('Cookie', vim_cookie)
|
||||
conn.endheaders()
|
||||
return conn.getresponse()
|
||||
except Exception as excep:
|
||||
# TODO(vbala) We need to catch and raise specific exceptions
|
||||
# related to connection problems, invalid request and invalid
|
||||
@ -86,41 +117,15 @@ class FileHandle(object):
|
||||
cookies=None,
|
||||
overwrite=None,
|
||||
content_type=None,
|
||||
cacerts=False):
|
||||
cacerts=False,
|
||||
ssl_thumbprint=None):
|
||||
"""Create HTTP connection to write to VMDK file."""
|
||||
LOG.debug("Creating HTTP connection to write to file with "
|
||||
"size = %(file_size)d and URL = %(url)s.",
|
||||
{'file_size': file_size,
|
||||
'url': url})
|
||||
_urlparse = urlparse.urlparse(url)
|
||||
scheme, netloc, path, params, query, fragment = _urlparse
|
||||
|
||||
try:
|
||||
if scheme == 'http':
|
||||
conn = httplib.HTTPConnection(netloc)
|
||||
elif scheme == 'https':
|
||||
conn = httplib.HTTPSConnection(netloc)
|
||||
cert_reqs = None
|
||||
|
||||
# cacerts can be either True or False or contain
|
||||
# actual certificates. If it is a boolean, then
|
||||
# we need to set cert_reqs and clear the cacerts
|
||||
if isinstance(cacerts, bool):
|
||||
if cacerts:
|
||||
cert_reqs = ssl.CERT_REQUIRED
|
||||
else:
|
||||
cert_reqs = ssl.CERT_NONE
|
||||
cacerts = None
|
||||
|
||||
conn.set_cert(ca_certs=cacerts, cert_reqs=cert_reqs)
|
||||
else:
|
||||
excep_msg = _("Invalid scheme: %s.") % scheme
|
||||
LOG.error(excep_msg)
|
||||
raise ValueError(excep_msg)
|
||||
|
||||
if query:
|
||||
path = path + '?' + query
|
||||
|
||||
conn = self._create_connection(url, 'PUT', cacerts, ssl_thumbprint)
|
||||
headers = {'User-Agent': USER_AGENT}
|
||||
if file_size:
|
||||
headers.update({'Content-Length': str(file_size)})
|
||||
@ -131,8 +136,6 @@ class FileHandle(object):
|
||||
self._build_vim_cookie_header(cookies)})
|
||||
if content_type:
|
||||
headers.update({'Content-Type': content_type})
|
||||
|
||||
conn.putrequest('PUT', path)
|
||||
for key, value in six.iteritems(headers):
|
||||
conn.putheader(key, value)
|
||||
conn.endheaders()
|
||||
@ -213,16 +216,18 @@ class FileHandle(object):
|
||||
def _find_vmdk_url(self, lease_info, host, port):
|
||||
"""Find the URL corresponding to a VMDK file in lease info."""
|
||||
url = None
|
||||
ssl_thumbprint = None
|
||||
for deviceUrl in lease_info.deviceUrl:
|
||||
if deviceUrl.disk:
|
||||
url = self._fix_esx_url(deviceUrl.url, host, port)
|
||||
ssl_thumbprint = deviceUrl.sslThumbprint
|
||||
break
|
||||
if not url:
|
||||
excep_msg = _("Could not retrieve VMDK URL from lease info.")
|
||||
LOG.error(excep_msg)
|
||||
raise exceptions.VimException(excep_msg)
|
||||
LOG.debug("Found VMDK URL: %s from lease info.", url)
|
||||
return url
|
||||
return url, ssl_thumbprint
|
||||
|
||||
def _log_progress(self, progress):
|
||||
"""Log data transfer progress."""
|
||||
@ -338,7 +343,7 @@ class VmdkWriteHandle(FileHandle):
|
||||
'info')
|
||||
|
||||
# Find VMDK URL where data is to be written
|
||||
self._url = self._find_vmdk_url(lease_info, host, port)
|
||||
self._url, thumbprint = self._find_vmdk_url(lease_info, host, port)
|
||||
self._vm_ref = lease_info.entity
|
||||
|
||||
cookies = session.vim.client.options.transport.cookiejar
|
||||
@ -349,7 +354,7 @@ class VmdkWriteHandle(FileHandle):
|
||||
cookies=cookies,
|
||||
overwrite='t',
|
||||
content_type=octet_stream,
|
||||
cacerts=session._cacert)
|
||||
ssl_thumbprint=thumbprint)
|
||||
FileHandle.__init__(self, self._conn)
|
||||
|
||||
def get_imported_vm(self):
|
||||
@ -499,12 +504,11 @@ class VmdkReadHandle(FileHandle):
|
||||
'info')
|
||||
|
||||
# find URL of the VMDK file to be read and open connection
|
||||
self._url = self._find_vmdk_url(lease_info, host, port)
|
||||
self._url, thumbprint = self._find_vmdk_url(lease_info, host, port)
|
||||
cookies = session.vim.client.options.transport.cookiejar
|
||||
cacerts = session.vim.client.options.transport.verify
|
||||
self._conn = self._create_read_connection(self._url,
|
||||
cookies=cookies,
|
||||
cacerts=cacerts)
|
||||
ssl_thumbprint=thumbprint)
|
||||
FileHandle.__init__(self, self._conn)
|
||||
|
||||
def _create_and_wait_for_lease(self, session, vm_ref):
|
||||
|
@ -24,6 +24,7 @@ from oslo_vmware import exceptions
|
||||
from oslo_vmware import rw_handles
|
||||
from oslo_vmware.tests import base
|
||||
from oslo_vmware import vim_util
|
||||
from urllib3 import connection as httplib
|
||||
|
||||
|
||||
class FileHandleTest(base.TestCase):
|
||||
@ -41,15 +42,23 @@ class FileHandleTest(base.TestCase):
|
||||
device_url_1 = mock.Mock()
|
||||
device_url_1.disk = True
|
||||
device_url_1.url = 'https://*/ds1/vm1.vmdk'
|
||||
device_url_1.sslThumbprint = '11:22:33:44:55'
|
||||
lease_info = mock.Mock()
|
||||
lease_info.deviceUrl = [device_url_0, device_url_1]
|
||||
host = '10.1.2.3'
|
||||
port = 443
|
||||
exp_url = 'https://%s:%d/ds1/vm1.vmdk' % (host, port)
|
||||
vmw_http_file = rw_handles.FileHandle(None)
|
||||
self.assertEqual(exp_url, vmw_http_file._find_vmdk_url(lease_info,
|
||||
host,
|
||||
port))
|
||||
url, thumbprint = vmw_http_file._find_vmdk_url(lease_info, host, port)
|
||||
self.assertEqual(exp_url, url)
|
||||
self.assertEqual('11:22:33:44:55', thumbprint)
|
||||
|
||||
def test_create_connection(self):
|
||||
handle = rw_handles.FileHandle(None)
|
||||
conn = handle._create_connection('http://fira', 'GET')
|
||||
self.assertIsInstance(conn, httplib.HTTPConnection)
|
||||
conn = handle._create_connection('https://fira', 'GET')
|
||||
self.assertIsInstance(conn, httplib.HTTPSConnection)
|
||||
|
||||
|
||||
class FileWriteHandleTest(base.TestCase):
|
||||
@ -182,12 +191,15 @@ class VmdkReadHandleTest(base.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VmdkReadHandleTest, self).setUp()
|
||||
|
||||
send_patcher = mock.patch('requests.sessions.Session.send')
|
||||
self.addCleanup(send_patcher.stop)
|
||||
send_mock = send_patcher.start()
|
||||
self._response = mock.Mock()
|
||||
send_mock.return_value = self._response
|
||||
self._resp = mock.Mock()
|
||||
self._resp.read.return_value = 'fake-data'
|
||||
self._conn = mock.Mock()
|
||||
self._conn.getresponse.return_value = self._resp
|
||||
patcher = mock.patch(
|
||||
'urllib3.connection.HTTPConnection')
|
||||
self.addCleanup(patcher.stop)
|
||||
HTTPConnectionMock = patcher.start()
|
||||
HTTPConnectionMock.return_value = self._conn
|
||||
|
||||
def _create_mock_session(self, disk=True, progress=-1):
|
||||
device_url = mock.Mock()
|
||||
@ -227,25 +239,22 @@ class VmdkReadHandleTest(base.TestCase):
|
||||
def test_read(self):
|
||||
chunk_size = rw_handles.READ_CHUNKSIZE
|
||||
session = self._create_mock_session()
|
||||
self._response.raw.read.return_value = [1] * chunk_size
|
||||
handle = rw_handles.VmdkReadHandle(session, '10.1.2.3', 443,
|
||||
'vm-1', '[ds] disk1.vmdk',
|
||||
chunk_size * 10)
|
||||
handle.read(chunk_size)
|
||||
self.assertEqual(chunk_size, handle._bytes_read)
|
||||
self._response.raw.read.assert_called_once_with(chunk_size)
|
||||
data = handle.read(chunk_size)
|
||||
self.assertEqual('fake-data', data)
|
||||
|
||||
def test_update_progress(self):
|
||||
chunk_size = rw_handles.READ_CHUNKSIZE
|
||||
chunk_size = len('fake-data')
|
||||
vmdk_size = chunk_size * 10
|
||||
session = self._create_mock_session(True, 10)
|
||||
self._response.raw.read.return_value = [1] * chunk_size
|
||||
handle = rw_handles.VmdkReadHandle(session, '10.1.2.3', 443,
|
||||
'vm-1', '[ds] disk1.vmdk',
|
||||
vmdk_size)
|
||||
handle.read(chunk_size)
|
||||
data = handle.read(chunk_size)
|
||||
handle.update_progress()
|
||||
self._response.raw.read.assert_called_once_with(chunk_size)
|
||||
self.assertEqual('fake-data', data)
|
||||
|
||||
def test_update_progress_with_error(self):
|
||||
session = self._create_mock_session(True, 10)
|
||||
|
Loading…
x
Reference in New Issue
Block a user