[NetApp] Implement security service update

This patch implements support for security service updates
for in use share networks. It works with all three security
service types. For 'active_directory' and 'kerberos', the 'domain'
attribute update isn't supported, since it can might affect
user's access to all related shares.

Change-Id: I8556e4e2e05deb9b116eacbd5afe2f7c5d77b44b
Depends-On: I129a794dfd2d179fa2b9a2fed050459d6f00b0de
Depends-On: I5fef50a17bc72ba66a3a9d6f786742bcb5745d7b
Implements: bp netapp-security-service-update
Co-Authored-By: Carlos Eduardo <ces.eduardo98@gmail.com>
Signed-off-by: Douglas Viroel <viroel@gmail.com>
This commit is contained in:
Douglas Viroel 2021-01-22 16:59:48 -03:00
parent ec9088a3d7
commit ff91db3ece
11 changed files with 797 additions and 7 deletions

View File

@ -20,6 +20,7 @@ class Resource(object):
SHARE_GROUP = 'SHARE_GROUP'
SHARE_REPLICA = 'SHARE_REPLICA'
SHARE_SNAPSHOT = 'SHARE_SNAPSHOT'
SECURITY_SERVICE = 'SECURITY_SERVICE'
class Action(object):
@ -34,6 +35,7 @@ class Action(object):
EXTEND = ('008', _('extend'))
SHRINK = ('009', _('shrink'))
UPDATE_ACCESS_RULES = ('010', _('update access rules'))
ADD_UPDATE_SECURITY_SERVICE = ('011', _('add or update security service'))
ALL = (
ALLOCATE_HOST,
CREATE,
@ -45,6 +47,7 @@ class Action(object):
EXTEND,
SHRINK,
UPDATE_ACCESS_RULES,
ADD_UPDATE_SECURITY_SERVICE,
)
@ -111,6 +114,12 @@ class Detail(object):
_("Failed to grant access to client. The access level or type may "
"be unsupported. You may try again with a different access level "
"or access type."))
UNSUPPORTED_ADD_UDPATE_SECURITY_SERVICE = (
'022',
_("Share driver has failed to setup one or more security services "
"that are associated with the used share network. The security "
"service may be unsupported or the provided parameters are invalid. "
"You may try again with a different set of configurations."))
ALL = (
UNKNOWN_ERROR,
@ -134,6 +143,7 @@ class Detail(object):
DRIVER_FAILED_SHRINK,
FORBIDDEN_CLIENT_ACCESS,
UNSUPPORTED_CLIENT_ACCESS,
UNSUPPORTED_ADD_UDPATE_SECURITY_SERVICE,
)
# Exception and detail mappings

View File

