[NetApp] Recreate security cert during vserver create.

The certificate is automatically created on NetApp with 1 year i.e. 365
days of expiration time, and admin needs to manually extend it. It would
be nice Manila can take care to create certs with admin configuable
expiration time. Manila should first create the new cert with given
expiration time and if successful, delete the old cert.

Closes-bug: #2011693
Change-Id: I37e52b94dc492e91fe9e673b3619e6716737d39a
This commit is contained in:
Kiran Pawar 2023-03-16 10:22:35 +00:00
parent afd0d723bb
commit 0553eb78be
10 changed files with 391 additions and 8 deletions

View File

@ -175,7 +175,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
@na_utils.trace
def create_vserver(self, vserver_name, root_volume_aggregate_name,
root_volume_name, aggregate_names, ipspace_name):
root_volume_name, aggregate_names, ipspace_name,
security_cert_expire_days):
"""Creates new vserver and assigns aggregates."""
self._create_vserver(
vserver_name, aggregate_names, ipspace_name,
@ -183,6 +184,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
root_volume_aggregate_name=root_volume_aggregate_name,
root_volume_security_style='unix',
name_server_switch='file')
self._modify_security_cert(vserver_name, security_cert_expire_days)
@na_utils.trace
def create_vserver_dp_destination(self, vserver_name, aggregate_names,
@ -230,6 +232,119 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
}
self.send_request('vserver-modify', modify_args)
@na_utils.trace
def _modify_security_cert(self, vserver_name, security_cert_expire_days):
"""Create new security certificate with given expire days."""
# Do not modify security certificate if specified expire days are
# equal to default security certificate expire days i.e. 365.
if security_cert_expire_days == 365:
return
api_args = {
'query': {
'certificate-info': {
'vserver': vserver_name,
'common-name': vserver_name,
'certificate-authority': vserver_name,
'type': 'server',
},
},
'desired-attributes': {
'certificate-info': {
'serial-number': None,
},
},
}
result = self.send_iter_request('security-certificate-get-iter',
api_args)
try:
old_certificate_info_list = result.get_child_by_name(
'attributes-list')
except AttributeError:
LOG.warning('Could not retrieve certificate-info for vserver '
'%(server)s.', {'server': vserver_name})
return
old_serial_nums = []
for certificate_info in old_certificate_info_list.get_children():
serial_num = certificate_info.get_child_content('serial-number')
old_serial_nums.append(serial_num)
try:
create_args = {
'vserver': vserver_name,
'common-name': vserver_name,
'type': 'server',
'expire-days': security_cert_expire_days,
}
self.send_request('security-certificate-create', create_args)
except netapp_api.NaApiError as e:
LOG.warning("Failed to create new security certificate: %s - %s",
e.code, e.message)
return
api_args = {
'query': {
'certificate-info': {
'vserver': vserver_name,
'common-name': vserver_name,
'certificate-authority': vserver_name,
'type': 'server',
},
},
'desired-attributes': {
'certificate-info': {
'serial-number': None,
},
},
}
result = self.send_iter_request('security-certificate-get-iter',
api_args)
try:
new_certificate_info_list = result.get_child_by_name(
'attributes-list')
except AttributeError:
LOG.warning('Could not retrieve certificate-info for vserver '
'%(server)s.', {'server': vserver_name})
return
for certificate_info in new_certificate_info_list.get_children():
serial_num = certificate_info.get_child_content('serial-number')
if serial_num not in old_serial_nums:
try:
ssl_modify_args = {
'certificate-authority': vserver_name,
'common-name': vserver_name,
'certificate-serial-number': serial_num,
'vserver': vserver_name,
'client-authentication-enabled': 'false',
'server-authentication-enabled': 'true',
}
self.send_request('security-ssl-modify', ssl_modify_args)
except netapp_api.NaApiError as e:
LOG.debug('Failed to modify SSL for security certificate '
'with serial number %s: %s - %s', serial_num,
e.code, e.message)
# Delete all old security certificates
for certificate_info in old_certificate_info_list.get_children():
serial_num = certificate_info.get_child_content('serial-number')
delete_args = {
'certificate-authority': vserver_name,
'common-name': vserver_name,
'serial-number': serial_num,
'type': 'server',
'vserver': vserver_name,
}
try:
self.send_request('security-certificate-delete', delete_args)
except netapp_api.NaApiError as e:
LOG.warning('Failed to delete security certificate with '
'serial number %s: %s - %s', serial_num, e.code,
e.message)
@na_utils.trace
def get_vserver_info(self, vserver_name):
"""Retrieves Vserver info."""

