From bce36e9bdb1fcb9658f7b684d160e656e88d816c Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Mon, 7 Oct 2013 13:33:31 -0700 Subject: [PATCH] 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 --- nova/api/metadata/handler.py | 13 +++++++++++++ nova/tests/test_metadata.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/nova/api/metadata/handler.py b/nova/api/metadata/handler.py index 8797996234bf..001bc28f75bf 100644 --- a/nova/api/metadata/handler.py +++ b/nova/api/metadata/handler.py @@ -148,6 +148,7 @@ class MetadataRequestHandler(wsgi.Application): def _handle_instance_id_request(self, req): instance_id = req.headers.get('X-Instance-ID') + tenant_id = req.headers.get('X-Tenant-ID') signature = req.headers.get('X-Instance-ID-Signature') remote_address = req.headers.get('X-Forwarded-For') @@ -155,8 +156,12 @@ class MetadataRequestHandler(wsgi.Application): if instance_id is None: 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): 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: msg = None @@ -196,4 +201,12 @@ class MetadataRequestHandler(wsgi.Application): LOG.error(_('Failed to get metadata for instance id: %s'), 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 diff --git a/nova/tests/test_metadata.py b/nova/tests/test_metadata.py index 76d43725cb04..92cf5ffeb125 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -643,6 +643,7 @@ class MetadataHandlerTestCase(test.TestCase): relpath="/2009-04-04/user-data", address="192.192.192.2", headers={'X-Instance-ID': 'a-b-c-d', + 'X-Tenant-ID': 'test', 'X-Instance-ID-Signature': signed}) self.assertEqual(response.status_int, 200) @@ -655,6 +656,7 @@ class MetadataHandlerTestCase(test.TestCase): 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': 'test', 'X-Instance-ID-Signature': signed}) self.assertEqual(response.status_int, 200) @@ -669,10 +671,36 @@ class MetadataHandlerTestCase(test.TestCase): 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': 'test', 'X-Instance-ID-Signature': ''}) 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 response = fake_request( self.stubs, self.mdinst, @@ -680,6 +708,7 @@ class MetadataHandlerTestCase(test.TestCase): address="192.192.192.2", fake_get_metadata_by_instance_id=fake_get_metadata, headers={'X-Instance-ID': 'a-b-c-d', + 'X-Tenant-ID': 'test', 'X-Instance-ID-Signature': signed}) self.assertEqual(response.status_int, 500) @@ -697,6 +726,7 @@ class MetadataHandlerTestCase(test.TestCase): fake_get_metadata_by_instance_id=fake_get_metadata, headers={'X-Forwarded-For': '192.192.192.2', 'X-Instance-ID': 'z-z-z-z', + 'X-Tenant-ID': 'test', 'X-Instance-ID-Signature': signed}) self.assertEqual(response.status_int, 500)