@ -1535,7 +1535,6 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
self.configure_dns(security_service)
config_name = hashlib.md5(six.b(security_service['id'])).hexdigest()
api_args = {
'ldap-client-config': config_name,
'tcp-port': '389',
@ -1590,6 +1589,13 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
LOG.exception(msg)
raise exception.NetAppException(message=msg)
@na_utils.trace
def _delete_ldap_client(self, security_service):
config_name = (
hashlib.md5(six.b(security_service['id'])).hexdigest())
api_args = {'ldap-client-config': config_name}
self.send_request('ldap-client-delete', api_args)
@na_utils.trace
def configure_ldap(self, security_service, timeout=30):
"""Configures LDAP on Vserver."""
@ -1598,17 +1604,59 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
self._enable_ldap_client(config_name, timeout=timeout)
@na_utils.trace
def configure_active_directory(self, security_service, vserver_name):
"""Configures AD on Vserver."""
self.configure_dns(security_service)
self.set_preferred_dc(security_service)
def modify_ldap(self, new_security_service, current_security_service):
"""Modifies LDAP client on a Vserver."""
# Create a new ldap client
self._create_ldap_client(new_security_service)
# Delete current ldap config
try:
self.send_request('ldap-config-delete')
except netapp_api.NaApiError as e:
if e.code != netapp_api.EOBJECTNOTFOUND:
msg = _("An error occurred while deleting original LDAP "
"configuration. %s")
raise exception.NetAppException(msg % e.message)
else:
msg = _("Original LDAP configuration was not found. "
"LDAP modification will continue.")
LOG.debug(msg)
new_config_name = (
hashlib.md5(six.b(new_security_service['id'])).hexdigest())
# Create ldap config with the new client
api_args = {'client-config': new_config_name, 'client-enabled': 'true'}
self.send_request('ldap-config-create', api_args)
# Delete old client configuration
try:
self._delete_ldap_client(current_security_service)
except netapp_api.NaApiError as e:
if e.code != netapp_api.EOBJECTNOTFOUND:
msg = _("An error occurred while deleting original LDAP "
"client configuration. %s")
raise exception.NetAppException(msg % e.message)
else:
msg = _("Original LDAP client configuration was not found.")
LOG.debug(msg)
@na_utils.trace
def _get_cifs_server_name(self, vserver_name):
# 'cifs-server' is CIFS Server NetBIOS Name, max length is 15.
# Should be unique within each domain (data['domain']).
# Cut to 15 char with begin and end, attempt to make valid DNS hostname
cifs_server = (vserver_name[0:8] +
'-' +
vserver_name[-6:]).replace('_', '-').upper()
return cifs_server
@na_utils.trace
def configure_active_directory(self, security_service, vserver_name):
"""Configures AD on Vserver."""
self.configure_dns(security_service)
self.set_preferred_dc(security_service)
cifs_server = self._get_cifs_server_name(vserver_name)
api_args = {
'admin-username': security_service['user'],
@ -1628,6 +1676,46 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
msg = _("Failed to create CIFS server entry. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def modify_active_directory_security_service(
self, vserver_name, differring_keys, new_security_service,
current_security_service):
cifs_server = self._get_cifs_server_name(vserver_name)
current_user_name = current_security_service['user']
new_username = new_security_service['user']
current_cifs_username = cifs_server + '\\' + current_user_name
if 'password' in differring_keys:
api_args = {
'user-name': current_cifs_username,
'user-password': new_security_service['password']
}
try:
self.send_request('cifs-local-user-set-password', api_args)
except netapp_api.NaApiError as e:
msg = _("Failed to modify existing CIFS server password. %s")
raise exception.NetAppException(msg % e.message)
if 'user' in differring_keys:
api_args = {
'user-name': current_cifs_username,
'new-user-name': new_username
}
try:
self.send_request('cifs-local-user-rename', api_args)
except netapp_api.NaApiError as e:
msg = _("Failed to modify existing CIFS server user-name. %s")
raise exception.NetAppException(msg % e.message)
if 'server' in differring_keys:
if current_security_service['server'] is not None:
self.remove_preferred_dcs(current_security_service)
if new_security_service['server'] is not None:
self.set_preferred_dc(new_security_service)
@na_utils.trace
def create_kerberos_realm(self, security_service):
"""Creates Kerberos realm on cluster."""
@ -1694,6 +1782,26 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
security_service['domain'] + '@' +
security_service['domain'].upper())
@na_utils.trace
def update_kerberos_realm(self, security_service):
"""Update Kerberos realm info. Only KDC IP can be changed."""
if not self.features.KERBEROS_VSERVER:
msg = _('Kerberos realms owned by Vserver are supported on ONTAP '
'8.3 or later.')
raise exception.NetAppException(msg)
api_args = {
'admin-server-ip': security_service['server'],
'kdc-ip': security_service['server'],
'password-server-ip': security_service['server'],
'realm': security_service['domain'].upper(),
}
try:
self.send_request('kerberos-realm-modify', api_args)
except netapp_api.NaApiError as e:
msg = _('Failed to update Kerberos realm. %s')
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def disable_kerberos(self, security_service):
"""Disable Kerberos in all Vserver LIFs."""
@ -1819,6 +1927,36 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
for server in servers.get_children()]
return dns_config
@na_utils.trace
def update_dns_configuration(self, dns_ips, domains):
"""Overrides DNS configuration with the specified IPs and domains."""
current_dns_config = self.get_dns_config()
api_args = {
'domains': [],
'name-servers': [],
'dns-state': 'enabled',
}
for domain in domains:
api_args['domains'].append({'string': domain})
for dns_ip in dns_ips:
api_args['name-servers'].append({'ip-address': dns_ip})
empty_dns_config = (not api_args['domains'] and
not api_args['name-servers'])
if current_dns_config:
api_name, api_args = (
('net-dns-destroy', {}) if empty_dns_config
else ('net-dns-modify', api_args))
else:
api_name, api_args = 'net-dns-create', api_args
try:
self.send_request(api_name, api_args)
except netapp_api.NaApiError as e:
msg = _("Failed to update DNS configuration. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def set_preferred_dc(self, security_service):
# server is optional
@ -1842,6 +1980,20 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
msg = _("Failed to set preferred DC. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def remove_preferred_dcs(self, security_service):
"""Drops all preferred DCs at once."""
api_args = {
'domain': security_service['domain'],
}
try:
self.send_request('cifs-domain-preferred-dc-remove', api_args)
except netapp_api.NaApiError as e:
msg = _("Failed to unset preferred DCs. %s")
raise exception.NetAppException(msg % e.message)
@na_utils.trace
def create_volume(self, aggregate_name, volume_name, size_gb,
thin_provisioned=False, snapshot_policy=None,

View File

@ -35,6 +35,9 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
True, *args, **kwargs)
self.library = lib_multi_svm.NetAppCmodeMultiSVMFileStorageLibrary(
self.DRIVER_NAME, **kwargs)
# NetApp driver supports updating security service for in use share
# networks.
self.security_service_update_support = True
def do_setup(self, context):
self.library.do_setup(context)
@ -336,3 +339,19 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver):
snapshots):
return self.library.share_server_migration_get_progress(
context, src_share_server, dest_share_server, shares, snapshots)
def update_share_server_security_service(
self, context, share_server, network_info, share_instances,
share_instance_rules, new_security_service,
current_security_service=None):
return self.library.update_share_server_security_service(
context, share_server, network_info, new_security_service,
current_security_service=current_security_service)
def check_update_share_server_security_service(
self, context, share_server, network_info, share_instances,
share_instance_rules, new_security_service,
current_security_service=None):
return self.library.check_update_share_server_security_service(
context, share_server, network_info, new_security_service,
current_security_service=current_security_service)