View File

@ -46,6 +46,7 @@ CUTOVER_ACTION_MAP = {
DEFAULT_TIMEOUT = 15
DEFAULT_TCP_MAX_XFER_SIZE = 65536
DEFAULT_UDP_MAX_XFER_SIZE = 32768
DEFAULT_SECURITY_CERT_EXPIRE_DAYS = 365
class NetAppRestClient(object):
@ -4120,7 +4121,8 @@ class NetAppRestClient(object):
@na_utils.trace
def create_vserver(self, vserver_name, root_volume_aggregate_name,
root_volume_name, aggregate_names, ipspace_name):
root_volume_name, aggregate_names, ipspace_name,
security_cert_expire_days):
"""Creates new vserver and assigns aggregates."""
# NOTE(nahimsouza): root_volume_aggregate_name and root_volume_name
@ -4129,6 +4131,7 @@ class NetAppRestClient(object):
self._create_vserver(
vserver_name, aggregate_names, ipspace_name,
name_server_switch=['files'])
self._modify_security_cert(vserver_name, security_cert_expire_days)
@na_utils.trace
def create_vserver_dp_destination(self, vserver_name, aggregate_names,
@ -4161,6 +4164,77 @@ class NetAppRestClient(object):
self.send_request('/svm/svms', 'post', body=body)
@na_utils.trace
def _modify_security_cert(self, vserver_name, security_cert_expire_days):
"""Create new security certificate with given expire days."""
# Do not modify security certificate if specified expire days are
# equal to default security certificate expire days i.e. 365.
if security_cert_expire_days == DEFAULT_SECURITY_CERT_EXPIRE_DAYS:
return
query = {
'common-name': vserver_name,
'ca': vserver_name,
'type': 'server',
'svm.name': vserver_name,
}
result = self.send_request('/security/certificates',
'get', query=query)
old_certificate_info_list = result.get('records', [])
if not old_certificate_info_list:
LOG.warning("Unable to retrieve certificate-info for vserver "
"%(server)s'. Cannot set the certificate expiry to "
"%s(conf)s. ", {'server': vserver_name,
'conf': security_cert_expire_days})
return
body = {
'common-name': vserver_name,
'type': 'server',
'svm.name': vserver_name,
'expiry_time': f'P{security_cert_expire_days}DT',
}
query = {
'return_records': 'true'
}
result = self.send_request('/security/certificates',
'post', body=body, query=query)
new_certificate_info_list = result.get('records', [])
if not new_certificate_info_list:
LOG.warning('Failed to create new security certificate for '
'vserver %(server)s.', {'server': vserver_name})
return
for certificate_info in new_certificate_info_list:
cert_uuid = certificate_info.get('uuid', None)
svm = certificate_info.get('svm', [])
svm_uuid = svm.get('uuid', None)
if not svm_uuid or not cert_uuid:
continue
try:
body = {
'certificate': {
'uuid': cert_uuid,
},
'client_enabled': 'false',
}
self.send_request(f'/svm/svms/{svm_uuid}', 'patch',
body=body)
except netapp_api.api.NaApiError:
LOG.debug('Failed to modify SSL for vserver '
'%(server)s.', {'server': vserver_name})
# Delete all old security certificates
for certificate_info in old_certificate_info_list:
uuid = certificate_info.get('uuid', None)
try:
self.send_request(f'/security/certificates/{uuid}', 'delete')
except netapp_api.api.NaApiError:
LOG.error("Failed to delete security certificate for vserver "
"%s.", vserver_name)
@na_utils.trace
def list_node_data_ports(self, node):
"""List data ports from node."""

View File

@ -343,7 +343,8 @@ class NetAppCmodeMultiSVMFileStorageLibrary(
self.configuration.netapp_root_volume_aggregate,
self.configuration.netapp_root_volume,
aggr_set,
ipspace_name)
ipspace_name,
self.configuration.netapp_security_cert_expire_days)
vserver_client = self._get_api_client(vserver=vserver_name)

