Skip to content

Commit 9c79308

Browse files
jayaanan-netapppriteau
authored andcommitted
[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> (cherry picked from commit d3d91d9)
1 parent 5ceb669 commit 9c79308

8 files changed

Lines changed: 96 additions & 5 deletions

File tree

cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,37 @@ def setUp(self):
4444
self.root = netapp_api.NaServer('127.0.0.1')
4545
super(NetAppApiServerTests, self).setUp()
4646

47+
@ddt.data(
48+
{'host': '127.0.0.1', 'ssl_cert_path': None,
49+
'port': 8080, 'api_trace_pattern': None
50+
},
51+
{'host': '127.0.0.1', 'ssl_cert_path': '/test/fake_cert.pem',
52+
'port': 8080, 'api_trace_pattern': 'pattern'
53+
},
54+
)
55+
@ddt.unpack
56+
def test__init__ssl_verify(self, host, ssl_cert_path, port,
57+
api_trace_pattern):
58+
59+
with mock.patch(
60+
'cinder.volume.drivers.netapp.utils.setup_api_trace_pattern'
61+
) as mock_trace:
62+
server = netapp_api.NaServer(
63+
host=host,
64+
ssl_cert_path=ssl_cert_path,
65+
port=port,
66+
api_trace_pattern=api_trace_pattern
67+
)
68+
69+
self.assertEqual(server._host, host)
70+
self.assertEqual(server._port, str(port) if port else None)
71+
self.assertEqual(server._refresh_conn, True)
72+
73+
if api_trace_pattern:
74+
mock_trace.assert_called_once_with(api_trace_pattern)
75+
else:
76+
mock_trace.assert_not_called()
77+
4778
@ddt.data(None, 'ftp')
4879
def test_set_transport_type_value_error(self, transport_type):
4980
"""Tests setting an invalid transport type"""
@@ -189,6 +220,31 @@ def test__build_opener_valid(self):
189220

190221
self.assertTrue(mock_invoke.called)
191222

223+
@mock.patch('ssl._create_unverified_context')
224+
@mock.patch('urllib.request.build_opener')
225+
def test_build_opener_with_ssl_verification_disabled(
226+
self, mock_build_opener, mock_unverified_context):
227+
self.root._ssl_verify = False
228+
mock_unverified_context.return_value = 'mock_unverified_context'
229+
230+
self.root._build_opener()
231+
232+
mock_unverified_context.assert_called_once()
233+
mock_build_opener.assert_called_once()
234+
235+
@mock.patch('urllib.request.HTTPPasswordMgrWithDefaultRealm')
236+
@mock.patch('urllib.request.build_opener')
237+
def test_build_opener_with_basic_auth(self, mock_build_opener,
238+
mock_password_mgr):
239+
self.root._username = 'user'
240+
self.root._password = 'pass'
241+
mock_password_mgr.return_value = mock.Mock()
242+
243+
self.root._build_opener()
244+
245+
mock_password_mgr.assert_called_once()
246+
mock_build_opener.assert_called_once()
247+
192248
@ddt.data(None, zapi_fakes.FAKE_XML_STR)
193249
def test_send_http_request_value_error(self, na_element):
194250
"""Tests whether invalid NaElement parameter causes error"""

cinder/tests/unit/volume/drivers/netapp/dataontap/utils/test_utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ def test_get_client_for_backend(self, use_legacy):
9898
self.mock_cmode_client.assert_called_once_with(
9999
hostname='fake_hostname', password='fake_password',
100100
username='fake_user', transport_type='https', port=8866,
101-
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex")
101+
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex",
102+
ssl_cert_path='fake_ca')
102103
self.mock_cmode_rest_client.assert_not_called()
103104
else:
104105
self.mock_cmode_rest_client.assert_called_once_with(
@@ -124,7 +125,7 @@ def test_get_client_for_backend_with_vserver(self, use_legacy):
124125
hostname='fake_hostname', password='fake_password',
125126
username='fake_user', transport_type='https', port=8866,
126127
trace=mock.ANY, vserver='fake_vserver',
127-
api_trace_pattern="fake_regex")
128+
api_trace_pattern="fake_regex", ssl_cert_path='fake_ca')
128129
self.mock_cmode_rest_client.assert_not_called()
129130
else:
130131
self.mock_cmode_rest_client.assert_called_once_with(

cinder/volume/drivers/netapp/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ def __new__(cls, *args, **kwargs):
6161
reason=_('Required configuration not found'))
6262

6363
config.append_config_values(options.netapp_proxy_opts)
64+
config.append_config_values(options.netapp_transport_opts)
6465
na_utils.check_flags(NetAppDriver.REQUIRED_FLAGS, config)
6566

