[NetApp]:Cinder support for self-signed transport
The NetApp ONTAP driver now supports HTTPS transport for management communication with certificates. ONTAP systems use self-signed certificates by default for HTTPS management access. This patch enables two modes of HTTPS communication: - When ssl_cert_path is configured: certificate validation - When ssl_cert_path is not provided: Unverified SSL context (encrypted transport but skips certificate validation) This allows secure communication while maintaining ease of configuration with ONTAP's default self-signed certificates. Implements: blueprint netapp-self-signed-https Change-Id: Ia486448b85feba636effef609d79ef8e57a6d39a Signed-off-by: jayaanan <jayaanand.borra@netapp.com>
This commit is contained in:
@@ -43,6 +43,37 @@ class NetAppApiServerTests(test.TestCase):
|
||||
self.root = netapp_api.NaServer('127.0.0.1')
|
||||
super(NetAppApiServerTests, self).setUp()
|
||||
|
||||
@ddt.data(
|
||||
{'host': '127.0.0.1', 'ssl_cert_path': None,
|
||||
'port': 8080, 'api_trace_pattern': None
|
||||
},
|
||||
{'host': '127.0.0.1', 'ssl_cert_path': '/test/fake_cert.pem',
|
||||
'port': 8080, 'api_trace_pattern': 'pattern'
|
||||
},
|
||||
)
|
||||
@ddt.unpack
|
||||
def test__init__ssl_verify(self, host, ssl_cert_path, port,
|
||||
api_trace_pattern):
|
||||
|
||||
with mock.patch(
|
||||
'cinder.volume.drivers.netapp.utils.setup_api_trace_pattern'
|
||||
) as mock_trace:
|
||||
server = netapp_api.NaServer(
|
||||
host=host,
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
port=port,
|
||||
api_trace_pattern=api_trace_pattern
|
||||
)
|
||||
|
||||
self.assertEqual(server._host, host)
|
||||
self.assertEqual(server._port, str(port) if port else None)
|
||||
self.assertEqual(server._refresh_conn, True)
|
||||
|
||||
if api_trace_pattern:
|
||||
mock_trace.assert_called_once_with(api_trace_pattern)
|
||||
else:
|
||||
mock_trace.assert_not_called()
|
||||
|
||||
@ddt.data(None, 'ftp')
|
||||
def test_set_transport_type_value_error(self, transport_type):
|
||||
"""Tests setting an invalid transport type"""
|
||||
@@ -191,6 +222,31 @@ class NetAppApiServerTests(test.TestCase):
|
||||
|
||||
self.assertTrue(mock_invoke.called)
|
||||
|
||||
@mock.patch('ssl._create_unverified_context')
|
||||
@mock.patch('urllib.request.build_opener')
|
||||
def test_build_opener_with_ssl_verification_disabled(
|
||||
self, mock_build_opener, mock_unverified_context):
|
||||
self.root._ssl_verify = False
|
||||
mock_unverified_context.return_value = 'mock_unverified_context'
|
||||
|
||||
self.root._build_opener()
|
||||
|
||||
mock_unverified_context.assert_called_once()
|
||||
mock_build_opener.assert_called_once()
|
||||
|
||||
@mock.patch('urllib.request.HTTPPasswordMgrWithDefaultRealm')
|
||||
@mock.patch('urllib.request.build_opener')
|
||||
def test_build_opener_with_basic_auth(self, mock_build_opener,
|
||||
mock_password_mgr):
|
||||
self.root._username = 'user'
|
||||
self.root._password = 'pass'
|
||||
mock_password_mgr.return_value = mock.Mock()
|
||||
|
||||
self.root._build_opener()
|
||||
|
||||
mock_password_mgr.assert_called_once()
|
||||
mock_build_opener.assert_called_once()
|
||||
|
||||
@ddt.data(None, zapi_fakes.FAKE_XML_STR)
|
||||
def test_send_http_request_value_error(self, na_element):
|
||||
"""Tests whether invalid NaElement parameter causes error"""
|
||||
|
@@ -107,6 +107,7 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
||||
hostname='fake_hostname', password='fake_password',
|
||||
username='fake_user', transport_type='https', port=8866,
|
||||
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex",
|
||||
ssl_cert_path='fake_ca',
|
||||
private_key_file='fake_private_key.pem',
|
||||
certificate_file='fake_cert.pem',
|
||||
ca_certificate_file='fake_ca_cert.crt',
|
||||
@@ -140,7 +141,7 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
||||
hostname='fake_hostname', password='fake_password',
|
||||
username='fake_user', transport_type='https', port=8866,
|
||||
trace=mock.ANY, vserver='fake_vserver',
|
||||
api_trace_pattern="fake_regex",
|
||||
api_trace_pattern="fake_regex", ssl_cert_path='fake_ca',
|
||||
private_key_file='fake_private_key.pem',
|
||||
certificate_file='fake_cert.pem',
|
||||
ca_certificate_file='fake_ca_cert.crt',
|
||||
|
@@ -61,6 +61,7 @@ class NetAppDriver(driver.ProxyVD):
|
||||
reason=_('Required configuration not found'))
|
||||
|
||||
config.append_config_values(options.netapp_proxy_opts)
|
||||
config.append_config_values(options.netapp_transport_opts)
|
||||
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
|
||||
|
||||
app_version = na_utils.OpenStackInfo().info()
|
||||
|
@@ -71,8 +71,8 @@ class NaServer(object):
|
||||
|
||||
def __init__(self, host, server_type=SERVER_TYPE_FILER,
|
||||
transport_type=TRANSPORT_TYPE_HTTP,
|
||||
username=None,
|
||||
password=None, port=None, api_trace_pattern=None,
|
||||
ssl_cert_path=None, username=None, password=None, port=None,
|
||||
api_trace_pattern=None,
|
||||
private_key_file=None, certificate_file=None,
|
||||
ca_certificate_file=None, certificate_host_validation=None):
|
||||
self._host = host
|
||||
@@ -87,6 +87,7 @@ class NaServer(object):
|
||||
self._ca_certificate_file = ca_certificate_file
|
||||
self._certificate_host_validation = certificate_host_validation
|
||||
self._refresh_conn = True
|
||||
self._ssl_cert_path = ssl_cert_path
|
||||
|
||||
if api_trace_pattern is not None:
|
||||
na_utils.setup_api_trace_pattern(api_trace_pattern)
|
||||
@@ -306,7 +307,16 @@ class NaServer(object):
|
||||
auth_handler = self._create_certificate_auth_handler()
|
||||
else:
|
||||
auth_handler = self._create_basic_auth_handler()
|
||||
opener = urllib.request.build_opener(auth_handler)
|
||||
|
||||
# Create an SSL context based on _ssl_cert_path
|
||||
if isinstance(self._ssl_cert_path, str): # with cert path
|
||||
ssl_context = (
|
||||
ssl.create_default_context(cafile=self._ssl_cert_path))
|
||||
else: # Disable SSL verification
|
||||
ssl_context = ssl._create_unverified_context()
|
||||
|
||||
https_handler = urllib.request.HTTPSHandler(context=ssl_context)
|
||||
opener = urllib.request.build_opener(auth_handler, https_handler)
|
||||
self._opener = opener
|
||||
|
||||
def _create_basic_auth_handler(self):
|
||||
|
@@ -42,11 +42,13 @@ class Client(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
certificate_file = kwargs['certificate_file']
|
||||
ca_certificate_file = kwargs['ca_certificate_file']
|
||||
certificate_host_validation = kwargs['certificate_host_validation']
|
||||
ssl_cert_path = kwargs.get('ssl_cert_path')
|
||||
if private_key_file and certificate_file and ca_certificate_file:
|
||||
self.connection = netapp_api.NaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
port=kwargs['port'],
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
ca_certificate_file=ca_certificate_file,
|
||||
@@ -57,6 +59,7 @@ class Client(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
host=host,
|
||||
transport_type='https',
|
||||
port=kwargs['port'],
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
certificate_host_validation=certificate_host_validation,
|
||||
@@ -66,6 +69,7 @@ class Client(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
host=host,
|
||||
transport_type=kwargs['transport_type'],
|
||||
port=kwargs['port'],
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
username=username,
|
||||
password=password,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
|
@@ -76,10 +76,13 @@ class RestClient(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
ca_certificate_file = kwargs['ca_certificate_file']
|
||||
certificate_host_validation = kwargs['certificate_host_validation']
|
||||
is_disaggregated = kwargs.get('is_disaggregated', False)
|
||||
ssl_cert_path = kwargs['ssl_cert_path']
|
||||
|
||||
if private_key_file and certificate_file and ca_certificate_file:
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
port=kwargs['port'],
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
@@ -90,6 +93,7 @@ class RestClient(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
port=kwargs['port'],
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
@@ -99,7 +103,7 @@ class RestClient(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type=kwargs['transport_type'],
|
||||
ssl_cert_path=kwargs.pop('ssl_cert_path'),
|
||||
ssl_cert_path=ssl_cert_path,
|
||||
port=kwargs['port'],
|
||||
username=username,
|
||||
password=password,
|
||||
|
@@ -84,6 +84,7 @@ def get_client_for_backend(backend_name, vserver_name=None, force_rest=False):
|
||||
if config.netapp_use_legacy_client and not force_rest:
|
||||
client = client_cmode.Client(
|
||||
transport_type=config.netapp_transport_type,
|
||||
ssl_cert_path=config.netapp_ssl_cert_path,
|
||||
username=config.netapp_login,
|
||||
password=config.netapp_password,
|
||||
hostname=config.netapp_server_hostname,
|
||||
|
@@ -0,0 +1,18 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
NetApp ONTAP driver: Added support for self-signed certificate
|
||||
support for HTTPS transport for management communication between
|
||||
Cinder and NetApp ONTAP.
|
||||
|
||||
ONTAP systems utilize self-signed certificates for HTTPS management
|
||||
access by default. These certificates are generated automatically
|
||||
during the initial setup or deployment of ONTAP. When ssl_cert_path
|
||||
is configured with the extracted certificate file (.PEM format),
|
||||
Cinder establishes HTTPS communication with full certificate validation.
|
||||
When ssl_cert_path is not provided, Cinder automatically uses HTTPS
|
||||
with an unverified SSL context, which provides encrypted communication
|
||||
but skips certificate validation. This allows secure transport while
|
||||
maintaining ease of configuration with ONTAP's default self-signed
|
||||
certificates. Administrators can extract the certificate using tools
|
||||
such as openssl or curl for full certificate validation if desired.
|
Reference in New Issue
Block a user