[NetApp] Certificate based authentication for NetApp drivers
The NetApp ONTAP driver now supports Certificate-Based-Authentication (CBA) for operators that desire certificate based authentication instead of user and password. Note: The options for cert-auth take precedence, if all the auth options are defined in the config (both cert and legacy), the legacy ones are ignored. Change-Id: Idad916a541fc1f355469912da38bd30cf366e3b0
This commit is contained in:
parent
e0c9a012b0
commit
9c26885e9b
@ -411,6 +411,8 @@ def list_opts():
|
||||
cinder_volume_drivers_netapp_options.netapp_connection_opts,
|
||||
cinder_volume_drivers_netapp_options.netapp_transport_opts,
|
||||
cinder_volume_drivers_netapp_options.netapp_basicauth_opts,
|
||||
cinder_volume_drivers_netapp_options.
|
||||
netapp_certificateauth_opts,
|
||||
cinder_volume_drivers_netapp_options.netapp_cluster_opts,
|
||||
cinder_volume_drivers_netapp_options.netapp_provisioning_opts,
|
||||
cinder_volume_drivers_netapp_options.netapp_img_cache_opts,
|
||||
|
@ -168,15 +168,23 @@ class NetAppApiServerTests(test.TestCase):
|
||||
|
||||
mock_invoke.assert_called_with(zapi_fakes.FAKE_XML_STR)
|
||||
|
||||
def test__build_opener_not_implemented_error(self):
|
||||
"""Tests whether certificate style authorization raises Exception"""
|
||||
self.root._auth_style = 'not_basic_auth'
|
||||
def test_build_opener_with_certificate_auth(self):
|
||||
"""Tests whether build opener works with """
|
||||
"""valid certificate parameters"""
|
||||
self.root._private_key_file = 'fake_key.pem'
|
||||
self.root._certificate_file = 'fake_cert.pem'
|
||||
auth_handler = self.mock_object(self.root,
|
||||
'_create_certificate_auth_handler',
|
||||
mock.Mock(return_value='fake_auth'))
|
||||
expected_opener = 'fake_auth'
|
||||
self.mock_object(urllib.request, 'build_opener', auth_handler)
|
||||
self.root._build_opener()
|
||||
self.assertEqual(self.root._opener, expected_opener)
|
||||
self.root._create_certificate_auth_handler.assert_called()
|
||||
|
||||
self.assertRaises(NotImplementedError, self.root._build_opener)
|
||||
|
||||
def test__build_opener_valid(self):
|
||||
"""Tests whether build opener works with valid parameters"""
|
||||
self.root._auth_style = 'basic_auth'
|
||||
def test__build_opener_default(self):
|
||||
"""Tests whether build opener works with """
|
||||
"""default(basic auth) parameters"""
|
||||
mock_invoke = self.mock_object(urllib.request, 'build_opener')
|
||||
|
||||
self.root._build_opener()
|
||||
@ -837,7 +845,9 @@ class NetAppRestApiServerTests(test.TestCase):
|
||||
|
||||
self.assertEqual(expected_vserver, res)
|
||||
|
||||
def test__build_session(self):
|
||||
def test__build_session_with_basic_auth(self):
|
||||
"""Tests whether build session works with """
|
||||
"""default(basic auth) parameters"""
|
||||
fake_session = mock.Mock()
|
||||
mock_requests_session = self.mock_object(
|
||||
requests, 'Session', mock.Mock(return_value=fake_session))
|
||||
@ -856,6 +866,28 @@ class NetAppRestApiServerTests(test.TestCase):
|
||||
mock_requests_session.assert_called_once_with()
|
||||
mock_auth.assert_called_once_with()
|
||||
|
||||
def test__build_session_with_certificate_auth(self):
|
||||
"""Tests whether build session works with """
|
||||
"""valid certificate parameters"""
|
||||
self.rest_client._private_key_file = 'fake_key.pem'
|
||||
self.rest_client._certificate_file = 'fake_cert.pem'
|
||||
self.rest_client._certificate_host_validation = False
|
||||
fake_session = mock.Mock()
|
||||
mock_requests_session = self.mock_object(
|
||||
requests, 'Session', mock.Mock(return_value=fake_session))
|
||||
mock_auth = self.mock_object(
|
||||
self.rest_client, '_create_certificate_auth_handler',
|
||||
mock.Mock(return_value=('fake_cert', 'fake_verify')))
|
||||
self.rest_client._build_session(zapi_fakes.FAKE_HEADERS)
|
||||
self.assertEqual(fake_session, self.rest_client._session)
|
||||
self.assertEqual(('fake_cert', 'fake_verify'),
|
||||
(self.rest_client._session.cert,
|
||||
self.rest_client._session.verify))
|
||||
self.assertEqual(zapi_fakes.FAKE_HEADERS,
|
||||
self.rest_client._session.headers)
|
||||
mock_requests_session.assert_called_once_with()
|
||||
mock_auth.assert_called_once_with()
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test__build_headers(self, enable_tunneling):
|
||||
self.rest_client._vserver = zapi_fakes.VSERVER_NAME
|
||||
@ -880,3 +912,33 @@ class NetAppRestApiServerTests(test.TestCase):
|
||||
|
||||
expected = auth.HTTPBasicAuth(username, password)
|
||||
self.assertEqual(expected.__dict__, res.__dict__)
|
||||
|
||||
def test__create_certificate_auth_handler_default(self):
|
||||
"""Test whether create certificate auth handler """
|
||||
"""works with default params"""
|
||||
self.rest_client._private_key_file = 'fake_key.pem'
|
||||
self.rest_client._certificate_file = 'fake_cert.pem'
|
||||
self.rest_client._certificate_host_validation = False
|
||||
cert = self.rest_client._certificate_file, \
|
||||
self.rest_client._private_key_file
|
||||
self.rest_client._session = mock.Mock()
|
||||
if not self.rest_client._certificate_host_validation:
|
||||
self.assertFalse(self.rest_client._certificate_host_validation)
|
||||
res = self.rest_client._create_certificate_auth_handler()
|
||||
self.assertEqual(res,
|
||||
(cert, self.rest_client._certificate_host_validation))
|
||||
|
||||
def test__create_certificate_auth_handler_with_host_validation(self):
|
||||
"""Test whether create certificate auth handler """
|
||||
"""works with host validation enabled"""
|
||||
self.rest_client._private_key_file = 'fake_key.pem'
|
||||
self.rest_client._certificate_file = 'fake_cert.pem'
|
||||
self.rest_client._ca_certificate_file = 'fake_ca_cert.crt'
|
||||
self.rest_client._certificate_host_validation = True
|
||||
cert = self.rest_client._certificate_file, \
|
||||
self.rest_client._private_key_file
|
||||
self.rest_client._session = mock.Mock()
|
||||
if self.rest_client._certificate_host_validation:
|
||||
self.assertTrue(self.rest_client._certificate_host_validation)
|
||||
res = self.rest_client._create_certificate_auth_handler()
|
||||
self.assertEqual(res, (cert, self.rest_client._ca_certificate_file))
|
||||
|
@ -35,7 +35,12 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
||||
'port': 443,
|
||||
'username': 'admin',
|
||||
'password': 'passw0rd',
|
||||
'api_trace_pattern': 'fake_regex'}
|
||||
'api_trace_pattern': 'fake_regex',
|
||||
'private_key_file': 'fake_private_key.pem',
|
||||
'certificate_file': 'fake_cert.pem',
|
||||
'ca_certificate_file': 'fake_ca_cert.crt',
|
||||
'certificate_host_validation': 'False'
|
||||
}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
@ -43,7 +43,11 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
||||
'username': 'admin',
|
||||
'password': 'passw0rd',
|
||||
'vserver': 'fake_vserver',
|
||||
'api_trace_pattern': 'fake_regex'}
|
||||
'api_trace_pattern': 'fake_regex',
|
||||
'private_key_file': 'fake_private_key.pem',
|
||||
'certificate_file': 'fake_cert.pem',
|
||||
'ca_certificate_file': 'fake_ca_cert.crt',
|
||||
'certificate_host_validation': 'False'}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
@ -41,7 +41,12 @@ CONNECTION_INFO = {'hostname': 'hostname',
|
||||
'password': 'passw0rd',
|
||||
'vserver': 'fake_vserver',
|
||||
'ssl_cert_path': 'fake_ca',
|
||||
'api_trace_pattern': 'fake_regex'}
|
||||
'api_trace_pattern': 'fake_regex',
|
||||
'private_key_file': 'fake_private_key.pem',
|
||||
'certificate_file': 'fake_cert.pem',
|
||||
'ca_certificate_file': 'fake_ca_cert.crt',
|
||||
'certificate_host_validation': 'False'
|
||||
}
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
@ -183,6 +183,7 @@ def get_fake_cmode_config(backend_name):
|
||||
config.append_config_values(na_opts.netapp_connection_opts)
|
||||
config.append_config_values(na_opts.netapp_transport_opts)
|
||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||
config.append_config_values(na_opts.netapp_san_opts)
|
||||
|
@ -76,6 +76,7 @@ class NetAppCDOTDataMotionMixinTestCase(test.TestCase):
|
||||
config.append_config_values(na_opts.netapp_connection_opts)
|
||||
config.append_config_values(na_opts.netapp_transport_opts)
|
||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||
config.append_config_values(na_opts.netapp_san_opts)
|
||||
|
@ -53,6 +53,14 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
||||
group=self.backend)
|
||||
CONF.set_override('netapp_ssl_cert_path', 'fake_ca',
|
||||
group=self.backend)
|
||||
CONF.set_override('netapp_private_key_file', 'fake_private_key.pem',
|
||||
group=self.backend)
|
||||
CONF.set_override('netapp_certificate_file', 'fake_cert.pem',
|
||||
group=self.backend)
|
||||
CONF.set_override('netapp_ca_certificate_file', 'fake_ca_cert.crt',
|
||||
group=self.backend)
|
||||
CONF.set_override('netapp_certificate_host_validation', False,
|
||||
group=self.backend)
|
||||
|
||||
def test_get_backend_configuration(self):
|
||||
self.mock_object(utils, 'CONF')
|
||||
@ -98,14 +106,22 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
||||
self.mock_cmode_client.assert_called_once_with(
|
||||
hostname='fake_hostname', password='fake_password',
|
||||
username='fake_user', transport_type='https', port=8866,
|
||||
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex")
|
||||
trace=mock.ANY, vserver=None, api_trace_pattern="fake_regex",
|
||||
private_key_file='fake_private_key.pem',
|
||||
certificate_file='fake_cert.pem',
|
||||
ca_certificate_file='fake_ca_cert.crt',
|
||||
certificate_host_validation=False)
|
||||
self.mock_cmode_rest_client.assert_not_called()
|
||||
else:
|
||||
self.mock_cmode_rest_client.assert_called_once_with(
|
||||
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', async_rest_timeout=60)
|
||||
ssl_cert_path='fake_ca', async_rest_timeout=60,
|
||||
private_key_file='fake_private_key.pem',
|
||||
certificate_file='fake_cert.pem',
|
||||
ca_certificate_file='fake_ca_cert.crt',
|
||||
certificate_host_validation=False)
|
||||
self.mock_cmode_client.assert_not_called()
|
||||
|
||||
@ddt.data(True, False)
|
||||
@ -124,7 +140,11 @@ 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",
|
||||
private_key_file='fake_private_key.pem',
|
||||
certificate_file='fake_cert.pem',
|
||||
ca_certificate_file='fake_ca_cert.crt',
|
||||
certificate_host_validation=False)
|
||||
self.mock_cmode_rest_client.assert_not_called()
|
||||
else:
|
||||
self.mock_cmode_rest_client.assert_called_once_with(
|
||||
@ -132,7 +152,11 @@ class NetAppCDOTDataMotionTestCase(test.TestCase):
|
||||
username='fake_user', transport_type='https', port=8866,
|
||||
trace=mock.ANY, vserver='fake_vserver',
|
||||
api_trace_pattern="fake_regex", ssl_cert_path='fake_ca',
|
||||
async_rest_timeout = 60)
|
||||
async_rest_timeout = 60,
|
||||
private_key_file='fake_private_key.pem',
|
||||
certificate_file='fake_cert.pem',
|
||||
ca_certificate_file='fake_ca_cert.crt',
|
||||
certificate_host_validation=False)
|
||||
self.mock_cmode_client.assert_not_called()
|
||||
|
||||
|
||||
|
@ -176,6 +176,7 @@ def create_configuration():
|
||||
config.append_config_values(na_opts.netapp_connection_opts)
|
||||
config.append_config_values(na_opts.netapp_transport_opts)
|
||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||
return config
|
||||
|
||||
|
@ -65,18 +65,19 @@ class NetAppLun(object):
|
||||
|
||||
def __str__(self, *args, **kwargs):
|
||||
return 'NetApp LUN [handle:%s, name:%s, size:%s, metadata:%s]' % (
|
||||
self.handle, self.name, self.size, self.metadata)
|
||||
self.handle, self.name, self.size, self.metadata)
|
||||
|
||||
|
||||
class NetAppBlockStorageLibrary(
|
||||
object,
|
||||
metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
"""NetApp block storage library for Data ONTAP."""
|
||||
|
||||
# do not increment this as it may be used in volume type definitions
|
||||
VERSION = "1.0.0"
|
||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
REQUIRED_FLAGS_BASIC = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
REQUIRED_FLAGS_CERT = ['netapp_private_key_file',
|
||||
'netapp_certificate_file']
|
||||
ALLOWED_LUN_OS_TYPES = ['linux', 'aix', 'hpux', 'image', 'windows',
|
||||
'windows_2008', 'windows_gpt', 'solaris',
|
||||
'solaris_efi', 'netware', 'openvms', 'hyper_v']
|
||||
@ -109,6 +110,8 @@ class NetAppBlockStorageLibrary(
|
||||
self.configuration = kwargs['configuration']
|
||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
self.configuration.append_config_values(
|
||||
na_opts.netapp_certificateauth_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||
self.configuration.append_config_values(
|
||||
na_opts.netapp_provisioning_opts)
|
||||
@ -137,13 +140,18 @@ class NetAppBlockStorageLibrary(
|
||||
reserved_percentage = 100 * int(reserved_ratio)
|
||||
msg = ('The "netapp_size_multiplier" configuration option is '
|
||||
'deprecated and will be removed in the Mitaka release. '
|
||||
'Please set "reserved_percentage = %d" instead.') % (
|
||||
reserved_percentage)
|
||||
'Please set "reserved_percentage = %d" instead.') \
|
||||
% reserved_percentage
|
||||
versionutils.report_deprecated_feature(LOG, msg)
|
||||
return reserved_percentage
|
||||
|
||||
def do_setup(self, context):
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
||||
if self.configuration.netapp_private_key_file or\
|
||||
self.configuration.netapp_certificate_file:
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS_CERT,
|
||||
self.configuration)
|
||||
else:
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS_BASIC, self.configuration)
|
||||
self.lun_ostype = (self.configuration.netapp_lun_ostype
|
||||
or self.DEFAULT_LUN_OS)
|
||||
self.host_type = (self.configuration.netapp_host_type
|
||||
@ -242,9 +250,9 @@ class NetAppBlockStorageLibrary(
|
||||
na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info))
|
||||
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
|
||||
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
|
||||
na_utils.is_qos_policy_group_spec_adaptive(
|
||||
qos_policy_group_info))
|
||||
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
|
||||
or na_utils.is_qos_policy_group_spec_adaptive
|
||||
(qos_policy_group_info))
|
||||
|
||||
try:
|
||||
self._create_lun(pool_name, lun_name, size, metadata,
|
||||
@ -367,9 +375,9 @@ class NetAppBlockStorageLibrary(
|
||||
na_utils.get_qos_policy_group_name_from_info(
|
||||
qos_policy_group_info))
|
||||
qos_policy_group_is_adaptive = (volume_utils.is_boolean_str(
|
||||
extra_specs.get('netapp:qos_policy_group_is_adaptive')) or
|
||||
na_utils.is_qos_policy_group_spec_adaptive(
|
||||
qos_policy_group_info))
|
||||
extra_specs.get('netapp:qos_policy_group_is_adaptive'))
|
||||
or na_utils.is_qos_policy_group_spec_adaptive
|
||||
(qos_policy_group_info))
|
||||
|
||||
try:
|
||||
self._clone_lun(
|
||||
@ -882,8 +890,8 @@ class NetAppBlockStorageLibrary(
|
||||
LOG.info("Unmanaged LUN with current path %(path)s and uuid "
|
||||
"%(uuid)s.",
|
||||
{'path': managed_lun.get_metadata_property('Path'),
|
||||
'uuid': managed_lun.get_metadata_property('UUID')
|
||||
or 'unknown'})
|
||||
'uuid': managed_lun.get_metadata_property('UUID') or
|
||||
'unknown'})
|
||||
|
||||
def initialize_connection_iscsi(self, volume, connector):
|
||||
"""Driver entry point to attach a volume to an instance.
|
||||
|
@ -20,8 +20,10 @@
|
||||
Contains classes required to issue API calls to Data ONTAP and OnCommand DFM.
|
||||
"""
|
||||
import random
|
||||
import ssl
|
||||
import urllib
|
||||
|
||||
|
||||
from eventlet import greenthread
|
||||
from eventlet import semaphore
|
||||
from lxml import etree
|
||||
@ -66,21 +68,24 @@ class NaServer(object):
|
||||
URL_FILER = 'servlets/netapp.servlets.admin.XMLrequest_filer'
|
||||
URL_DFM = 'apis/XMLrequest'
|
||||
NETAPP_NS = 'http://www.netapp.com/filer/admin'
|
||||
STYLE_LOGIN_PASSWORD = 'basic_auth'
|
||||
STYLE_CERTIFICATE = 'certificate_auth'
|
||||
|
||||
def __init__(self, host, server_type=SERVER_TYPE_FILER,
|
||||
transport_type=TRANSPORT_TYPE_HTTP,
|
||||
style=STYLE_LOGIN_PASSWORD, username=None,
|
||||
password=None, port=None, api_trace_pattern=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
|
||||
self.set_server_type(server_type)
|
||||
self.set_transport_type(transport_type)
|
||||
self.set_style(style)
|
||||
if port:
|
||||
self.set_port(port)
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._private_key_file = private_key_file
|
||||
self._certificate_file = certificate_file
|
||||
self._ca_certificate_file = ca_certificate_file
|
||||
self._certificate_host_validation = certificate_host_validation
|
||||
self._refresh_conn = True
|
||||
|
||||
if api_trace_pattern is not None:
|
||||
@ -189,10 +194,8 @@ class NaServer(object):
|
||||
"""Invoke the API on the server."""
|
||||
if not na_element or not isinstance(na_element, NaElement):
|
||||
raise ValueError('NaElement must be supplied to invoke API')
|
||||
|
||||
request, request_element = self._create_request(na_element,
|
||||
enable_tunneling)
|
||||
|
||||
if not hasattr(self, '_opener') or not self._opener \
|
||||
or self._refresh_conn:
|
||||
self._build_opener()
|
||||
@ -223,14 +226,14 @@ class NaServer(object):
|
||||
result = self.send_http_request(na_element, enable_tunneling)
|
||||
if result.has_attr('status') and result.get_attr('status') == 'passed':
|
||||
return result
|
||||
code = result.get_attr('errno')\
|
||||
or result.get_child_content('errorno')\
|
||||
code = result.get_attr('errno') \
|
||||
or result.get_child_content('errorno') \
|
||||
or 'ESTATUSFAILED'
|
||||
if code == ESIS_CLONE_NOT_LICENSED:
|
||||
msg = 'Clone operation failed: FlexClone not licensed.'
|
||||
else:
|
||||
msg = result.get_attr('reason')\
|
||||
or result.get_child_content('reason')\
|
||||
msg = result.get_attr('reason') \
|
||||
or result.get_child_content('reason') \
|
||||
or 'Execution status is failed due to unknown reason'
|
||||
raise NaApiError(code, msg)
|
||||
|
||||
@ -299,10 +302,10 @@ class NaServer(object):
|
||||
self._url)
|
||||
|
||||
def _build_opener(self):
|
||||
if self._auth_style == NaServer.STYLE_LOGIN_PASSWORD:
|
||||
auth_handler = self._create_basic_auth_handler()
|
||||
else:
|
||||
if self._private_key_file and self._certificate_file:
|
||||
auth_handler = self._create_certificate_auth_handler()
|
||||
else:
|
||||
auth_handler = self._create_basic_auth_handler()
|
||||
opener = urllib.request.build_opener(auth_handler)
|
||||
self._opener = opener
|
||||
|
||||
@ -314,7 +317,17 @@ class NaServer(object):
|
||||
return auth_handler
|
||||
|
||||
def _create_certificate_auth_handler(self):
|
||||
raise NotImplementedError()
|
||||
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
||||
if not self._certificate_host_validation:
|
||||
context.check_hostname = False
|
||||
context.verify_mode = ssl.CERT_NONE
|
||||
if self._certificate_file and self._private_key_file:
|
||||
context.load_cert_chain(certfile=self._certificate_file,
|
||||
keyfile=self._private_key_file)
|
||||
if self._ca_certificate_file:
|
||||
context.load_verify_locations(cafile=self._ca_certificate_file)
|
||||
auth_handler = urllib.request.HTTPSHandler(context=context)
|
||||
return auth_handler
|
||||
|
||||
def __str__(self):
|
||||
return "server: %s" % self._host
|
||||
@ -606,10 +619,9 @@ class SSHUtil(object):
|
||||
response = stdout.channel.recv(999)
|
||||
if expected_prompt_text not in response.strip().decode():
|
||||
msg = _("Unexpected output. Expected [%(expected)s] but "
|
||||
"received [%(output)s]") % {
|
||||
'expected': expected_prompt_text,
|
||||
'output': response.strip(),
|
||||
}
|
||||
"received [%(output)s]")\
|
||||
% {'expected': expected_prompt_text,
|
||||
'output': response.strip(), }
|
||||
LOG.error(msg)
|
||||
stdin.close()
|
||||
stdout.close()
|
||||
@ -651,7 +663,6 @@ REST_NAMESPACE_EOBJECTNOTFOUND = ('72090006', '72090006')
|
||||
|
||||
|
||||
class RestNaServer(object):
|
||||
|
||||
TRANSPORT_TYPE_HTTP = 'http'
|
||||
TRANSPORT_TYPE_HTTPS = 'https'
|
||||
HTTP_PORT = '80'
|
||||
@ -664,12 +675,18 @@ class RestNaServer(object):
|
||||
|
||||
def __init__(self, host, transport_type=TRANSPORT_TYPE_HTTP,
|
||||
ssl_cert_path=None, username=None, password=None, port=None,
|
||||
api_trace_pattern=None):
|
||||
api_trace_pattern=None,
|
||||
private_key_file=None, certificate_file=None,
|
||||
ca_certificate_file=None, certificate_host_validation=None):
|
||||
self._host = host
|
||||
self.set_transport_type(transport_type)
|
||||
self.set_port(port=port)
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._private_key_file = private_key_file
|
||||
self._certificate_file = certificate_file
|
||||
self._ca_certificate_file = ca_certificate_file
|
||||
self._certificate_host_validation = certificate_host_validation
|
||||
|
||||
if api_trace_pattern is not None:
|
||||
na_utils.setup_api_trace_pattern(api_trace_pattern)
|
||||
@ -799,9 +816,12 @@ class RestNaServer(object):
|
||||
max_retries = Retry(total=5, connect=5, read=2, backoff_factor=1)
|
||||
adapter = HTTPAdapter(max_retries=max_retries)
|
||||
self._session.mount('%s://' % self._protocol, adapter)
|
||||
|
||||
self._session.auth = self._create_basic_auth_handler()
|
||||
self._session.verify = self._ssl_verify
|
||||
if self._private_key_file and self._certificate_file:
|
||||
self._session.cert, self._session.verify\
|
||||
= self._create_certificate_auth_handler()
|
||||
else:
|
||||
self._session.auth = self._create_basic_auth_handler()
|
||||
self._session.verify = self._ssl_verify
|
||||
self._session.headers = headers
|
||||
|
||||
def _build_headers(self, enable_tunneling):
|
||||
@ -819,6 +839,20 @@ class RestNaServer(object):
|
||||
"""Creates and returns a basic HTTP auth handler."""
|
||||
return auth.HTTPBasicAuth(self._username, self._password)
|
||||
|
||||
def _create_certificate_auth_handler(self):
|
||||
"""Creates and returns a certificate auth handler."""
|
||||
self._certificate_host_validation = self._session.verify
|
||||
if self._certificate_file and self._private_key_file \
|
||||
and self._ca_certificate_file:
|
||||
self._session.cert = (self._certificate_file,
|
||||
self._private_key_file)
|
||||
if self._certificate_host_validation:
|
||||
self._session.verify = self._ca_certificate_file
|
||||
elif self._certificate_file and self._private_key_file:
|
||||
self._session.cert = (self._certificate_file,
|
||||
self._private_key_file)
|
||||
return self._session.cert, self._session.verify
|
||||
|
||||
@volume_utils.trace_api(
|
||||
filter_function=na_utils.trace_filter_func_rest_api)
|
||||
def send_http_request(self, method, url, body, headers):
|
||||
|
@ -38,13 +38,37 @@ class Client(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
username = kwargs['username']
|
||||
password = kwargs['password']
|
||||
api_trace_pattern = kwargs['api_trace_pattern']
|
||||
self.connection = netapp_api.NaServer(
|
||||
host=host,
|
||||
transport_type=kwargs['transport_type'],
|
||||
port=kwargs['port'],
|
||||
username=username,
|
||||
password=password,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
private_key_file = kwargs['private_key_file']
|
||||
certificate_file = kwargs['certificate_file']
|
||||
ca_certificate_file = kwargs['ca_certificate_file']
|
||||
certificate_host_validation = kwargs['certificate_host_validation']
|
||||
if private_key_file and certificate_file and ca_certificate_file:
|
||||
self.connection = netapp_api.NaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
port=kwargs['port'],
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
ca_certificate_file=ca_certificate_file,
|
||||
certificate_host_validation=certificate_host_validation,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
elif private_key_file and certificate_file:
|
||||
self.connection = netapp_api.NaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
port=kwargs['port'],
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
certificate_host_validation=certificate_host_validation,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
else:
|
||||
self.connection = netapp_api.NaServer(
|
||||
host=host,
|
||||
transport_type=kwargs['transport_type'],
|
||||
port=kwargs['port'],
|
||||
username=username,
|
||||
password=password,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
|
||||
self.ssh_client = self._init_ssh_client(host, username, password)
|
||||
|
||||
|
@ -71,14 +71,38 @@ class RestClient(object, metaclass=volume_utils.TraceWrapperMetaclass):
|
||||
username = kwargs['username']
|
||||
password = kwargs['password']
|
||||
api_trace_pattern = kwargs['api_trace_pattern']
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type=kwargs['transport_type'],
|
||||
ssl_cert_path=kwargs.pop('ssl_cert_path'),
|
||||
port=kwargs['port'],
|
||||
username=username,
|
||||
password=password,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
private_key_file = kwargs['private_key_file']
|
||||
certificate_file = kwargs['certificate_file']
|
||||
ca_certificate_file = kwargs['ca_certificate_file']
|
||||
certificate_host_validation = kwargs['certificate_host_validation']
|
||||
if private_key_file and certificate_file and ca_certificate_file:
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
port=kwargs['port'],
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
ca_certificate_file=ca_certificate_file,
|
||||
certificate_host_validation=certificate_host_validation,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
elif private_key_file and certificate_file:
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type='https',
|
||||
port=kwargs['port'],
|
||||
private_key_file=private_key_file,
|
||||
certificate_file=certificate_file,
|
||||
certificate_host_validation=certificate_host_validation,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
else:
|
||||
self.connection = netapp_api.RestNaServer(
|
||||
host=host,
|
||||
transport_type=kwargs['transport_type'],
|
||||
ssl_cert_path=kwargs.pop('ssl_cert_path'),
|
||||
port=kwargs['port'],
|
||||
username=username,
|
||||
password=password,
|
||||
api_trace_pattern=api_trace_pattern)
|
||||
|
||||
self.async_rest_timeout = kwargs.get('async_rest_timeout', 60)
|
||||
|
||||
|
@ -49,7 +49,6 @@ from cinder.volume.drivers.netapp import utils as na_utils
|
||||
from cinder.volume.drivers import nfs
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
HOUSEKEEPING_INTERVAL_SECONDS = 600 # ten minutes
|
||||
@ -67,8 +66,10 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "NetApp_CI"
|
||||
|
||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
REQUIRED_FLAGS_BASIC = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
REQUIRED_FLAGS_CERT = ['netapp_private_key_file',
|
||||
'netapp_certificate_file']
|
||||
DEFAULT_FILTER_FUNCTION = 'capabilities.utilization < 70'
|
||||
DEFAULT_GOODNESS_FUNCTION = '100 - capabilities.utilization'
|
||||
|
||||
@ -81,6 +82,8 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
super(NetAppNfsDriver, self).__init__(*args, **kwargs)
|
||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
self.configuration.append_config_values(
|
||||
na_opts.netapp_certificateauth_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_img_cache_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_nfs_extra_opts)
|
||||
@ -90,7 +93,12 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
def do_setup(self, context):
|
||||
super(NetAppNfsDriver, self).do_setup(context)
|
||||
self._context = context
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
||||
if self.configuration.netapp_private_key_file or\
|
||||
self.configuration.netapp_certificate_file:
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS_CERT,
|
||||
self.configuration)
|
||||
else:
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS_BASIC, self.configuration)
|
||||
self.zapi_client = None
|
||||
|
||||
def check_for_setup_error(self):
|
||||
@ -542,6 +550,7 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
|
||||
def _do_clone_rel_img_cache(self, src, dst, share, cache_file):
|
||||
"""Do clone operation w.r.t image cache file."""
|
||||
|
||||
@utils.synchronized(cache_file, external=True)
|
||||
def _do_clone():
|
||||
dir = self._get_mount_point_for_share(share)
|
||||
@ -552,6 +561,7 @@ class NetAppNfsDriver(driver.ManageableVD,
|
||||
share=share)
|
||||
src_path = '%s/%s' % (dir, src)
|
||||
os.utime(src_path, None)
|
||||
|
||||
_do_clone()
|
||||
|
||||
def _clean_image_cache(self):
|
||||
|
@ -63,8 +63,10 @@ class NetAppNVMeStorageLibrary(
|
||||
|
||||
# do not increment this as it may be used in volume type definitions.
|
||||
VERSION = "1.0.0"
|
||||
REQUIRED_FLAGS = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
REQUIRED_FLAGS_BASIC = ['netapp_login', 'netapp_password',
|
||||
'netapp_server_hostname']
|
||||
REQUIRED_FLAGS_CERT = ['netapp_private_key_file',
|
||||
'netapp_certificate_file']
|
||||
ALLOWED_NAMESPACE_OS_TYPES = ['aix', 'linux', 'vmware', 'windows']
|
||||
ALLOWED_SUBSYSTEM_HOST_TYPES = ['aix', 'linux', 'vmware', 'windows']
|
||||
DEFAULT_NAMESPACE_OS = 'linux'
|
||||
@ -93,6 +95,8 @@ class NetAppNVMeStorageLibrary(
|
||||
self.configuration = kwargs['configuration']
|
||||
self.configuration.append_config_values(na_opts.netapp_connection_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
self.configuration.append_config_values(
|
||||
na_opts.netapp_certificateauth_opts)
|
||||
self.configuration.append_config_values(na_opts.netapp_transport_opts)
|
||||
self.configuration.append_config_values(
|
||||
na_opts.netapp_provisioning_opts)
|
||||
@ -107,7 +111,13 @@ class NetAppNVMeStorageLibrary(
|
||||
self.loopingcalls = loopingcalls.LoopingCalls()
|
||||
|
||||
def do_setup(self, context):
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS, self.configuration)
|
||||
if self.configuration.netapp_private_key_file or\
|
||||
self.configuration.netapp_certificate_file:
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS_CERT,
|
||||
self.configuration)
|
||||
else:
|
||||
na_utils.check_flags(self.REQUIRED_FLAGS_BASIC,
|
||||
self.configuration)
|
||||
self.namespace_ostype = (self.configuration.netapp_namespace_ostype
|
||||
or self.DEFAULT_NAMESPACE_OS)
|
||||
self.host_type = (self.configuration.netapp_host_type
|
||||
|
@ -52,6 +52,7 @@ def get_backend_configuration(backend_name):
|
||||
config.append_config_values(na_opts.netapp_connection_opts)
|
||||
config.append_config_values(na_opts.netapp_transport_opts)
|
||||
config.append_config_values(na_opts.netapp_basicauth_opts)
|
||||
config.append_config_values(na_opts.netapp_certificateauth_opts)
|
||||
config.append_config_values(na_opts.netapp_provisioning_opts)
|
||||
config.append_config_values(na_opts.netapp_cluster_opts)
|
||||
config.append_config_values(na_opts.netapp_san_opts)
|
||||
@ -72,6 +73,11 @@ def get_client_for_backend(backend_name, vserver_name=None, force_rest=False):
|
||||
username=config.netapp_login,
|
||||
password=config.netapp_password,
|
||||
hostname=config.netapp_server_hostname,
|
||||
private_key_file=config.netapp_private_key_file,
|
||||
certificate_file=config.netapp_certificate_file,
|
||||
ca_certificate_file=config.netapp_ca_certificate_file,
|
||||
certificate_host_validation=
|
||||
config.netapp_certificate_host_validation,
|
||||
port=config.netapp_server_port,
|
||||
vserver=vserver_name or config.netapp_vserver,
|
||||
trace=volume_utils.TRACE_API,
|
||||
@ -83,12 +89,16 @@ def get_client_for_backend(backend_name, vserver_name=None, force_rest=False):
|
||||
username=config.netapp_login,
|
||||
password=config.netapp_password,
|
||||
hostname=config.netapp_server_hostname,
|
||||
private_key_file=config.netapp_private_key_file,
|
||||
certificate_file=config.netapp_certificate_file,
|
||||
ca_certificate_file=config.netapp_ca_certificate_file,
|
||||
certificate_host_validation=
|
||||
config.netapp_certificate_host_validation,
|
||||
port=config.netapp_server_port,
|
||||
vserver=vserver_name or config.netapp_vserver,
|
||||
trace=volume_utils.TRACE_API,
|
||||
api_trace_pattern=config.netapp_api_trace_pattern,
|
||||
async_rest_timeout=config.netapp_async_rest_timeout)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
|
@ -88,6 +88,80 @@ netapp_basicauth_opts = [
|
||||
'specified in the netapp_login option.'),
|
||||
secret=True), ]
|
||||
|
||||
netapp_certificateauth_opts = [
|
||||
cfg.StrOpt('netapp_private_key_file',
|
||||
sample_default='/path/to/private_key.key',
|
||||
help=("""
|
||||
This option is applicable for both self signed and ca
|
||||
verified certificates.
|
||||
|
||||
For self signed certificate: Absolute path to the file
|
||||
containing the private key associated with the self
|
||||
signed certificate. It is a sensitive file that should
|
||||
be kept secure and protected. The private key is used
|
||||
to sign the certificate and establish the authenticity
|
||||
and integrity of the certificate during the
|
||||
authentication process.
|
||||
|
||||
For ca verified certificate: Absolute path to the file
|
||||
containing the private key associated with the
|
||||
certificate. It is generated when creating the
|
||||
certificate signingrequest (CSR) and should be kept
|
||||
secure and protected. The private key is used to sign
|
||||
the CSR and later used to establish secure connections
|
||||
and authenticate the entity.
|
||||
"""),
|
||||
secret=True),
|
||||
cfg.StrOpt('netapp_certificate_file',
|
||||
sample_default='/path/to/certificate.pem',
|
||||
help=("""
|
||||
This option is applicable for both self signed and ca
|
||||
verified certificates.
|
||||
|
||||
For self signed certificate: Absolute path to the file
|
||||
containing the self-signed digital certificate itself.
|
||||
It includes information about the entity such as the
|
||||
common name (e.g., domain name), organization details,
|
||||
validity period, and public key. The certificate file
|
||||
is generated based on the private key and is used by
|
||||
clients or systems to verify the entity identity during
|
||||
the authentication process.
|
||||
|
||||
For ca verified certificate: Absolute path to the file
|
||||
containing the digital certificate issued by the
|
||||
trusted third-party certificate authority (CA). It
|
||||
includes information about the entity identity, public
|
||||
key, and the CA that issued the certificate. The
|
||||
certificate file is used by clients or systems to verify
|
||||
the authenticity and integrity of the entity during the
|
||||
authentication process.
|
||||
"""),
|
||||
secret=True),
|
||||
cfg.StrOpt('netapp_ca_certificate_file',
|
||||
sample_default='/path/to/ca_certificate.crt',
|
||||
help=("""
|
||||
This option is applicable only for a ca verified
|
||||
certificate.
|
||||
|
||||
Ca verified file: Absolute path to the file containing
|
||||
the public key certificate of the trusted third-party
|
||||
certificate authority (CA) that issued the certificate.
|
||||
It is used by clients or systems to validate the
|
||||
authenticity of the certificate presented by the
|
||||
entity. The CA certificate file is typically pre
|
||||
configured in the trust store of clients or systems to
|
||||
establish trust in certificates issued by that CA.
|
||||
"""),
|
||||
secret=True),
|
||||
cfg.BoolOpt('netapp_certificate_host_validation',
|
||||
default=False,
|
||||
help=('This option is used only if netapp_private_key_file'
|
||||
' and netapp_certificate_file files are passed in the'
|
||||
' configuration.'
|
||||
' By default certificate verification is disabled'
|
||||
' and to verify the certificates please set the value'
|
||||
' to True.')), ]
|
||||
|
||||
netapp_provisioning_opts = [
|
||||
cfg.FloatOpt('netapp_size_multiplier',
|
||||
default=NETAPP_SIZE_MULTIPLIER_DEFAULT,
|
||||
@ -245,6 +319,7 @@ CONF.register_opts(netapp_proxy_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_connection_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_transport_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_basicauth_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_certificateauth_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_cluster_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_provisioning_opts, group=conf.SHARED_CONF_GROUP)
|
||||
CONF.register_opts(netapp_img_cache_opts, group=conf.SHARED_CONF_GROUP)
|
||||
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The NetApp ONTAP driver now supports Certificate-Based-Authentication (CBA)
|
||||
for operators that desire certificate based authentication instead of user
|
||||
and password.
|
||||
Note: The options for cert-auth take precedence, if all the auth options
|
||||
are defined in the config (both cert and legacy), the legacy ones are
|
||||
ignored.
|
Loading…
x
Reference in New Issue
Block a user