6667
app_version = na_utils.OpenStackInfo().info()

cinder/volume/drivers/netapp/dataontap/client/api.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
2121
"""
2222
import random
23+
import ssl
2324

2425
from eventlet import greenthread
2526
from eventlet import semaphore
@@ -72,7 +73,7 @@ class NaServer(object):
7273

7374
def __init__(self, host, server_type=SERVER_TYPE_FILER,
7475
transport_type=TRANSPORT_TYPE_HTTP,
75-
style=STYLE_LOGIN_PASSWORD, username=None,
76+
style=STYLE_LOGIN_PASSWORD, ssl_cert_path=None, username=None,
7677
password=None, port=None, api_trace_pattern=None):
7778
self._host = host
7879
self.set_server_type(server_type)
@@ -83,6 +84,7 @@ def __init__(self, host, server_type=SERVER_TYPE_FILER,
8384
self._username = username
8485
self._password = password
8586
self._refresh_conn = True
87+
self._ssl_cert_path = ssl_cert_path
8688

8789
if api_trace_pattern is not None:
8890
na_utils.setup_api_trace_pattern(api_trace_pattern)
@@ -305,7 +307,16 @@ def _build_opener(self):
305307
auth_handler = self._create_basic_auth_handler()
306308
else:
307309
auth_handler = self._create_certificate_auth_handler()
308-
opener = urllib.request.build_opener(auth_handler)
310+
311+
# Create an SSL context based on _ssl_cert_path
312+
if isinstance(self._ssl_cert_path, str): # with cert path
313+
ssl_context = (
314+
ssl.create_default_context(cafile=self._ssl_cert_path))
315+
else: # Disable SSL verification
316+
ssl_context = ssl._create_unverified_context()
317+
318+
https_handler = urllib.request.HTTPSHandler(context=ssl_context)
319+
opener = urllib.request.build_opener(auth_handler, https_handler)
309320
self._opener = opener
310321

311322
def _create_basic_auth_handler(self):

cinder/volume/drivers/netapp/dataontap/client/client_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ def __init__(self, **kwargs):
4242
username = kwargs['username']
4343
password = kwargs['password']
4444
api_trace_pattern = kwargs['api_trace_pattern']
45+
ssl_cert_path = kwargs.get('ssl_cert_path')
4546
self.connection = netapp_api.NaServer(
4647
host=host,
4748
transport_type=kwargs['transport_type'],
4849
port=kwargs['port'],
50+
ssl_cert_path=ssl_cert_path,
4951
username=username,
5052
password=password,
5153
api_trace_pattern=api_trace_pattern)

cinder/volume/drivers/netapp/dataontap/client/client_cmode_rest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,11 @@ def __init__(self, **kwargs):
7373
username = kwargs['username']
7474
password = kwargs['password']
7575
api_trace_pattern = kwargs['api_trace_pattern']
76+
ssl_cert_path = kwargs['ssl_cert_path']
7677
self.connection = netapp_api.RestNaServer(
7778
host=host,
7879
transport_type=kwargs['transport_type'],
79-
ssl_cert_path=kwargs.pop('ssl_cert_path'),
80+
ssl_cert_path=ssl_cert_path,
8081
port=kwargs['port'],
8182
username=username,
8283
password=password,

cinder/volume/drivers/netapp/dataontap/utils/utils.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def get_client_for_backend(backend_name, vserver_name=None, force_rest=False):
6969
if config.netapp_use_legacy_client and not force_rest:
7070
client = client_cmode.Client(
7171
transport_type=config.netapp_transport_type,
72+
ssl_cert_path=config.netapp_ssl_cert_path,
7273
username=config.netapp_login,
7374
password=config.netapp_password,
7475
hostname=config.netapp_server_hostname,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
features:
3+
- |
4+
NetApp ONTAP driver: Added support for self-signed certificate
5+
support for HTTPS transport for management communication between
6+
Cinder and NetApp ONTAP.
7+
8+
ONTAP systems utilize self-signed certificates for HTTPS management
9+
access by default. These certificates are generated automatically
10+
during the initial setup or deployment of ONTAP. When ssl_cert_path
11+
is configured with the extracted certificate file (.PEM format),
12+
Cinder establishes HTTPS communication with full certificate validation.
13+
When ssl_cert_path is not provided, Cinder automatically uses HTTPS
14+
with an unverified SSL context, which provides encrypted communication
15+
but skips certificate validation. This allows secure transport while
16+
maintaining ease of configuration with ONTAP's default self-signed
17+
certificates. Administrators can extract the certificate using tools
18+
such as openssl or curl for full certificate validation if desired.

0 commit comments

Comments
 (0)