From 4363a9a11bea40da13074ba1a0b1779a4be8cd39 Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Tue, 30 Oct 2012 14:53:47 -0400 Subject: add metadata support for overlapping networks implements blueprint metadata-overlapping-networks This is the Nova side of the changes required to support Metadata service on overlapping Quantum networks. This change is BACKWARDS COMPATIBLE and does not require any new configuration for Nova only deployments. Change-Id: I4dacd565e43e136e3faeb95184a4a2f7188415cd --- nova/api/metadata/base.py | 9 +++- nova/api/metadata/handler.py | 122 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 117 insertions(+), 14 deletions(-) (limited to 'nova/api') diff --git a/nova/api/metadata/base.py b/nova/api/metadata/base.py index 8e5bcb370..b271662b8 100644 --- a/nova/api/metadata/base.py +++ b/nova/api/metadata/base.py @@ -384,7 +384,14 @@ def get_metadata_by_address(address): ctxt = context.get_admin_context() fixed_ip = network.API().get_fixed_ip_by_address(ctxt, address) - instance = db.instance_get_by_uuid(ctxt, fixed_ip['instance_uuid']) + return get_metadata_by_instance_id(fixed_ip['instance_uuid'], + address, + ctxt) + + +def get_metadata_by_instance_id(instance_id, address, ctxt=None): + ctxt = ctxt or context.get_admin_context() + instance = db.instance_get_by_uuid(ctxt, instance_id) return InstanceMetadata(instance, address) diff --git a/nova/api/metadata/handler.py b/nova/api/metadata/handler.py index bbf1f9318..06fdce30e 100644 --- a/nova/api/metadata/handler.py +++ b/nova/api/metadata/handler.py @@ -17,6 +17,8 @@ # under the License. """Metadata request handler.""" +import hashlib +import hmac import os import webob.dec @@ -28,10 +30,26 @@ from nova.openstack.common import cfg from nova.openstack.common import log as logging from nova import wsgi +CACHE_EXPIRATION = 15 # in seconds + CONF = cfg.CONF CONF.import_opt('memcached_servers', 'nova.config') CONF.import_opt('use_forwarded_for', 'nova.api.auth') +metadata_proxy_opts = [ + cfg.BoolOpt( + 'service_quantum_metadata_proxy', + default=False, + help='Set flag to indicate Quantum will proxy metadata requests and ' + 'resolve instance ids.'), + cfg.StrOpt( + 'quantum_metadata_proxy_shared_secret', + default='', + help='Shared secret to validate proxies Quantum metadata requests') +] + +CONF.register_opts(metadata_proxy_opts) + LOG = logging.getLogger(__name__) if CONF.memcached_servers: @@ -46,7 +64,7 @@ class MetadataRequestHandler(wsgi.Application): def __init__(self): self._cache = memcache.Client(CONF.memcached_servers, debug=0) - def get_metadata(self, address): + def get_metadata_by_remote_address(self, address): if not address: raise exception.FixedIpNotFoundForAddress(address=address) @@ -60,35 +78,113 @@ class MetadataRequestHandler(wsgi.Application): except exception.NotFound: return None - self._cache.set(cache_key, data, 15) + self._cache.set(cache_key, data, CACHE_EXPIRATION) + + return data + + def get_metadata_by_instance_id(self, instance_id, address): + cache_key = 'metadata-%s' % instance_id + data = self._cache.get(cache_key) + if data: + return data + + try: + data = base.get_metadata_by_instance_id(instance_id, address) + except exception.NotFound: + return None + + self._cache.set(cache_key, data, CACHE_EXPIRATION) return data @webob.dec.wsgify(RequestClass=wsgi.Request) def __call__(self, req): + if os.path.normpath("/" + req.path_info) == "/": + return(base.ec2_md_print(base.VERSIONS + ["latest"])) + + if CONF.service_quantum_metadata_proxy: + meta_data = self._handle_instance_id_request(req) + else: + if req.headers.get('X-Instance-ID'): + LOG.warn( + _("X-Instance-ID present in request headers. The " + "'service_quantum_metadata_proxy' option must be enabled" + " to process this header.")) + meta_data = self._handle_remote_ip_request(req) + + if meta_data is None: + raise webob.exc.HTTPNotFound() + + try: + data = meta_data.lookup(req.path_info) + except base.InvalidMetadataPath: + raise webob.exc.HTTPNotFound() + + return base.ec2_md_print(data) + + def _handle_remote_ip_request(self, req): remote_address = req.remote_addr if CONF.use_forwarded_for: remote_address = req.headers.get('X-Forwarded-For', remote_address) - if os.path.normpath("/" + req.path_info) == "/": - return(base.ec2_md_print(base.VERSIONS + ["latest"])) - try: - meta_data = self.get_metadata(remote_address) + meta_data = self.get_metadata_by_remote_address(remote_address) except Exception: LOG.exception(_('Failed to get metadata for ip: %s'), remote_address) msg = _('An unknown error has occurred. ' 'Please try your request again.') - exc = webob.exc.HTTPInternalServerError(explanation=unicode(msg)) - return exc + raise webob.exc.HTTPInternalServerError(explanation=unicode(msg)) + if meta_data is None: LOG.error(_('Failed to get metadata for ip: %s'), remote_address) - raise webob.exc.HTTPNotFound() + + return meta_data + + def _handle_instance_id_request(self, req): + instance_id = req.headers.get('X-Instance-ID') + signature = req.headers.get('X-Instance-ID-Signature') + remote_address = req.remote_addr + + # Ensure that only one header was passed + + if instance_id is None: + msg = _('X-Instance-ID header is missing from request.') + elif not isinstance(instance_id, basestring): + msg = _('Multiple X-Instance-ID headers found within request.') + else: + msg = None + + if msg: + raise webob.exc.HTTPBadRequest(explanation=msg) + + expected_signature = hmac.new( + CONF.quantum_metadata_proxy_shared_secret, + instance_id, + hashlib.sha256).hexdigest() + + if expected_signature != signature: + if instance_id: + w = _('X-Instance-ID-Signature: %(signature)s does not match ' + 'the expected value: %(expected_signature)s for id: ' + '%(instance_id)s. Request From: %(remote_address)s') + LOG.warn(w % locals()) + + msg = _('Invalid proxy request signature.') + raise webob.exc.HTTPForbidden(explanation=msg) try: - data = meta_data.lookup(req.path_info) - except base.InvalidMetadataPath: - raise webob.exc.HTTPNotFound() + meta_data = self.get_metadata_by_instance_id(instance_id, + remote_address) + except Exception: + LOG.exception(_('Failed to get metadata for instance id: %s'), + instance_id) + msg = _('An unknown error has occurred. ' + 'Please try your request again.') + raise webob.exc.HTTPInternalServerError(explanation=unicode(msg)) - return base.ec2_md_print(data) + if meta_data is None: + LOG.error(_('Failed to get metadata for instance id: %s'), + instance_id) + + return meta_data -- cgit