View File

@ -319,3 +319,15 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver):
self, context, share_servers, share_group_ref,
share_group_snapshot=None):
raise NotImplementedError
def update_share_server_security_service(
self, context, share_server, network_info, share_instances,
share_instance_rules, new_security_service,
current_security_service=None):
raise NotImplementedError
def check_update_share_server_security_service(
self, context, share_server, network_info, share_instances,
share_instance_rules, new_security_service,
current_security_service=None):
raise NotImplementedError

View File

@ -37,6 +37,7 @@ from manila.common import constants
from manila import coordination
from manila import exception
from manila.i18n import _
from manila.message import api as message_api
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
from manila.share.drivers.netapp.dataontap.client import client_cmode
from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion
@ -161,6 +162,7 @@ class NetAppCmodeFileStorageLibrary(object):
self.configuration.netapp_api_trace_pattern)
self._backend_name = self.configuration.safe_get(
'share_backend_name') or driver_name
self.message_api = message_api.API()
@na_utils.trace
def do_setup(self, context):
@ -440,6 +442,7 @@ class NetAppCmodeFileStorageLibrary(object):
'snapshot_support': True,
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': self._revert_to_snapshot_support,
'security_service_update_support': True,
}
# Add storage service catalog data.

View File

@ -29,6 +29,7 @@ from oslo_utils import units
from manila import exception
from manila.i18n import _
from manila.message import message_field
from manila.share.drivers.netapp.dataontap.client import api as netapp_api
from manila.share.drivers.netapp.dataontap.client import client_cmode
from manila.share.drivers.netapp.dataontap.cluster_mode import data_motion
@ -1365,3 +1366,157 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
.validate_provisioning_options_for_share(provisioning_options,
extra_specs=extra_specs,
qos_specs=qos_specs))
def _get_different_keys_for_equal_ss_type(self, current_sec_service,
new_sec_service):
different_keys = []
valid_keys = ['dns_ip', 'server', 'domain', 'user', 'password', 'ou']
for key, value in current_sec_service.items():
if (current_sec_service[key] != new_sec_service[key]
and key in valid_keys):
different_keys.append(key)
return different_keys
def _is_security_service_valid(self, security_service):
mandatory_params = {
'ldap': ['user', 'password'],
'active_directory': ['dns_ip', 'domain', 'user', 'password'],
'kerberos': ['dns_ip', 'domain', 'user', 'password', 'server'],
}
ss_type = security_service['type']
if ss_type == 'ldap':
ad_domain = security_service.get('domain')
ldap_servers = security_service.get('server')
if not bool(ad_domain) ^ bool(ldap_servers):
msg = _("LDAP security service must have either 'server' or "
"'domain' parameters. Use 'server' for Linux/Unix "
"LDAP servers or 'domain' for Active Directory LDAP "
"server.")
LOG.error(msg)
return False
if not all([security_service[key] is not None
for key in mandatory_params[ss_type]]):
msg = _("The security service %s does not have all the "
"parameters needed to used by the share driver."
) % security_service['id']
LOG.error(msg)
return False
return True
def update_share_server_security_service(self, context, share_server,
network_info,
new_security_service,
current_security_service=None):
current_type = (
current_security_service['type'].lower()
if current_security_service else '')
new_type = new_security_service['type'].lower()
vserver_name, vserver_client = self._get_vserver(
share_server=share_server)
# Check if this update is supported by our driver
if not self.check_update_share_server_security_service(
context, share_server, network_info, new_security_service,
current_security_service=current_security_service):
msg = _("The requested security service update is not supported "
"by the NetApp driver.")
LOG.exception(msg)
raise exception.NetAppException(msg)
if current_security_service is None:
self._client.setup_security_services([new_security_service],
vserver_client,
vserver_name)
LOG.info("A new security service configuration was added to share "
"server '%(share_server_id)s'",
{'share_server_id': share_server['id']})
return
different_keys = self._get_different_keys_for_equal_ss_type(
current_security_service, new_security_service)
if not different_keys:
msg = _("The former and the latter security services are "
"equal. Nothing to do.")
LOG.debug(msg)
return
if 'dns_ip' in different_keys:
dns_ips = set()
domains = set()
# Read all dns-ips and domains from other security services
for sec_svc in network_info['security_services']:
if sec_svc['type'] == current_type:
# skip the one that we are replacing
continue
if sec_svc.get('dns_ip') is not None:
for dns_ip in sec_svc['dns_ip'].split(','):
dns_ips.add(dns_ip.strip())
if sec_svc.get('domain') is not None:
domains.add(sec_svc['domain'])
# Merge with the new dns configuration
if new_security_service.get('dns_ip') is not None:
for dns_ip in new_security_service['dns_ip'].split(','):
dns_ips.add(dns_ip.strip())
if new_security_service.get('domain') is not None:
domains.add(new_security_service['domain'])
# Update vserver DNS configuration
vserver_client.update_dns_configuration(dns_ips, domains)
if new_type == 'kerberos':
if 'server' in different_keys:
# NOTE(dviroel): Only IPs will be updated here, new principals
# won't be configured here. It is expected that only the IP was
# changed, but the KDC remains the same.
LOG.debug('Updating kerberos realm on NetApp backend.')
vserver_client.update_kerberos_realm(new_security_service)
elif new_type == 'active_directory':
vserver_client.modify_active_directory_security_service(
vserver_name, different_keys, new_security_service,
current_security_service)
else:
vserver_client.modify_ldap(new_security_service,
current_security_service)
LOG.info("Security service configuration was updated for share server "
"'%(share_server_id)s'",
{'share_server_id': share_server['id']})
def check_update_share_server_security_service(
self, context, share_server, network_info,
new_security_service, current_security_service=None):
current_type = (
current_security_service['type'].lower()
if current_security_service else '')
if not self._is_security_service_valid(new_security_service):
self.message_api.create(
context,
message_field.Action.ADD_UPDATE_SECURITY_SERVICE,
new_security_service['project_id'],
resource_type=message_field.Resource.SECURITY_SERVICE,
resource_id=new_security_service['id'],
detail=(message_field.Detail
.UNSUPPORTED_ADD_UDPATE_SECURITY_SERVICE))
return False
if current_security_service:
if current_type != 'ldap':
# NOTE(dviroel): We don't support domain/realm updates for
# Kerberos security service, because it might require a new SPN
# to be created and to destroy the old one, thus disrupting all
# shares hosted by this share server. Same issue can happen
# with AD domain modifications.
if (current_security_service['domain'].lower() !=
new_security_service['domain'].lower()):
msg = _("Currently the driver does not support updates "
"in the security service 'domain'.")
LOG.info(msg)
return False
return True

