From dc78683291872b8828c10c816c6ed374606cdb6c Mon Sep 17 00:00:00 2001 From: Anna Khmelnitsky Date: Mon, 6 Mar 2017 07:10:30 -0800 Subject: [PATCH] NSXv3: Add certificate expiration alert A warning will be printed in log if cert expires in less than 30 days. In addition, fix refcount in cert provider and unit test. Change-Id: I8899e84c37d56602736b8fb0c1994ad04a5d5b14 --- vmware_nsx/plugins/nsx_v3/cert_utils.py | 5 ++ vmware_nsx/plugins/nsx_v3/utils.py | 61 ++++++++++++++----- .../plugins/nsxv3/resources/certificates.py | 4 +- .../tests/unit/nsx_v3/test_client_cert.py | 20 ++++-- 4 files changed, 66 insertions(+), 24 deletions(-) diff --git a/vmware_nsx/plugins/nsx_v3/cert_utils.py b/vmware_nsx/plugins/nsx_v3/cert_utils.py index a223321b04..4ed4fe1f12 100644 --- a/vmware_nsx/plugins/nsx_v3/cert_utils.py +++ b/vmware_nsx/plugins/nsx_v3/cert_utils.py @@ -31,6 +31,11 @@ NSX_OPENSTACK_IDENTITY = "com.vmware.nsx.openstack" _SECRET = None +def reset_secret(): + global _SECRET + _SECRET = None + + def generate_secret_from_password(password): m = hashlib.md5() m.update(password.encode('ascii')) diff --git a/vmware_nsx/plugins/nsx_v3/utils.py b/vmware_nsx/plugins/nsx_v3/utils.py index f8fa8ef5ef..7be0769ea9 100644 --- a/vmware_nsx/plugins/nsx_v3/utils.py +++ b/vmware_nsx/plugins/nsx_v3/utils.py @@ -12,20 +12,21 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +import os + from oslo_config import cfg from oslo_log import log as logging from neutron import version as n_version from neutron_lib import context as q_context +from vmware_nsx._i18n import _LE, _LW from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.plugins.nsx_v3 import cert_utils from vmware_nsxlib import v3 from vmware_nsxlib.v3 import client_cert from vmware_nsxlib.v3 import config -import os - NSX_NEUTRON_PLUGIN = 'NSX Neutron plugin' OS_NEUTRON_ID_SCOPE = 'os-neutron-id' @@ -38,38 +39,66 @@ class DbCertProvider(client_cert.ClientCertProvider): Since several connections may use same filename simultaneously, this class maintains refcount to write/delete the file only once """ + EXPIRATION_ALERT_DAYS = 30 # days prior to expiration + def __init__(self, filename): super(DbCertProvider, self).__init__(filename) self.refcount = 0 + def _check_expiration(self, expires_in_days): + if expires_in_days > self.EXPIRATION_ALERT_DAYS: + return + + if expires_in_days < 0: + LOG.error(_LE("Client certificate has expired %d days ago."), + expires_in_days * -1) + else: + LOG.warning(_LW("Client certificate expires in %d days. " + "Once expired, service will become unavailable."), + expires_in_days) + def __enter__(self): self.refcount += 1 if self.refcount > 1: - return + # The file was prepared for another connection + # and was not removed yet + return self - context = q_context.get_admin_context() - db_storage_driver = cert_utils.DbCertificateStorageDriver(context) - cert_manager = client_cert.ClientCertificateManager( - cert_utils.NSX_OPENSTACK_IDENTITY, None, db_storage_driver) - if not cert_manager.exists(): - msg = _("Unable to load from nsx-db") - raise nsx_exc.ClientCertificateException(err_msg=msg) + try: + context = q_context.get_admin_context() + db_storage_driver = cert_utils.DbCertificateStorageDriver(context) + with client_cert.ClientCertificateManager( + cert_utils.NSX_OPENSTACK_IDENTITY, + None, + db_storage_driver) as cert_manager: + if not cert_manager.exists(): + msg = _("Unable to load from nsx-db") + raise nsx_exc.ClientCertificateException(err_msg=msg) - if not os.path.exists(os.path.dirname(self._filename)): - if len(os.path.dirname(self._filename)) > 0: - os.makedirs(os.path.dirname(self._filename)) + if not os.path.exists(os.path.dirname(self._filename)): + if len(os.path.dirname(self._filename)) > 0: + os.makedirs(os.path.dirname(self._filename)) + cert_manager.export_pem(self._filename) + + expires_in_days = cert_manager.expires_in_days() + self._check_expiration(expires_in_days) + except Exception as e: + self._on_exit() + raise e - cert_manager.export_pem(self._filename) LOG.debug("Prepared client certificate file") return self - def __exit__(self, type, value, traceback): + def _on_exit(self): self.refcount -= 1 - if self.refcount == 0: + if self.refcount == 0 and os.path.isfile(self._filename): os.remove(self._filename) LOG.debug("Deleted client certificate file") + def __exit__(self, type, value, traceback): + self._on_exit() + def filename(self): return self._filename diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/certificates.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/certificates.py index 8e182d61c3..eb4102416c 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/certificates.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/certificates.py @@ -149,9 +149,9 @@ def show_cert(resource, event, trigger, **kwargs): cert_data = cert.get_subject() cert_data['alg'] = cert.get_signature_alg() cert_data['key_size'] = cert.get_key_size() - if expires_in_days > 0: + if expires_in_days >= 0: LOG.info(_LI("Client certificate is valid. " - "Expires on %(date)s (in %(days)d days)."), + "Expires on %(date)s UTC (in %(days)d days)."), {'date': expires_on, 'days': expires_in_days}) diff --git a/vmware_nsx/tests/unit/nsx_v3/test_client_cert.py b/vmware_nsx/tests/unit/nsx_v3/test_client_cert.py index 35fae95c5e..d20f7bc7f4 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_client_cert.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_client_cert.py @@ -81,11 +81,12 @@ class NsxV3iClientCertProviderTestCase(unittest.TestCase): cfg.CONF.set_override('nsx_client_cert_storage', storage_type, 'nsx_v3') - if cert_file: - cfg.CONF.set_override('nsx_client_cert_file', cert_file, 'nsx_v3') - if password: - cfg.CONF.set_override('nsx_client_cert_pk_password', - password, 'nsx_v3') + cfg.CONF.set_override('nsx_client_cert_file', cert_file, 'nsx_v3') + cfg.CONF.set_override('nsx_client_cert_pk_password', + password, 'nsx_v3') + + # pk password secret is cached - reset it for each test + cert_utils.reset_secret() self._provider = utils.get_client_cert_provider() @@ -126,7 +127,14 @@ class NsxV3iClientCertProviderTestCase(unittest.TestCase): self.assertRaises(nsx_exc.ClientCertificateException, self._provider.__enter__) - def x_test_db_provider_with_cert(self): + # now verify return to normal after failure + mock.patch( + "vmware_nsx.db.db.get_certificate", + return_value=(self.CERT, self.PKEY)).start() + + self.validate_db_provider(self.CERT + self.PKEY) + + def test_db_provider_with_cert(self): """Verify successful certificate load from storage""" self._init_config()