Prevent spoofing instance_id from neutron to nova

Previously, one could update a port's device_id in neutron to be
that of another tenant's instance_id and then be able to retrieve
that instance's metadata. This patch prevents this from occurring by
checking that X-Tenant-ID received from the metadata request matches
the tenant_id in the nova database.

DocImpact - This patch is dependent on another patch in neutron
            which adds X-Tenant-ID to the request. Therefore to
            minimize downtime one should upgrade Neutron first (then
            restart neutron-metadata-agent) and lastly update nova.

Change-Id: I93bf662797c3986324ca2099b403833c2e990fb4
Closes-Bug: #1235450
This commit is contained in:
Aaron Rosen 2013-10-07 13:33:31 -07:00 committed by Aaron Rosen
parent 62e549eb02
commit bce36e9bdb
2 changed files with 43 additions and 0 deletions

View File

@ -148,6 +148,7 @@ class MetadataRequestHandler(wsgi.Application):
def _handle_instance_id_request(self, req): def _handle_instance_id_request(self, req):
instance_id = req.headers.get('X-Instance-ID') instance_id = req.headers.get('X-Instance-ID')
tenant_id = req.headers.get('X-Tenant-ID')
signature = req.headers.get('X-Instance-ID-Signature') signature = req.headers.get('X-Instance-ID-Signature')
remote_address = req.headers.get('X-Forwarded-For') remote_address = req.headers.get('X-Forwarded-For')
@ -155,8 +156,12 @@ class MetadataRequestHandler(wsgi.Application):
if instance_id is None: if instance_id is None:
msg = _('X-Instance-ID header is missing from request.') msg = _('X-Instance-ID header is missing from request.')
elif tenant_id is None:
msg = _('X-Tenant-ID header is missing from request.')
elif not isinstance(instance_id, six.string_types): elif not isinstance(instance_id, six.string_types):
msg = _('Multiple X-Instance-ID headers found within request.') msg = _('Multiple X-Instance-ID headers found within request.')
elif not isinstance(tenant_id, six.string_types):
msg = _('Multiple X-Tenant-ID headers found within request.')
else: else:
msg = None msg = None
@ -196,4 +201,12 @@ class MetadataRequestHandler(wsgi.Application):
LOG.error(_('Failed to get metadata for instance id: %s'), LOG.error(_('Failed to get metadata for instance id: %s'),
instance_id) instance_id)
if meta_data.instance['project_id'] != tenant_id:
LOG.warning(_("Tenant_id %(tenant_id)s does not match tenant_id "
"of instance %(instance_id)s."),
{'tenant_id': tenant_id,
'instance_id': instance_id})
# causes a 404 to be raised
meta_data = None
return meta_data return meta_data

View File

@ -643,6 +643,7 @@ class MetadataHandlerTestCase(test.TestCase):
relpath="/2009-04-04/user-data", relpath="/2009-04-04/user-data",
address="192.192.192.2", address="192.192.192.2",
headers={'X-Instance-ID': 'a-b-c-d', headers={'X-Instance-ID': 'a-b-c-d',
'X-Tenant-ID': 'test',
'X-Instance-ID-Signature': signed}) 'X-Instance-ID-Signature': signed})
self.assertEqual(response.status_int, 200) self.assertEqual(response.status_int, 200)
@ -655,6 +656,7 @@ class MetadataHandlerTestCase(test.TestCase):
fake_get_metadata_by_instance_id=fake_get_metadata, fake_get_metadata_by_instance_id=fake_get_metadata,
headers={'X-Forwarded-For': '192.192.192.2', headers={'X-Forwarded-For': '192.192.192.2',
'X-Instance-ID': 'a-b-c-d', 'X-Instance-ID': 'a-b-c-d',
'X-Tenant-ID': 'test',
'X-Instance-ID-Signature': signed}) 'X-Instance-ID-Signature': signed})
self.assertEqual(response.status_int, 200) self.assertEqual(response.status_int, 200)
@ -669,10 +671,36 @@ class MetadataHandlerTestCase(test.TestCase):
fake_get_metadata_by_instance_id=fake_get_metadata, fake_get_metadata_by_instance_id=fake_get_metadata,
headers={'X-Forwarded-For': '192.192.192.2', headers={'X-Forwarded-For': '192.192.192.2',
'X-Instance-ID': 'a-b-c-d', 'X-Instance-ID': 'a-b-c-d',
'X-Tenant-ID': 'test',
'X-Instance-ID-Signature': ''}) 'X-Instance-ID-Signature': ''})
self.assertEqual(response.status_int, 403) self.assertEqual(response.status_int, 403)
# missing X-Tenant-ID from request
response = fake_request(
self.stubs, self.mdinst,
relpath="/2009-04-04/user-data",
address="192.192.192.2",
fake_get_metadata_by_instance_id=fake_get_metadata,
headers={'X-Forwarded-For': '192.192.192.2',
'X-Instance-ID': 'a-b-c-d',
'X-Instance-ID-Signature': signed})
self.assertEqual(response.status_int, 400)
# mismatched X-Tenant-ID
response = fake_request(
self.stubs, self.mdinst,
relpath="/2009-04-04/user-data",
address="192.192.192.2",
fake_get_metadata_by_instance_id=fake_get_metadata,
headers={'X-Forwarded-For': '192.192.192.2',
'X-Instance-ID': 'a-b-c-d',
'X-Tenant-ID': 'FAKE',
'X-Instance-ID-Signature': signed})
self.assertEqual(response.status_int, 404)
# without X-Forwarded-For # without X-Forwarded-For
response = fake_request( response = fake_request(
self.stubs, self.mdinst, self.stubs, self.mdinst,
@ -680,6 +708,7 @@ class MetadataHandlerTestCase(test.TestCase):
address="192.192.192.2", address="192.192.192.2",
fake_get_metadata_by_instance_id=fake_get_metadata, fake_get_metadata_by_instance_id=fake_get_metadata,
headers={'X-Instance-ID': 'a-b-c-d', headers={'X-Instance-ID': 'a-b-c-d',
'X-Tenant-ID': 'test',
'X-Instance-ID-Signature': signed}) 'X-Instance-ID-Signature': signed})
self.assertEqual(response.status_int, 500) self.assertEqual(response.status_int, 500)
@ -697,6 +726,7 @@ class MetadataHandlerTestCase(test.TestCase):
fake_get_metadata_by_instance_id=fake_get_metadata, fake_get_metadata_by_instance_id=fake_get_metadata,
headers={'X-Forwarded-For': '192.192.192.2', headers={'X-Forwarded-For': '192.192.192.2',
'X-Instance-ID': 'z-z-z-z', 'X-Instance-ID': 'z-z-z-z',
'X-Tenant-ID': 'test',
'X-Instance-ID-Signature': signed}) 'X-Instance-ID-Signature': signed})
self.assertEqual(response.status_int, 500) self.assertEqual(response.status_int, 500)