View File

@ -471,7 +471,17 @@ CIFS_SECURITY_SERVICE = {
'ou': 'fake_ou',
'domain': 'fake_domain',
'dns_ip': 'fake_dns_ip',
'server': '',
'server': 'fake_server',
}
CIFS_SECURITY_SERVICE_2 = {
'type': 'active_directory',
'password': 'fake_password_2',
'user': 'fake_user_2',
'ou': 'fake_ou_2',
'domain': 'fake_domain_2',
'dns_ip': 'fake_dns_ip_2',
'server': 'fake_server_2',
}
LDAP_LINUX_SECURITY_SERVICE = {
@ -496,6 +506,7 @@ LDAP_AD_SECURITY_SERVICE = {
'server': None,
}
KERBEROS_SECURITY_SERVICE = {
'type': 'kerberos',
'password': 'fake_password',

View File

@ -2670,6 +2670,42 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.create_kerberos_realm,
fake.KERBEROS_SECURITY_SERVICE)
def test_update_kerberos_realm(self):
self.client.features.add_feature('KERBEROS_VSERVER')
self.mock_object(self.client, 'send_request')
self.client.update_kerberos_realm(fake.KERBEROS_SECURITY_SERVICE)
kerberos_realm_create_args = {
'admin-server-ip': fake.KERBEROS_SECURITY_SERVICE['server'],
'kdc-ip': fake.KERBEROS_SECURITY_SERVICE['server'],
'password-server-ip': fake.KERBEROS_SECURITY_SERVICE['server'],
'realm': fake.KERBEROS_SECURITY_SERVICE['domain'].upper(),
}
self.client.send_request.assert_has_calls([
mock.call('kerberos-realm-modify',
kerberos_realm_create_args)])
def test_update_kerberos_realm_failure(self):
self.client.features.add_feature('KERBEROS_VSERVER')
self.mock_object(self.client, 'send_request', self._mock_api_error())
self.assertRaises(exception.NetAppException,
self.client.update_kerberos_realm,
fake.KERBEROS_SECURITY_SERVICE)
kerberos_realm_create_args = {
'admin-server-ip': fake.KERBEROS_SECURITY_SERVICE['server'],
'kdc-ip': fake.KERBEROS_SECURITY_SERVICE['server'],
'password-server-ip': fake.KERBEROS_SECURITY_SERVICE['server'],
'realm': fake.KERBEROS_SECURITY_SERVICE['domain'].upper(),
}
self.client.send_request.assert_has_calls([
mock.call('kerberos-realm-modify',
kerberos_realm_create_args)])
def test_configure_kerberos(self):
self.client.features.add_feature('KERBEROS_VSERVER')
self.mock_object(self.client, 'send_request')
@ -2880,6 +2916,34 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_has_calls([
mock.call('net-dns-modify', net_dns_create_args)])
def test_update_dns_configuration(self):
fake_configured_dns = {
'dns-state': 'enabled',
'domains': ['fake_domain_2'],
'dns-ips': ['fake_dns_ip_2']
}
self.mock_object(self.client, 'get_dns_config',
mock.Mock(return_value=fake_configured_dns))
self.mock_object(self.client, 'send_request')
self.client.configure_dns(fake.KERBEROS_SECURITY_SERVICE)
domains = set()
domains.add(fake_configured_dns['domains'][0])
domains.add(fake.KERBEROS_SECURITY_SERVICE['domain'])
dns_ips = set()
dns_ips.add(fake_configured_dns['dns-ips'][0])
dns_ips.add(fake.KERBEROS_SECURITY_SERVICE['dns_ip'])
net_dns_create_args = {
'domains': [{'string': domain} for domain in domains],
'dns-state': 'enabled',
'name-servers': [{'ip-address': dns_ip} for dns_ip in dns_ips]
}
self.client.send_request.assert_has_calls([
mock.call('net-dns-modify', net_dns_create_args)])
def test_configure_dns_api_error(self):
self.mock_object(self.client, 'send_request', self._mock_api_error())
@ -2960,6 +3024,34 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.set_preferred_dc,
security_service)
def test_remove_preferred_dcs(self):
self.mock_object(self.client, 'send_request')
security_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE)
self.client.remove_preferred_dcs(security_service)
preferred_dc_add_args = {
'domain': security_service['domain'],
}
self.client.send_request.assert_has_calls([
mock.call('cifs-domain-preferred-dc-remove',
preferred_dc_add_args)])
def test_remove_preferred_dcs_error(self):
self.mock_object(self.client, 'send_request', self._mock_api_error())
security_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE)
self.assertRaises(exception.NetAppException,
self.client.remove_preferred_dcs,
security_service)
preferred_dc_add_args = {
'domain': security_service['domain'],
}
self.client.send_request.assert_has_calls([
mock.call('cifs-domain-preferred-dc-remove',
preferred_dc_add_args)])
def test_create_volume(self):
self.mock_object(self.client, 'send_request')
@ -7622,6 +7714,114 @@ class NetAppClientCmodeTestCase(test.TestCase):
self.client.send_request.assert_called_once_with(
'volume-autosize-get', {'volume': fake.SHARE_NAME})
def test_modify_active_directory_security_service(self):
curr_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE)
new_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE_2)
# we don't support domain change, but this validation isn't made in
# within this method
new_sec_service['domain'] = curr_sec_service['domain']
api_responses = [fake.PASSED_RESPONSE, fake.PASSED_RESPONSE]
self.mock_object(self.client, 'send_request',
mock.Mock(side_effect=api_responses))
self.mock_object(self.client, 'remove_preferred_dcs')
self.mock_object(self.client, 'set_preferred_dc')
differing_keys = {'password', 'user', 'server'}
self.client.modify_active_directory_security_service(
fake.VSERVER_NAME, differing_keys, new_sec_service,
curr_sec_service)
cifs_server = self.client._get_cifs_server_name(fake.VSERVER_NAME)
current_cifs_username = cifs_server + '\\' + curr_sec_service['user']
set_pass_api_args = {
'user-name': current_cifs_username,
'user-password': new_sec_service['password']
}
user_rename_api_args = {
'user-name': current_cifs_username,
'new-user-name': new_sec_service['user']
}
self.client.send_request.assert_has_calls([
mock.call('cifs-local-user-set-password', set_pass_api_args),
mock.call('cifs-local-user-rename', user_rename_api_args)])
self.client.remove_preferred_dcs.assert_called_once_with(
curr_sec_service)
self.client.set_preferred_dc.assert_called_once_with(new_sec_service)
@ddt.data(True, False)
def test_modify_active_directory_security_service_error(
self, cifs_set_password_failure):
curr_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE)
new_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE_2)
# we don't support domain change, but this validation isn't made in
# within this method
new_sec_service['domain'] = curr_sec_service['domain']
if cifs_set_password_failure:
api_responses = [netapp_api.NaApiError(code='fake'),
fake.PASSED_RESPONSE]
else:
api_responses = [fake.PASSED_RESPONSE,
netapp_api.NaApiError(code='fake')]
self.mock_object(self.client, 'send_request',
mock.Mock(side_effect=api_responses))
differing_keys = {'password', 'user', 'server'}
self.assertRaises(
exception.NetAppException,
self.client.modify_active_directory_security_service,
fake.VSERVER_NAME, differing_keys, new_sec_service,
curr_sec_service)
cifs_server = self.client._get_cifs_server_name(fake.VSERVER_NAME)
current_cifs_username = cifs_server + '\\' + curr_sec_service['user']
set_pass_api_args = {
'user-name': current_cifs_username,
'user-password': new_sec_service['password']
}
user_rename_api_args = {
'user-name': current_cifs_username,
'new-user-name': new_sec_service['user']
}
if cifs_set_password_failure:
send_request_calls = [
mock.call('cifs-local-user-set-password', set_pass_api_args)]
else:
send_request_calls = [
mock.call('cifs-local-user-set-password', set_pass_api_args),
mock.call('cifs-local-user-rename', user_rename_api_args)
]
self.client.send_request.assert_has_calls(send_request_calls)
@ddt.data(False, True)
def test_modify_ldap(self, api_not_found):
current_ldap_service = fake.LDAP_AD_SECURITY_SERVICE
new_ldap_service = fake.LDAP_LINUX_SECURITY_SERVICE
config_name = hashlib.md5(six.b(new_ldap_service['id'])).hexdigest()
api_result = (self._mock_api_error(code=netapp_api.EOBJECTNOTFOUND)
if api_not_found else mock.Mock())
mock_create_client = self.mock_object(
self.client, '_create_ldap_client')
mock_send_request = self.mock_object(
self.client, 'send_request',
mock.Mock(return_value=api_result))
mock_delete_client = self.mock_object(
self.client, '_delete_ldap_client',
mock.Mock(return_value=api_result))
self.client.modify_ldap(new_ldap_service, current_ldap_service)
api_args = {'client-config': config_name, 'client-enabled': 'true'}
mock_create_client.assert_called_once_with(new_ldap_service)
mock_send_request.assert_has_calls([
mock.call('ldap-config-delete'),
mock.call('ldap-config-create', api_args)])
mock_delete_client.assert_called_once_with(current_ldap_service)
def test_create_fpolicy_event(self):
self.mock_object(self.client, 'send_request')