View File

@ -190,6 +190,14 @@ netapp_provisioning_opts = [
default=60, # Default to one minutes
help='Sets maximum amount of time in seconds to wait for a '
'synchronous ONTAP REST API operation to be completed.'),
cfg.IntOpt('netapp_security_cert_expire_days',
min=1,
max=3652,
default=365,
help='Defines the expiration time (in days) for the '
'certificate created during the vserver creation. This '
'option only applies when the option '
'driver_handles_share_servers is set to True.'),
]
netapp_cluster_opts = [

View File

@ -50,6 +50,8 @@ VSERVER_PEER_STATE = 'peered'
ADMIN_VSERVER_NAME = 'fake_admin_vserver'
NODE_VSERVER_NAME = 'fake_node_vserver'
NFS_VERSIONS = ['nfs3', 'nfs4.0']
SECURITY_CERT_DEFAULT_EXPIRE_DAYS = 365
SECURITY_CERT_LARGE_EXPIRE_DAYS = 3652
ROOT_AGGREGATE_NAMES = ('root_aggr1', 'root_aggr2')
ROOT_VOLUME_AGGREGATE_NAME = 'fake_root_aggr'
ROOT_VOLUME_NAME = 'fake_root_volume'
@ -315,6 +317,18 @@ VSERVER_GET_RESPONSE = etree.XML("""
'aggr2': SHARE_AGGREGATE_NAMES[1],
})
SECURITY_CERT_GET_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
<certificate-info>
<vserver>%(vserver)s</vserver>
<serial-number>12345</serial-number>
</certificate-info>
</attributes-list>
<num-records>1</num-records>
</results>
""" % {'vserver': VSERVER_NAME})
VSERVER_DATA_LIST_RESPONSE = etree.XML("""
<results status="passed">
<attributes-list>
@ -4451,6 +4465,46 @@ SERVICE_POLICIES_REST = {
'num_records': 1,
}
SECURITY_CERT_GET_RESPONSE_REST = {
'records': [
{
'uuid': 'fake_cert_uuid',
'serial_number': 'fake_serial_number',
'key_size': 0,
'hash_function': "sha256",
'common_name': "fake_common_name",
'name': "cert1",
'ca': 'fake_ca',
'expiry_time': 'fake_expiry_time',
'svm': {
'name': VSERVER_NAME,
'uuid': 'fake_uuid',
},
},
],
'num_records': 1,
}
SECURITY_CERT_POST_RESPONSE_REST = {
'records': [
{
'uuid': 'fake_cert_uuid',
'serial_number': 'fake_serial_number',
'key_size': 0,
'hash_function': "sha256",
'common_name': "fake_common_name",
'name': "cert1",
'ca': 'fake_ca',
'expiry_time': 'fake_expiry_time',
'svm': {
'name': VSERVER_NAME,
'uuid': 'fake_uuid',
},
},
],
'num_records': 1,
}
GET_SNAPMIRROR_POLICIES_REST = {
"records": [

View File

@ -420,6 +420,9 @@ class NetAppClientCmodeTestCase(test.TestCase):
def test_create_vserver_no_ipspace(self):
self.mock_object(self.client, 'send_request')
self.mock_object(self.client,
'_modify_security_cert',
mock.Mock())
vserver_create_args = {
'vserver-name': fake.VSERVER_NAME,
@ -438,16 +441,22 @@ class NetAppClientCmodeTestCase(test.TestCase):
fake.ROOT_VOLUME_AGGREGATE_NAME,
fake.ROOT_VOLUME_NAME,
fake.SHARE_AGGREGATE_NAMES,
None)
None,
fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
self.client.send_request.assert_has_calls([
mock.call('vserver-create', vserver_create_args),
mock.call('vserver-modify', vserver_modify_args)])
self.client._modify_security_cert.assert_called_with(
fake.VSERVER_NAME, fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
def test_create_vserver_with_ipspace(self):
self.client.features.add_feature('IPSPACES')
self.mock_object(self.client, 'send_request')
self.mock_object(self.client,
'_modify_security_cert',
mock.Mock())
vserver_create_args = {
'vserver-name': fake.VSERVER_NAME,
@ -467,11 +476,65 @@ class NetAppClientCmodeTestCase(test.TestCase):
fake.ROOT_VOLUME_AGGREGATE_NAME,
fake.ROOT_VOLUME_NAME,
fake.SHARE_AGGREGATE_NAMES,
fake.IPSPACE_NAME)
fake.IPSPACE_NAME,
fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
self.client.send_request.assert_has_calls([
mock.call('vserver-create', vserver_create_args),
mock.call('vserver-modify', vserver_modify_args)])
self.client._modify_security_cert.assert_called_with(
fake.VSERVER_NAME, fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
def test__modify_security_cert(self):
certificate_create_args = {
'vserver': fake.VSERVER_NAME,
'common-name': fake.VSERVER_NAME,
'type': 'server',
'expire-days': fake.SECURITY_CERT_LARGE_EXPIRE_DAYS,
}
self.mock_object(self.client, 'send_request')
api_response = netapp_api.NaElement(fake.SECURITY_CERT_GET_RESPONSE)
self.mock_object(self.client,
'send_iter_request',
mock.Mock(return_value=api_response))
certificate_get_args = {
'query': {
'certificate-info': {
'vserver': fake.VSERVER_NAME,
'common-name': fake.VSERVER_NAME,
'certificate-authority': fake.VSERVER_NAME,
'type': 'server',
},
},
'desired-attributes': {
'certificate-info': {
'serial-number': None,
},
},
}
certificate_delete_args = {
'certificate-authority': fake.VSERVER_NAME,
'common-name': fake.VSERVER_NAME,
'serial-number': '12345',
'type': 'server',
'vserver': fake.VSERVER_NAME,
}
self.client._modify_security_cert(
fake.VSERVER_NAME,
fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
self.client.send_request.assert_has_calls([
mock.call(
'security-certificate-create', certificate_create_args),
mock.call(
'security-certificate-delete', certificate_delete_args)])
self.client.send_iter_request.assert_has_calls([
mock.call('security-certificate-get-iter', certificate_get_args)])
def test_create_vserver_dp_destination(self):
@ -506,7 +569,8 @@ class NetAppClientCmodeTestCase(test.TestCase):
fake.ROOT_VOLUME_AGGREGATE_NAME,
fake.ROOT_VOLUME_NAME,
fake.SHARE_AGGREGATE_NAMES,
fake.IPSPACE_NAME)
fake.IPSPACE_NAME,
fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
def test_get_vserver_root_volume_name(self):

View File

@ -4808,13 +4808,69 @@ class NetAppRestCmodeClientTestCase(test.TestCase):
def test_create_vserver(self):
mock = self.mock_object(self.client, '_create_vserver')
self.mock_object(self.client, '_modify_security_cert',
mock.Mock(return_value=[]))
self.client.create_vserver(fake.VSERVER_NAME, None, None,
[fake.SHARE_AGGREGATE_NAME],
fake.IPSPACE_NAME)
fake.IPSPACE_NAME,
fake.SECURITY_CERT_DEFAULT_EXPIRE_DAYS)
mock.assert_called_once_with(fake.VSERVER_NAME,
[fake.SHARE_AGGREGATE_NAME],
fake.IPSPACE_NAME,
name_server_switch=['files'])
self.client._modify_security_cert.assert_called_once_with(
fake.VSERVER_NAME,
fake.SECURITY_CERT_DEFAULT_EXPIRE_DAYS)
def test__modify_security_cert(self):
api_response = copy.deepcopy(fake.SECURITY_CERT_GET_RESPONSE_REST)
api_response2 = copy.deepcopy(fake.SECURITY_CERT_POST_RESPONSE_REST)
self.mock_object(
self.client, 'send_request',
mock.Mock(side_effect=[api_response, api_response2, None, None]))
query = {
'common-name': fake.VSERVER_NAME,
'ca': fake.VSERVER_NAME,
'type': 'server',
'svm.name': fake.VSERVER_NAME,
}
old_cert_info = copy.deepcopy(
fake.SECURITY_CERT_GET_RESPONSE_REST['records'][0])
old_cert_uuid = old_cert_info['uuid']
body1 = {
'common-name': fake.VSERVER_NAME,
'type': 'server',
'svm.name': fake.VSERVER_NAME,
'expiry_time': 'P' + str(
fake.SECURITY_CERT_LARGE_EXPIRE_DAYS) + 'DT',
}
query1 = {
'return_records': 'true'
}
new_cert_info = copy.deepcopy(
fake.SECURITY_CERT_POST_RESPONSE_REST['records'][0])
new_cert_uuid = new_cert_info['uuid']
new_svm_uuid = new_cert_info['svm']['uuid']
body2 = {
'certificate': {
'uuid': new_cert_uuid,
},
'client_enabled': 'false',
}
self.client._modify_security_cert(
fake.VSERVER_NAME,
fake.SECURITY_CERT_LARGE_EXPIRE_DAYS)
self.client.send_request.assert_has_calls([
mock.call('/security/certificates', 'get', query=query),
mock.call('/security/certificates', 'post', body=body1,
query=query1),
mock.call(f'/svm/svms/{new_svm_uuid}', 'patch', body=body2),
mock.call(f'/security/certificates/{old_cert_uuid}', 'delete'),
])
def test__broadcast_domain_exists(self):
response = fake.FAKE_GET_BROADCAST_DOMAIN

View File

@ -731,7 +731,8 @@ class NetAppFileStorageLibraryTestCase(test.TestCase):
fake.NETWORK_INFO)
self.library._client.create_vserver.assert_called_once_with(
vserver_name, fake.ROOT_VOLUME_AGGREGATE, fake.ROOT_VOLUME,
set(fake.AGGREGATES), fake.IPSPACE)
set(fake.AGGREGATES), fake.IPSPACE,
fake.SECURITY_CERT_DEFAULT_EXPIRE_DAYS)
self.library._get_api_client.assert_called_once_with(
vserver=vserver_name)
self.library._create_vserver_lifs.assert_called_once_with(

View File

@ -68,6 +68,8 @@ AGGR_POOL_NAME = 'manila_aggr_1'
FLEXGROUP_POOL_NAME = 'flexgroup_pool'
ROOT_AGGREGATES = ('root_aggr_1', 'root_aggr_2')
ROOT_VOLUME_AGGREGATE = 'manila1'
SECURITY_CERT_DEFAULT_EXPIRE_DAYS = 365
SECURITY_CERT_LARGE_EXPIRE_DAYS = 3652
ROOT_VOLUME = 'root'
CLUSTER_NODE = 'cluster1_01'
CLUSTER_NODES = ('cluster1_01', 'cluster1_02')

View File

@ -0,0 +1,8 @@
---
features:
- |
NetApp ONTAP driver now allows cloud operator to define security
certificate expire days for vserver. So instead of using vserver's default
security certificate with 365 expire days, cloud operator can ask backend
to create new security certificate with given expire days using config
option 'netapp_security_cert_expire_days'.