[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
This commit is contained in:
Abhishek Raut 2015-09-25 15:32:44 -07:00
parent bd4a4ef306
commit ea77b5f857
8 changed files with 138 additions and 7 deletions

View File

@ -104,6 +104,9 @@ function neutron_plugin_configure_service {
_nsxv_ini_set nova_metadata_port "$NSXV_NOVA_METADATA_PORT" _nsxv_ini_set nova_metadata_port "$NSXV_NOVA_METADATA_PORT"
_nsxv_ini_set nova_metadata_ips "$NSXV_NOVA_METADATA_IPS" _nsxv_ini_set nova_metadata_ips "$NSXV_NOVA_METADATA_IPS"
_nsxv_ini_set metadata_shared_secret "$NSXV_METADATA_SHARED_SECRET" _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 edge_ha "$NSXV_EDGE_HA"
_nsxv_ini_set exclusive_router_appliance_size "$NSXV_EXCLUSIVE_ROUTER_APPLIANCE_SIZE" _nsxv_ini_set exclusive_router_appliance_size "$NSXV_EXCLUSIVE_ROUTER_APPLIANCE_SIZE"
} }

View File

@ -71,10 +71,10 @@
# Specify a CA bundle file to use in verifying the NSXv server certificate. # Specify a CA bundle file to use in verifying the NSXv server certificate.
# ca_file = # 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 # then the default CA truststore is used for verification. This option
# is ignored if "ca_file" is set. # is ignored if "ca_file" is set.
# insecure = true # insecure = True
# (Required) Datacenter MoRef ID for Edge deployment # (Required) Datacenter MoRef ID for Edge deployment
# datacenter_moid = # datacenter_moid =
@ -143,6 +143,17 @@
# (Optional) Shared secret to sign metadata requests # (Optional) Shared secret to sign metadata requests
# metadata_shared_secret = # 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 # (Optional) Indicates if Nsxv spoofguard component is used to implement
# port-security feature. # port-security feature.
# spoofguard_enabled = True # spoofguard_enabled = True

View File

@ -303,6 +303,15 @@ nsxv_opts = [
cfg.StrOpt('metadata_shared_secret', cfg.StrOpt('metadata_shared_secret',
secret=True, secret=True,
help=_('Shared secret to sign metadata requests')), 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', cfg.BoolOpt('spoofguard_enabled',
default=True, default=True,
help=_("If True then plugin will use NSXV spoofguard " help=_("If True then plugin will use NSXV spoofguard "

View File

@ -35,3 +35,20 @@ INTERNAL_TENANT_ID = 'a1b2c3d4-e5f6-eeff-ffee-6f5e4d3c2b1a'
# L2 gateway edge name prefix # L2 gateway edge name prefix
L2_GATEWAY_EDGE = 'L2 bridging' 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 = ("<csr><subject>"
"<attribute><key>CN</key><value>metadata.nsx.local</value>"
"</attribute>"
"<attribute><key>O</key><value>Organization</value></attribute>"
"<attribute><key>OU</key><value>Unit</value></attribute>"
"<attribute><key>L</key><value>Locality</value></attribute>"
"<attribute><key>ST</key><value>State</value></attribute>"
"<attribute><key>C</key><value>US</value></attribute>"
"</subject><algorithm>RSA</algorithm><keySize>2048</keySize>"
"</csr>")

View File

@ -16,6 +16,7 @@
import hashlib import hashlib
from neutron.api.v2 import attributes from neutron.api.v2 import attributes
from neutron.i18n import _LE
from neutron import version from neutron import version
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log from oslo_log import log
@ -147,3 +148,12 @@ def dict_match(dict1, dict2):
elif v1 != v2: elif v1 != v2:
return False return False
return True 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)})

View File

@ -28,16 +28,20 @@ from neutron.i18n import _LE
from vmware_nsx.common import exceptions as nsxv_exc from vmware_nsx.common import exceptions as nsxv_exc
from vmware_nsx.common import locking from vmware_nsx.common import locking
from vmware_nsx.common import nsxv_constants from vmware_nsx.common import nsxv_constants
from vmware_nsx.common import utils
from vmware_nsx.db import nsxv_db from vmware_nsx.db import nsxv_db
from vmware_nsx.plugins.nsx_v.vshield import ( from vmware_nsx.plugins.nsx_v.vshield import (
nsxv_loadbalancer as nsxv_lb) nsxv_loadbalancer as nsxv_lb)
from vmware_nsx.plugins.nsx_v.vshield.common import ( from vmware_nsx.plugins.nsx_v.vshield.common import (
constants as vcns_const) constants as vcns_const)
from vmware_nsx.plugins.nsx_v.vshield import edge_utils 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_VSE_NAME = 'MdSrv'
METADATA_IP_ADDR = '169.254.169.254' METADATA_IP_ADDR = '169.254.169.254'
METADATA_TCP_PORT = 80 METADATA_TCP_PORT = 80
METADATA_HTTPS_PORT = 443
METADATA_HTTPS_VIP_PORT = 8775
INTERNAL_SUBNET = '169.254.128.0/17' INTERNAL_SUBNET = '169.254.128.0/17'
MAX_INIT_THREADS = 3 MAX_INIT_THREADS = 3
@ -486,6 +490,39 @@ class NsxVMetadataProxyHandler:
address_groups.append(address_group) address_groups.append(address_group)
return address_groups 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, def _setup_metadata_lb(self, rtr_id, vip, v_port, s_port, member_ips,
proxy_lb=False, context=None): proxy_lb=False, context=None):
@ -497,10 +534,26 @@ class NsxVMetadataProxyHandler:
lb_obj = nsxv_lb.NsxvLoadbalancer() 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 # Create virtual server
virt_srvr = nsxv_lb.NsxvLBVirtualServer( virt_srvr = nsxv_lb.NsxvLBVirtualServer(
name=METADATA_VSE_NAME, name=METADATA_VSE_NAME,
ip_address=vip, ip_address=vip,
protocol=protocol,
port=v_port) port=v_port)
# For router Edge, we add X-LB-Proxy-ID header # For router Edge, we add X-LB-Proxy-ID header
@ -525,8 +578,11 @@ class NsxVMetadataProxyHandler:
# XFF is inserted in router LBs # XFF is inserted in router LBs
app_profile = nsxv_lb.NsxvLBAppProfile( app_profile = nsxv_lb.NsxvLBAppProfile(
name='MDSrvProxy', name='MDSrvProxy',
template='HTTP', template=protocol,
insert_xff=not proxy_lb) 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) virt_srvr.set_app_profile(app_profile)
@ -534,8 +590,8 @@ class NsxVMetadataProxyHandler:
pool = nsxv_lb.NsxvLBPool( pool = nsxv_lb.NsxvLBPool(
name='MDSrvPool') name='MDSrvPool')
monitor = nsxv_lb.NsxvLBMonitor( monitor = nsxv_lb.NsxvLBMonitor(name='MDSrvMon',
name='MDSrvMon', mon_type='http' if proxy_lb else 'icmp') mon_type=mon_type.lower())
pool.add_monitor(monitor) pool.add_monitor(monitor)
i = 0 i = 0