View File

@ -2627,3 +2627,164 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
exception.NetAppException,
self.library.validate_provisioning_options_for_share,
fake.PROVISIONING_OPTS_WITH_ADAPT_QOS, qos_specs=None)
def test__get_different_keys_for_equal_ss_type(self):
curr_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE)
new_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE_2)
expected_keys = ['password', 'user', 'ou',
'domain', 'dns_ip', 'server']
result = self.library._get_different_keys_for_equal_ss_type(
curr_sec_service, new_sec_service)
self.assertEqual(expected_keys, result)
@ddt.data(
{'current': None,
'new': fake.CIFS_SECURITY_SERVICE,
'existing': []},
{'current': fake.CIFS_SECURITY_SERVICE,
'new': fake.CIFS_SECURITY_SERVICE_2,
'existing': [fake.CIFS_SECURITY_SERVICE,
fake.KERBEROS_SECURITY_SERVICE]},
{'current': fake.KERBEROS_SECURITY_SERVICE,
'new': fake.KERBEROS_SECURITY_SERVICE_2,
'existing': [fake.CIFS_SECURITY_SERVICE,
fake.KERBEROS_SECURITY_SERVICE]},
{'current': fake.CIFS_SECURITY_SERVICE,
'new': fake.CIFS_SECURITY_SERVICE,
'existing': [fake.CIFS_SECURITY_SERVICE]},
)
@ddt.unpack
def test_update_share_server_security_service(self, current, new,
existing):
fake_context = mock.Mock()
fake_net_info = copy.deepcopy(fake.NETWORK_INFO)
new_sec_service = copy.deepcopy(new)
curr_sec_service = copy.deepcopy(current) if current else None
new_type = new_sec_service['type'].lower()
fake_net_info['security_services'] = existing
if curr_sec_service:
# domain modification aren't support
new_sec_service['domain'] = curr_sec_service['domain']
different_keys = []
if curr_sec_service != new_sec_service:
different_keys = ['dns_ip', 'server', 'domain', 'user', 'password']
if new_sec_service.get('ou') is not None:
different_keys.append('ou')
fake_vserver_client = mock.Mock()
mock_get_vserver = self.mock_object(
self.library, '_get_vserver',
mock.Mock(return_value=[fake.VSERVER1, fake_vserver_client]))
mock_check_update = self.mock_object(
self.library, 'check_update_share_server_security_service',
mock.Mock(return_value=True))
mock_setup_sec_serv = self.mock_object(
self.library._client, 'setup_security_services')
mock_diff_keys = self.mock_object(
self.library, '_get_different_keys_for_equal_ss_type',
mock.Mock(return_value=different_keys))
mock_dns_update = self.mock_object(
fake_vserver_client, 'update_dns_configuration')
mock_update_krealm = self.mock_object(
fake_vserver_client, 'update_kerberos_realm')
mock_modify_ad = self.mock_object(
fake_vserver_client, 'modify_active_directory_security_service')
self.library.update_share_server_security_service(
fake_context, fake.SHARE_SERVER, fake_net_info,
new_sec_service, current_security_service=curr_sec_service)
dns_ips = set()
domains = set()
# we don't need to split and strip since we know that fake have only
# on dns-ip and domain configured
for ss in existing:
if ss['type'] != new_sec_service['type']:
dns_ips.add(ss['dns_ip'])
domains.add(ss['domain'])
dns_ips.add(new_sec_service['dns_ip'])
domains.add(new_sec_service['domain'])
mock_get_vserver.assert_called_once_with(
share_server=fake.SHARE_SERVER)
mock_check_update.assert_called_once_with(
fake_context, fake.SHARE_SERVER, fake_net_info, new_sec_service,
current_security_service=curr_sec_service)
if curr_sec_service is None:
mock_setup_sec_serv.assert_called_once_with(
[new_sec_service], fake_vserver_client, fake.VSERVER1)
else:
mock_diff_keys.assert_called_once_with(curr_sec_service,
new_sec_service)
if different_keys:
mock_dns_update.assert_called_once_with(dns_ips, domains)
if new_type == 'kerberos':
mock_update_krealm.assert_called_once_with(new_sec_service)
elif new_type == 'active_directory':
mock_modify_ad.assert_called_once_with(
fake.VSERVER1, different_keys, new_sec_service,
curr_sec_service)
def test_update_share_server_security_service_check_error(self):
curr_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE)
new_sec_service = copy.deepcopy(fake.CIFS_SECURITY_SERVICE_2)
fake_vserver_client = mock.Mock()
fake_context = mock.Mock()
fake_net_info = mock.Mock()
mock_get_vserver = self.mock_object(
self.library, '_get_vserver',
mock.Mock(return_value=[fake.VSERVER1, fake_vserver_client]))
mock_check_update = self.mock_object(
self.library, 'check_update_share_server_security_service',
mock.Mock(return_value=False))
self.assertRaises(
exception.NetAppException,
self.library.update_share_server_security_service,
fake_context, fake.SHARE_SERVER, fake_net_info,
new_sec_service, current_security_service=curr_sec_service)
mock_get_vserver.assert_called_once_with(
share_server=fake.SHARE_SERVER)
mock_check_update.assert_called_once_with(
fake_context, fake.SHARE_SERVER, fake_net_info,
new_sec_service, current_security_service=curr_sec_service)
@ddt.data(
{'new': fake.LDAP_AD_SECURITY_SERVICE,
'current': fake.LDAP_LINUX_SECURITY_SERVICE,
'expected': True},
{'new': fake.CIFS_SECURITY_SERVICE,
'current': fake.KERBEROS_SECURITY_SERVICE,
'expected': False},
{'new': fake.CIFS_SECURITY_SERVICE,
'current': fake.CIFS_SECURITY_SERVICE,
'expected': True},
{'new': fake.KERBEROS_SECURITY_SERVICE,
'current': fake.KERBEROS_SECURITY_SERVICE,
'expected': True},
{'new': fake.CIFS_SECURITY_SERVICE,
'current': None,
'expected': True},
)
@ddt.unpack
def test_check_update_share_server_security_service(self, new, current,
expected):
result = self.library.check_update_share_server_security_service(
None, None, None, new, current_security_service=current)
self.assertEqual(expected, result)

