Policy plugin: Add devstack/admin-utils for client auth

Adding devstack support for policy plugin with certificate and the certificate
admin utilis which are needed for the devstack support.

Change-Id: I5c9d23c7f0a83cbf4cb71fed4da488bafa230be4
This commit is contained in:
Adit Sarfaty 2018-12-06 13:54:56 +02:00
parent 944402c8c5
commit 57776776d4
8 changed files with 389 additions and 227 deletions

View File

@ -201,6 +201,13 @@ function nsxp_configure_service {
_nsxp_ini_set retries $NSX_RETRIES
_nsxp_ini_set insecure $NSX_INSECURE
_nsxp_ini_set ca_file $NSX_CA_FILE
if [[ "$NSX_USE_CLIENT_CERT_AUTH" == "True" ]]; then
_nsxp_ini_set nsx_use_client_auth "True"
_nsxp_ini_set nsx_client_cert_file "$CLIENT_CERT_FILE"
_nsxp_ini_set nsx_client_cert_storage "nsx-db"
_nsxp_ini_set nsx_client_cert_pk_password "openstack"
fi
}
function init_vmware_nsx_p {

View File

@ -597,6 +597,29 @@ NSX Policy Plugin
nsxadmin -r routers -o list
Client Certificate
~~~~~~~~~~~~~~~~~~
- Generate new client certificate (this command will delete previous certificate if exists)::
nsxadmin -r certificate -o generate [--property username=<username> --property password=<password> --property key-size=<size> --property sig-alg=<alg> --property valid-days=<days> --property country=<country> --property state=<state> --property org=<organization> --property unit=<unit> --property host=<hostname>]
- Delete client certificate::
nsxadmin -r certificate -o clean
- Show client certificate details::
nsxadmin -r certificate -o show
- Import external certificate to NSX::
nsxadmin -r certificate -o import [--property username=<username> --property password=<password> --property filename=<cert filename>]
- List certificates associated with openstack principal identity in NSX::
nsxadmin -r certificate -o nsx-list
Upgrade Steps (NSX-T Version 1.0.0 to Version 1.1.0)
----------------------------------------------------

View File

@ -137,26 +137,29 @@ def get_client_cert_provider(conf_path=cfg.CONF.nsx_v3):
return DbCertProvider
def get_nsxlib_wrapper(nsx_username=None, nsx_password=None, basic_auth=False):
def get_nsxlib_wrapper(nsx_username=None, nsx_password=None, basic_auth=False,
plugin_conf=None):
client_cert_provider = None
if not basic_auth:
# if basic auth requested, dont use cert file even if provided
client_cert_provider = get_client_cert_provider()
if not plugin_conf:
plugin_conf = cfg.CONF.nsx_v3
nsxlib_config = config.NsxLibConfig(
username=nsx_username or cfg.CONF.nsx_v3.nsx_api_user,
password=nsx_password or cfg.CONF.nsx_v3.nsx_api_password,
username=nsx_username or plugin_conf.nsx_api_user,
password=nsx_password or plugin_conf.nsx_api_password,
client_cert_provider=client_cert_provider,
retries=cfg.CONF.nsx_v3.http_retries,
insecure=cfg.CONF.nsx_v3.insecure,
ca_file=cfg.CONF.nsx_v3.ca_file,
concurrent_connections=cfg.CONF.nsx_v3.concurrent_connections,
http_timeout=cfg.CONF.nsx_v3.http_timeout,
http_read_timeout=cfg.CONF.nsx_v3.http_read_timeout,
conn_idle_timeout=cfg.CONF.nsx_v3.conn_idle_timeout,
retries=plugin_conf.http_retries,
insecure=plugin_conf.insecure,
ca_file=plugin_conf.ca_file,
concurrent_connections=plugin_conf.concurrent_connections,
http_timeout=plugin_conf.http_timeout,
http_read_timeout=plugin_conf.http_read_timeout,
conn_idle_timeout=plugin_conf.conn_idle_timeout,
http_provider=None,
max_attempts=cfg.CONF.nsx_v3.retries,
nsx_api_managers=cfg.CONF.nsx_v3.nsx_api_managers,
max_attempts=plugin_conf.retries,
nsx_api_managers=plugin_conf.nsx_api_managers,
plugin_scope=OS_NEUTRON_ID_SCOPE,
plugin_tag=NSX_NEUTRON_PLUGIN,
plugin_ver=n_version.version_info.release_string(),

View File

@ -0,0 +1,250 @@
# Copyright 2018 VMware, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from neutron_lib import context
from vmware_nsx.plugins.nsx_v3 import cert_utils
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils
from vmware_nsxlib.v3 import client_cert
from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3 import trust_management
LOG = logging.getLogger(__name__)
CERT_DEFAULTS = {'key-size': 2048,
'sig-alg': 'sha256',
'valid-days': 3650,
'country': 'US',
'state': 'California',
'org': 'default org',
'unit': 'default unit',
'host': 'defaulthost.org'}
def get_nsx_trust_management(plugin_conf, **kwargs):
username, password = None, None
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
username = properties.get('user')
password = properties.get('password')
nsx_client = utils.get_nsxv3_client(username, password, True,
plugin_conf=plugin_conf)
nsx_trust = trust_management.NsxLibTrustManagement(nsx_client, {})
return nsx_trust
def get_certificate_manager(plugin_conf, **kwargs):
storage_driver_type = plugin_conf.nsx_client_cert_storage.lower()
LOG.info("Certificate storage is %s", storage_driver_type)
if storage_driver_type == 'nsx-db':
storage_driver = cert_utils.DbCertificateStorageDriver(
context.get_admin_context())
elif storage_driver_type == 'none':
storage_driver = cert_utils.DummyCertificateStorageDriver()
# TODO(annak) - add support for barbican storage driver
return client_cert.ClientCertificateManager(
cert_utils.NSX_OPENSTACK_IDENTITY,
get_nsx_trust_management(plugin_conf, **kwargs),
storage_driver)
def verify_client_cert_on(plugin_conf):
if plugin_conf.nsx_use_client_auth:
return True
LOG.info("Operation not applicable since client authentication "
"is disabled")
return False
def generate_cert(plugin_conf, **kwargs):
"""Generate self signed client certificate and private key
"""
if not verify_client_cert_on(plugin_conf):
return
if plugin_conf.nsx_client_cert_storage.lower() == "none":
LOG.info("Generate operation is not supported "
"with storage type 'none'")
return
# update cert defaults based on user input
properties = CERT_DEFAULTS.copy()
if kwargs.get('property'):
properties.update(admin_utils.parse_multi_keyval_opt(
kwargs['property']))
try:
prop = 'key-size'
key_size = int(properties.get(prop))
prop = 'valid-days'
valid_for_days = int(properties.get(prop))
except ValueError:
LOG.info("%s property must be a number", prop)
return
signature_alg = properties.get('sig-alg')
subject = {}
subject[client_cert.CERT_SUBJECT_COUNTRY] = properties.get('country')
subject[client_cert.CERT_SUBJECT_STATE] = properties.get('state')
subject[client_cert.CERT_SUBJECT_ORG] = properties.get('org')
subject[client_cert.CERT_SUBJECT_UNIT] = properties.get('org')
subject[client_cert.CERT_SUBJECT_HOST] = properties.get('host')
regenerate = False
with get_certificate_manager(plugin_conf, **kwargs) as cert:
if cert.exists():
LOG.info("Deleting existing certificate")
# Need to delete cert first
cert.delete()
regenerate = True
try:
cert.generate(subject, key_size, valid_for_days, signature_alg)
except exceptions.NsxLibInvalidInput as e:
LOG.info(e)
return
LOG.info("Client certificate generated successfully")
if not regenerate:
# No certificate existed, so client authentication service was likely
# changed to true just now. The user must restart neutron to avoid
# failures.
LOG.info("Please restart neutron service")
def delete_cert(plugin_conf, **kwargs):
"""Delete client certificate and private key """
if not verify_client_cert_on(plugin_conf):
return
with get_certificate_manager(plugin_conf, **kwargs) as cert:
if plugin_conf.nsx_client_cert_storage.lower() == "none":
filename = get_cert_filename(plugin_conf, **kwargs)
if not filename:
LOG.info("Please specify file containing the certificate "
"using filename property")
return
cert.delete_pem(filename)
else:
if not cert.exists():
LOG.info("Nothing to clean")
return
cert.delete()
LOG.info("Client certificate deleted successfully")
def show_cert(plugin_conf, **kwargs):
"""Show client certificate details """
if not verify_client_cert_on(plugin_conf):
return
with get_certificate_manager(plugin_conf, **kwargs) as cert:
if cert.exists():
cert_pem, key_pem = cert.get_pem()
expires_on = cert.expires_on()
expires_in_days = cert.expires_in_days()
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:
LOG.info("Client certificate is valid. "
"Expires on %(date)s UTC (in %(days)d days).",
{'date': expires_on,
'days': expires_in_days})
else:
LOG.info("Client certificate expired on %s.", expires_on)
LOG.info("Key Size %(key_size)s, "
"Signature Algorithm %(alg)s\n"
"Subject: Country %(country)s, State %(state)s, "
"Organization %(organization)s, Unit %(unit)s, "
"Common Name %(hostname)s", cert_data)
LOG.info(cert_pem)
else:
LOG.info("Client certificate is not registered "
"in storage")
def get_cert_filename(plugin_conf, **kwargs):
filename = plugin_conf.nsx_client_cert_file
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
filename = properties.get('filename', filename)
if not filename:
LOG.info("Please specify file containing the certificate "
"using filename property")
return filename
def import_cert(plugin_conf, **kwargs):
"""Import client certificate that was generated externally"""
if not verify_client_cert_on(plugin_conf):
return
if plugin_conf.nsx_client_cert_storage.lower() != "none":
LOG.info("Import operation is supported "
"with storage type 'none' only")
return
with get_certificate_manager(plugin_conf, **kwargs) as cert:
if cert.exists():
LOG.info("Deleting existing certificate")
cert.delete()
filename = get_cert_filename(plugin_conf, **kwargs)
if not filename:
return
cert.import_pem(filename)
LOG.info("Client certificate imported successfully")
def show_nsx_certs(plugin_conf, **kwargs):
"""Show client certificates associated with openstack identity in NSX"""
# Note - this operation is supported even if the feature is disabled
nsx_trust = get_nsx_trust_management(plugin_conf, **kwargs)
ids = nsx_trust.get_identities(cert_utils.NSX_OPENSTACK_IDENTITY)
if not ids:
LOG.info("Principal identity %s not found",
cert_utils.NSX_OPENSTACK_IDENTITY)
return
LOG.info("Certificate(s) associated with principal identity %s\n",
cert_utils.NSX_OPENSTACK_IDENTITY)
cert = None
for identity in ids:
if 'certificate_id' in identity:
cert = nsx_trust.get_cert(identity['certificate_id'])
LOG.info(cert['pem_encoded'])
if not cert:
LOG.info("No certificates found")

View File

@ -0,0 +1,73 @@
# Copyright 2018 VMware, Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from vmware_nsx.shell.admin.plugins.common import constants
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.common import v3_common_cert
from vmware_nsx.shell import resources as shell
from neutron_lib.callbacks import registry
from oslo_config import cfg
@admin_utils.output_header
def generate_cert(resource, event, trigger, **kwargs):
"""Generate self signed client certificate and private key
"""
return v3_common_cert.generate_cert(cfg.CONF.nsx_p, **kwargs)
@admin_utils.output_header
def delete_cert(resource, event, trigger, **kwargs):
"""Delete client certificate and private key """
return v3_common_cert.delete_cert(cfg.CONF.nsx_p, **kwargs)
@admin_utils.output_header
def show_cert(resource, event, trigger, **kwargs):
"""Show client certificate details """
return v3_common_cert.show_cert(cfg.CONF.nsx_p, **kwargs)
@admin_utils.output_header
def import_cert(resource, event, trigger, **kwargs):
"""Import client certificate that was generated externally"""
return v3_common_cert.import_cert(cfg.CONF.nsx_p, **kwargs)
@admin_utils.output_header
def show_nsx_certs(resource, event, trigger, **kwargs):
"""Show client certificates associated with openstack identity in NSX"""
return v3_common_cert.show_nsx_certs(cfg.CONF.nsx_p, **kwargs)
registry.subscribe(generate_cert,
constants.CERTIFICATE,
shell.Operations.GENERATE.value)
registry.subscribe(show_cert,
constants.CERTIFICATE,
shell.Operations.SHOW.value)
registry.subscribe(delete_cert,
constants.CERTIFICATE,
shell.Operations.CLEAN.value)
registry.subscribe(import_cert,
constants.CERTIFICATE,
shell.Operations.IMPORT.value)
registry.subscribe(show_nsx_certs,
constants.CERTIFICATE,
shell.Operations.NSX_LIST.value)

View File

@ -13,248 +13,44 @@
# under the License.
from oslo_log import log as logging
from vmware_nsx.plugins.nsx_v3 import cert_utils
from vmware_nsx.shell.admin.plugins.common import constants
from vmware_nsx.shell.admin.plugins.common import utils as admin_utils
from vmware_nsx.shell.admin.plugins.nsxv3.resources import utils
from vmware_nsx.shell.admin.plugins.common import v3_common_cert
from vmware_nsx.shell import resources as shell
from vmware_nsxlib.v3 import client_cert
from vmware_nsxlib.v3 import exceptions
from vmware_nsxlib.v3 import trust_management
from neutron_lib.callbacks import registry
from neutron_lib import context
from oslo_config import cfg
LOG = logging.getLogger(__name__)
CERT_DEFAULTS = {'key-size': 2048,
'sig-alg': 'sha256',
'valid-days': 3650,
'country': 'US',
'state': 'California',
'org': 'default org',
'unit': 'default unit',
'host': 'defaulthost.org'}
def get_nsx_trust_management(**kwargs):
username, password = None, None
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
username = properties.get('user')
password = properties.get('password')
nsx_client = utils.get_nsxv3_client(username, password, True)
nsx_trust = trust_management.NsxLibTrustManagement(nsx_client, {})
return nsx_trust
def get_certificate_manager(**kwargs):
storage_driver_type = cfg.CONF.nsx_v3.nsx_client_cert_storage.lower()
LOG.info("Certificate storage is %s", storage_driver_type)
if storage_driver_type == 'nsx-db':
storage_driver = cert_utils.DbCertificateStorageDriver(
context.get_admin_context())
elif storage_driver_type == 'none':
storage_driver = cert_utils.DummyCertificateStorageDriver()
# TODO(annak) - add support for barbican storage driver
return client_cert.ClientCertificateManager(
cert_utils.NSX_OPENSTACK_IDENTITY,
get_nsx_trust_management(**kwargs),
storage_driver)
def verify_client_cert_on():
if cfg.CONF.nsx_v3.nsx_use_client_auth:
return True
LOG.info("Operation not applicable since client authentication "
"is disabled")
return False
@admin_utils.output_header
def generate_cert(resource, event, trigger, **kwargs):
"""Generate self signed client certificate and private key
"""
if not verify_client_cert_on():
return
if cfg.CONF.nsx_v3.nsx_client_cert_storage.lower() == "none":
LOG.info("Generate operation is not supported "
"with storage type 'none'")
return
# update cert defaults based on user input
properties = CERT_DEFAULTS.copy()
if kwargs.get('property'):
properties.update(admin_utils.parse_multi_keyval_opt(
kwargs['property']))
try:
prop = 'key-size'
key_size = int(properties.get(prop))
prop = 'valid-days'
valid_for_days = int(properties.get(prop))
except ValueError:
LOG.info("%s property must be a number", prop)
return
signature_alg = properties.get('sig-alg')
subject = {}
subject[client_cert.CERT_SUBJECT_COUNTRY] = properties.get('country')
subject[client_cert.CERT_SUBJECT_STATE] = properties.get('state')
subject[client_cert.CERT_SUBJECT_ORG] = properties.get('org')
subject[client_cert.CERT_SUBJECT_UNIT] = properties.get('org')
subject[client_cert.CERT_SUBJECT_HOST] = properties.get('host')
regenerate = False
with get_certificate_manager(**kwargs) as cert:
if cert.exists():
LOG.info("Deleting existing certificate")
# Need to delete cert first
cert.delete()
regenerate = True
try:
cert.generate(subject, key_size, valid_for_days, signature_alg)
except exceptions.NsxLibInvalidInput as e:
LOG.info(e)
return
LOG.info("Client certificate generated successfully")
if not regenerate:
# No certificate existed, so client authentication service was likely
# changed to true just now. The user must restart neutron to avoid
# failures.
LOG.info("Please restart neutron service")
return v3_common_cert.generate_cert(cfg.CONF.nsx_v3, **kwargs)
@admin_utils.output_header
def delete_cert(resource, event, trigger, **kwargs):
"""Delete client certificate and private key """
if not verify_client_cert_on():
return
with get_certificate_manager(**kwargs) as cert:
if cfg.CONF.nsx_v3.nsx_client_cert_storage.lower() == "none":
filename = get_cert_filename(**kwargs)
if not filename:
LOG.info("Please specify file containing the certificate "
"using filename property")
return
cert.delete_pem(filename)
else:
if not cert.exists():
LOG.info("Nothing to clean")
return
cert.delete()
LOG.info("Client certificate deleted successfully")
return v3_common_cert.delete_cert(cfg.CONF.nsx_v3, **kwargs)
@admin_utils.output_header
def show_cert(resource, event, trigger, **kwargs):
"""Show client certificate details """
if not verify_client_cert_on():
return
with get_certificate_manager(**kwargs) as cert:
if cert.exists():
cert_pem, key_pem = cert.get_pem()
expires_on = cert.expires_on()
expires_in_days = cert.expires_in_days()
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:
LOG.info("Client certificate is valid. "
"Expires on %(date)s UTC (in %(days)d days).",
{'date': expires_on,
'days': expires_in_days})
else:
LOG.info("Client certificate expired on %s.", expires_on)
LOG.info("Key Size %(key_size)s, "
"Signature Algorithm %(alg)s\n"
"Subject: Country %(country)s, State %(state)s, "
"Organization %(organization)s, Unit %(unit)s, "
"Common Name %(hostname)s", cert_data)
LOG.info(cert_pem)
else:
LOG.info("Client certificate is not registered "
"in storage")
def get_cert_filename(**kwargs):
filename = cfg.CONF.nsx_v3.nsx_client_cert_file
if kwargs.get('property'):
properties = admin_utils.parse_multi_keyval_opt(kwargs['property'])
filename = properties.get('filename', filename)
if not filename:
LOG.info("Please specify file containing the certificate "
"using filename property")
return filename
return v3_common_cert.show_cert(cfg.CONF.nsx_v3, **kwargs)
@admin_utils.output_header
def import_cert(resource, event, trigger, **kwargs):
"""Import client certificate that was generated externally"""
if not verify_client_cert_on():
return
if cfg.CONF.nsx_v3.nsx_client_cert_storage.lower() != "none":
LOG.info("Import operation is supported "
"with storage type 'none' only")
return
with get_certificate_manager(**kwargs) as cert:
if cert.exists():
LOG.info("Deleting existing certificate")
cert.delete()
filename = get_cert_filename(**kwargs)
if not filename:
return
cert.import_pem(filename)
LOG.info("Client certificate imported successfully")
return v3_common_cert.import_cert(cfg.CONF.nsx_v3, **kwargs)
@admin_utils.output_header
def show_nsx_certs(resource, event, trigger, **kwargs):
"""Show client certificates associated with openstack identity in NSX"""
# Note - this operation is supported even if the feature is disabled
nsx_trust = get_nsx_trust_management(**kwargs)
ids = nsx_trust.get_identities(cert_utils.NSX_OPENSTACK_IDENTITY)
if not ids:
LOG.info("Principal identity %s not found",
cert_utils.NSX_OPENSTACK_IDENTITY)
return
LOG.info("Certificate(s) associated with principal identity %s\n",
cert_utils.NSX_OPENSTACK_IDENTITY)
cert = None
for identity in ids:
if 'certificate_id' in identity:
cert = nsx_trust.get_cert(identity['certificate_id'])
LOG.info(cert['pem_encoded'])
if not cert:
LOG.info("No certificates found")
return v3_common_cert.show_nsx_certs(cfg.CONF.nsx_v3, **kwargs)
registry.subscribe(generate_cert,

View File

@ -44,24 +44,28 @@ _NSXLIB = None
def get_nsxv3_client(nsx_username=None, nsx_password=None,
use_basic_auth=False):
use_basic_auth=False,
plugin_conf=None):
return get_connected_nsxlib(nsx_username,
nsx_password,
use_basic_auth).client
use_basic_auth,
plugin_conf).client
def get_connected_nsxlib(nsx_username=None, nsx_password=None,
use_basic_auth=False):
use_basic_auth=False,
plugin_conf=None):
global _NSXLIB
# for non-default agruments, initiate new lib
if nsx_username or use_basic_auth:
return v3_utils.get_nsxlib_wrapper(nsx_username,
nsx_password,
use_basic_auth)
use_basic_auth,
plugin_conf)
if _NSXLIB is None:
_NSXLIB = v3_utils.get_nsxlib_wrapper()
_NSXLIB = v3_utils.get_nsxlib_wrapper(plugin_conf=plugin_conf)
return _NSXLIB

View File

@ -248,6 +248,12 @@ nsxp_resources = {
[Operations.LIST.value]),
constants.ROUTERS: Resource(constants.ROUTERS,
[Operations.LIST.value]),
constants.CERTIFICATE: Resource(constants.CERTIFICATE,
[Operations.GENERATE.value,
Operations.SHOW.value,
Operations.CLEAN.value,
Operations.IMPORT.value,
Operations.NSX_LIST.value]),
}
nsxv3_resources_names = list(nsxv3_resources.keys())