From ea77b5f857f131462e7fad732dc1c7ebab10f177 Mon Sep 17 00:00:00 2001 From: Abhishek Raut Date: Fri, 25 Sep 2015 15:32:44 -0700 Subject: [PATCH] [NSXv] Add SSL support for metadata service in NSX-V plugin Metadata service in the NSX-V plugin is handled by a Edge DHCP or router VM. Currently the traffic between nova and the metadata service is insecure. This patch adds the SSL support for metadata service which will make the connection secure. The certificate used for secure communication will be created on the VC under the edge scope. If user does not supply the certificate and private key for secure communication, a self signed certificate will be generated in the backend. This self signed certificate will last for a period of 10yrs. A certifcate with the given details will be created in the backend if such a configuration exists in nsx.ini Appropriate config is pushed for the loadbalancer with the protocol set to HTTPS if SSL is enabled for metadata service. DocImpact Change-Id: I5582cc1186ef4b8451f999b46e55bc2c684b1be3 --- devstack/lib/vmware_nsx_v | 3 + etc/nsx.ini | 15 ++++- vmware_nsx/common/config.py | 9 +++ vmware_nsx/common/nsxv_constants.py | 17 +++++ vmware_nsx/common/utils.py | 10 +++ vmware_nsx/plugins/nsx_v/md_proxy.py | 64 +++++++++++++++++-- .../nsx_v/vshield/nsxv_loadbalancer.py | 7 ++ vmware_nsx/plugins/nsx_v/vshield/vcns.py | 20 +++++- 8 files changed, 138 insertions(+), 7 deletions(-) diff --git a/devstack/lib/vmware_nsx_v b/devstack/lib/vmware_nsx_v index bed6a93f22..68a09fadc4 100644 --- a/devstack/lib/vmware_nsx_v +++ b/devstack/lib/vmware_nsx_v @@ -104,6 +104,9 @@ function neutron_plugin_configure_service { _nsxv_ini_set nova_metadata_port "$NSXV_NOVA_METADATA_PORT" _nsxv_ini_set nova_metadata_ips "$NSXV_NOVA_METADATA_IPS" _nsxv_ini_set metadata_shared_secret "$NSXV_METADATA_SHARED_SECRET" + _nsxv_ini_set metadata_insecure "$NSXV_METADATA_INSECURE" + _nsxv_ini_set metadata_nova_client_cert "$NSXV_METADATA_NOVA_CERT" + _nsxv_ini_set metadata_nova_client_priv_key "$NSXV_METADATA_NOVA_PRIV_KEY" _nsxv_ini_set edge_ha "$NSXV_EDGE_HA" _nsxv_ini_set exclusive_router_appliance_size "$NSXV_EXCLUSIVE_ROUTER_APPLIANCE_SIZE" } diff --git a/etc/nsx.ini b/etc/nsx.ini index 143c8a3e17..cc1756989c 100644 --- a/etc/nsx.ini +++ b/etc/nsx.ini @@ -71,10 +71,10 @@ # Specify a CA bundle file to use in verifying the NSXv server certificate. # ca_file = -# If true, the NSXv server certificate is not verified. If false, +# If True, the NSXv server certificate is not verified. If False, # then the default CA truststore is used for verification. This option # is ignored if "ca_file" is set. -# insecure = true +# insecure = True # (Required) Datacenter MoRef ID for Edge deployment # datacenter_moid = @@ -143,6 +143,17 @@ # (Optional) Shared secret to sign metadata requests # metadata_shared_secret = +# (Optional) If True, the end to end connection for metadata service is +# not verified. If False, the default CA truststore is used for verification. +# metadata_insecure = + +# (Optional) Client certificate to use when metadata connection is to be +# verified. If not provided, a self signed certificate will be used. +# metadata_nova_client_cert = + +# (Optional) Private key to use for client certificate +# metadata_nova_client_priv_key = + # (Optional) Indicates if Nsxv spoofguard component is used to implement # port-security feature. # spoofguard_enabled = True diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index fe38cb3bdc..a2a5af90cd 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -303,6 +303,15 @@ nsxv_opts = [ cfg.StrOpt('metadata_shared_secret', secret=True, help=_('Shared secret to sign metadata requests')), + cfg.BoolOpt('metadata_insecure', + default=True, + help=_('If True, the end to end connection for metadata ' + 'service is not verified. If False, the default CA ' + 'truststore is used for verification')), + cfg.StrOpt('metadata_nova_client_cert', + help=_('Client certificate for nova metadata api server')), + cfg.StrOpt('metadata_nova_client_priv_key', + help=_('Private key of client certificate')), cfg.BoolOpt('spoofguard_enabled', default=True, help=_("If True then plugin will use NSXV spoofguard " diff --git a/vmware_nsx/common/nsxv_constants.py b/vmware_nsx/common/nsxv_constants.py index 9243cdd2bd..1975893e37 100644 --- a/vmware_nsx/common/nsxv_constants.py +++ b/vmware_nsx/common/nsxv_constants.py @@ -35,3 +35,20 @@ INTERNAL_TENANT_ID = 'a1b2c3d4-e5f6-eeff-ffee-6f5e4d3c2b1a' # L2 gateway edge name prefix L2_GATEWAY_EDGE = 'L2 bridging' + +# LoadBalancer Certificate constants +#NOTE(abhiraut): Number of days specify the total number of days for which the +# the certificate will be active. This certificate will expire +# in 10 years. Once the backend API allows creation of certs +# which do not expire, the following constant should be removed. +CERT_NUMBER_OF_DAYS = 3650 +CSR_REQUEST = ("" + "CNmetadata.nsx.local" + "" + "OOrganization" + "OUUnit" + "LLocality" + "STState" + "CUS" + "RSA2048" + "") diff --git a/vmware_nsx/common/utils.py b/vmware_nsx/common/utils.py index a9872fdcbd..4ace5edf51 100644 --- a/vmware_nsx/common/utils.py +++ b/vmware_nsx/common/utils.py @@ -16,6 +16,7 @@ import hashlib from neutron.api.v2 import attributes +from neutron.i18n import _LE from neutron import version from oslo_config import cfg from oslo_log import log @@ -147,3 +148,12 @@ def dict_match(dict1, dict2): elif v1 != v2: return False return True + + +def read_file(path): + try: + with open(path) as file: + return file.read().strip() + except IOError as e: + LOG.error(_LE("Error while opening file " + "%(path)s: %(err)s"), {'path': path, 'err': str(e)}) diff --git a/vmware_nsx/plugins/nsx_v/md_proxy.py b/vmware_nsx/plugins/nsx_v/md_proxy.py index de31cd70e3..796f439665 100644 --- a/vmware_nsx/plugins/nsx_v/md_proxy.py +++ b/vmware_nsx/plugins/nsx_v/md_proxy.py @@ -28,16 +28,20 @@ from neutron.i18n import _LE from vmware_nsx.common import exceptions as nsxv_exc from vmware_nsx.common import locking from vmware_nsx.common import nsxv_constants +from vmware_nsx.common import utils from vmware_nsx.db import nsxv_db from vmware_nsx.plugins.nsx_v.vshield import ( nsxv_loadbalancer as nsxv_lb) from vmware_nsx.plugins.nsx_v.vshield.common import ( constants as vcns_const) from vmware_nsx.plugins.nsx_v.vshield import edge_utils +from vmware_nsx.services.lbaas.nsx_v import lbaas_common METADATA_VSE_NAME = 'MdSrv' METADATA_IP_ADDR = '169.254.169.254' METADATA_TCP_PORT = 80 +METADATA_HTTPS_PORT = 443 +METADATA_HTTPS_VIP_PORT = 8775 INTERNAL_SUBNET = '169.254.128.0/17' MAX_INIT_THREADS = 3 @@ -486,6 +490,39 @@ class NsxVMetadataProxyHandler: address_groups.append(address_group) return address_groups + def _create_ssl_cert(self, edge_id=None): + # Create a self signed certificate in the backend if both Cert details + # and private key are not supplied in nsx.ini + if (not cfg.CONF.nsxv.metadata_nova_client_cert and + not cfg.CONF.nsxv.metadata_nova_client_priv_key): + h = self.nsxv_plugin.nsx_v.vcns.create_csr(edge_id)[0] + # Extract the CSR ID from header + csr_id = lbaas_common.extract_resource_id(h['location']) + # Create a self signed certificate + cert = self.nsxv_plugin.nsx_v.vcns.create_csr_cert(csr_id)[1] + cert_id = cert['objectId'] + else: + # Raise an error if either the Cert path or the private key is not + # configured + error = None + if not cfg.CONF.nsxv.metadata_nova_client_cert: + error = _('Metadata certificate path not configured') + elif not cfg.CONF.nsxv.metadata_nova_client_priv_key: + error = _('Metadata client private key not configured') + if error: + raise nsxv_exc.NsxPluginException(err_msg=error) + pem_encoding = utils.read_file( + cfg.CONF.nsxv.metadata_nova_client_cert) + priv_key = utils.read_file( + cfg.CONF.nsxv.metadata_nova_client_priv_key) + request = { + 'pemEncoding': pem_encoding, + 'privateKey': priv_key} + cert = self.nsxv_plugin.nsx_v.vcns.upload_edge_certificate( + edge_id, request)[1] + cert_id = cert.get('certificates')[0]['objectId'] + return cert_id + def _setup_metadata_lb(self, rtr_id, vip, v_port, s_port, member_ips, proxy_lb=False, context=None): @@ -497,10 +534,26 @@ class NsxVMetadataProxyHandler: lb_obj = nsxv_lb.NsxvLoadbalancer() + protocol = 'HTTP' + ssl_pass_through = False + cert_id = None + # Set protocol to HTTPS with default port of 443 if metadata_insecure + # is set to False. + if not cfg.CONF.nsxv.metadata_insecure: + protocol = 'HTTPS' + if proxy_lb: + v_port = METADATA_HTTPS_VIP_PORT + else: + v_port = METADATA_HTTPS_PORT + # Create the certificate on the backend + cert_id = self._create_ssl_cert(edge_id) + ssl_pass_through = proxy_lb + mon_type = protocol if proxy_lb else 'tcp' # Create virtual server virt_srvr = nsxv_lb.NsxvLBVirtualServer( name=METADATA_VSE_NAME, ip_address=vip, + protocol=protocol, port=v_port) # For router Edge, we add X-LB-Proxy-ID header @@ -525,8 +578,11 @@ class NsxVMetadataProxyHandler: # XFF is inserted in router LBs app_profile = nsxv_lb.NsxvLBAppProfile( name='MDSrvProxy', - template='HTTP', - insert_xff=not proxy_lb) + template=protocol, + server_ssl_enabled=not cfg.CONF.nsxv.metadata_insecure, + ssl_pass_through=ssl_pass_through, + insert_xff=not proxy_lb, + client_ssl_cert=cert_id) virt_srvr.set_app_profile(app_profile) @@ -534,8 +590,8 @@ class NsxVMetadataProxyHandler: pool = nsxv_lb.NsxvLBPool( name='MDSrvPool') - monitor = nsxv_lb.NsxvLBMonitor( - name='MDSrvMon', mon_type='http' if proxy_lb else 'icmp') + monitor = nsxv_lb.NsxvLBMonitor(name='MDSrvMon', + mon_type=mon_type.lower()) pool.add_monitor(monitor) i = 0 diff --git a/vmware_nsx/plugins/nsx_v/vshield/nsxv_loadbalancer.py b/vmware_nsx/plugins/nsx_v/vshield/nsxv_loadbalancer.py index 62b7075133..15ad1f1d00 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/nsxv_loadbalancer.py +++ b/vmware_nsx/plugins/nsx_v/vshield/nsxv_loadbalancer.py @@ -235,6 +235,7 @@ class NsxvLBAppProfile(object): ssl_pass_through=False, template='TCP', insert_xff=False, + client_ssl_cert=None, persist=False, persist_method='cookie', persist_cookie_name='JSESSIONID', @@ -256,6 +257,12 @@ class NsxvLBAppProfile(object): self.payload['persistence']['cookieMode'] = persist_cookie_mode self.payload['persistence']['cookieName'] = persist_cookie_name + if client_ssl_cert: + self.payload['clientSsl'] = { + 'clientAuth': 'ignore', + 'serviceCertificate': [client_ssl_cert] + } + def set_persistence( self, persist=False, diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns.py b/vmware_nsx/plugins/nsx_v/vshield/vcns.py index 541e619bf4..efa724059e 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/vcns.py +++ b/vmware_nsx/plugins/nsx_v/vshield/vcns.py @@ -21,6 +21,7 @@ import retrying import six import xml.etree.ElementTree as et +from vmware_nsx.common import nsxv_constants from vmware_nsx.plugins.nsx_v.vshield.common import exceptions from vmware_nsx.plugins.nsx_v.vshield.common import VcnsApiClient @@ -42,6 +43,7 @@ SECURITYGROUP_PREFIX = '/api/2.0/services/securitygroup' VDN_PREFIX = '/api/2.0/vdn' SERVICES_PREFIX = '/api/2.0/services' SPOOFGUARD_PREFIX = '/api/4.0/services/spoofguard' +TRUSTSTORE_PREFIX = '%s/%s' % (SERVICES_PREFIX, 'truststore') #LbaaS Constants LOADBALANCER_SERVICE = "loadbalancer/config" @@ -65,6 +67,10 @@ SYSCTL_SERVICE = 'systemcontrol/config' # L2 gateway constants BRIDGE = "bridging/config" +# Self Signed Certificate constants +CSR = "csr" +CERTIFICATE = "certificate" + def retry_upon_exception(exc, delay=500, max_delay=2000, max_attempts=cfg.CONF.nsxv.retries): @@ -810,5 +816,17 @@ class Vcns(object): def upload_edge_certificate(self, edge_id, request): """Creates a certificate on the specified Edge appliance.""" - uri = '/api/2.0/services/truststore/certificate/%s' % edge_id + uri = '%s/%s/%s' % (TRUSTSTORE_PREFIX, CERTIFICATE, edge_id) return self.do_request(HTTP_POST, uri, request, decode=True) + + def create_csr(self, edge_id, request=nsxv_constants.CSR_REQUEST): + """Create a CSR on the specified Edge appliance.""" + uri = '%s/%s/%s' % (TRUSTSTORE_PREFIX, CSR, edge_id) + return self.do_request(HTTP_POST, uri, request, format='xml', + decode=False) + + def create_csr_cert(self, csr_id): + """Create a CSR self signed cert on the specified Edge appliance.""" + uri = '%s/%s/%s?noOfDays=%s' % (TRUSTSTORE_PREFIX, CSR, csr_id, + nsxv_constants.CERT_NUMBER_OF_DAYS) + return self.do_request(HTTP_PUT, uri)