View File

@ -235,6 +235,7 @@ class NsxvLBAppProfile(object):
ssl_pass_through=False, ssl_pass_through=False,
template='TCP', template='TCP',
insert_xff=False, insert_xff=False,
client_ssl_cert=None,
persist=False, persist=False,
persist_method='cookie', persist_method='cookie',
persist_cookie_name='JSESSIONID', persist_cookie_name='JSESSIONID',
@ -256,6 +257,12 @@ class NsxvLBAppProfile(object):
self.payload['persistence']['cookieMode'] = persist_cookie_mode self.payload['persistence']['cookieMode'] = persist_cookie_mode
self.payload['persistence']['cookieName'] = persist_cookie_name self.payload['persistence']['cookieName'] = persist_cookie_name
if client_ssl_cert:
self.payload['clientSsl'] = {
'clientAuth': 'ignore',
'serviceCertificate': [client_ssl_cert]
}
def set_persistence( def set_persistence(
self, self,
persist=False, persist=False,

View File

@ -21,6 +21,7 @@ import retrying
import six import six
import xml.etree.ElementTree as et 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 exceptions
from vmware_nsx.plugins.nsx_v.vshield.common import VcnsApiClient 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' VDN_PREFIX = '/api/2.0/vdn'
SERVICES_PREFIX = '/api/2.0/services' SERVICES_PREFIX = '/api/2.0/services'
SPOOFGUARD_PREFIX = '/api/4.0/services/spoofguard' SPOOFGUARD_PREFIX = '/api/4.0/services/spoofguard'
TRUSTSTORE_PREFIX = '%s/%s' % (SERVICES_PREFIX, 'truststore')
#LbaaS Constants #LbaaS Constants
LOADBALANCER_SERVICE = "loadbalancer/config" LOADBALANCER_SERVICE = "loadbalancer/config"
@ -65,6 +67,10 @@ SYSCTL_SERVICE = 'systemcontrol/config'
# L2 gateway constants # L2 gateway constants
BRIDGE = "bridging/config" BRIDGE = "bridging/config"
# Self Signed Certificate constants
CSR = "csr"
CERTIFICATE = "certificate"
def retry_upon_exception(exc, delay=500, max_delay=2000, def retry_upon_exception(exc, delay=500, max_delay=2000,
max_attempts=cfg.CONF.nsxv.retries): max_attempts=cfg.CONF.nsxv.retries):
@ -810,5 +816,17 @@ class Vcns(object):
def upload_edge_certificate(self, edge_id, request): def upload_edge_certificate(self, edge_id, request):
"""Creates a certificate on the specified Edge appliance.""" """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) 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)