View File

@ -911,6 +911,7 @@ POOLS = [
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': True,
'security_service_update_support': True,
},
{
'pool_name': AGGREGATES[1],
@ -934,6 +935,7 @@ POOLS = [
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': True,
'security_service_update_support': True,
},
]
@ -957,6 +959,7 @@ POOLS_VSERVER_CREDS = [
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': False,
'security_service_update_support': True,
},
{
'pool_name': AGGREGATES[1],
@ -977,6 +980,7 @@ POOLS_VSERVER_CREDS = [
'create_share_from_snapshot_support': True,
'revert_to_snapshot_support': True,
'qos': False,
'security_service_update_support': True,
},
]
@ -1555,7 +1559,60 @@ CIFS_SECURITY_SERVICE = {
'ou': 'fake_ou',
'domain': 'fake_domain',
'dns_ip': 'fake_dns_ip',
'server': '',
'server': 'fake_server',
}
CIFS_SECURITY_SERVICE_2 = {
'id': 'fake_id_2',
'type': 'active_directory',
'password': 'fake_password_2',
'user': 'fake_user_2',
'ou': 'fake_ou_2',
'domain': 'fake_domain_2',
'dns_ip': 'fake_dns_ip_2',
'server': 'fake_server_2',
}
LDAP_LINUX_SECURITY_SERVICE = {
'id': 'fake_id',
'type': 'ldap',
'user': 'fake_user',
'password': 'fake_password',
'server': 'fake_server',
'ou': 'fake_ou',
'dns_ip': None,
'domain': None
}
LDAP_AD_SECURITY_SERVICE = {
'id': 'fake_id',
'type': 'ldap',
'user': 'fake_user',
'password': 'fake_password',
'domain': 'fake_domain',
'ou': 'fake_ou',
'dns_ip': 'fake_dns_ip',
'server': None,
}
KERBEROS_SECURITY_SERVICE = {
'id': 'fake_id_3',
'type': 'kerberos',
'password': 'fake_password_3',
'user': 'fake_user_3',
'domain': 'fake_realm',
'dns_ip': 'fake_dns_ip_3',
'server': 'fake_server_3',
}
KERBEROS_SECURITY_SERVICE_2 = {
'id': 'fake_id_4',
'type': 'kerberos',
'password': 'fake_password_4',
'user': 'fake_user_4',
'domain': 'fake_realm_2',
'dns_ip': 'fake_dns_ip_4',
'server': 'fake_server_4',
}
SHARE_NETWORK_SUBNET = {

View File

@ -0,0 +1,10 @@
---
features:
- |
NetApp ONTAP driver now supports add and update security services when
they are associated with in use share networks. Both add and update
operations are supported by all three security service types:
``active_directory``, ``kerberos`` and ``ldap``. In order to update their
parameters in a non-disruptively way, ``active_directory`` and ``kerberos``
don't support ``domain`` updates.