From af2f823107010933ecd94a9c938f8b739baaecb7 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 27f4d4e1b210..7ac902304d6f 100644 --- a/nova/api/metadata/handler.py +++ b/nova/api/metadata/handler.py @@ -147,6 +147,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') @@ -154,8 +155,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, basestring): msg = _('Multiple X-Instance-ID headers found within request.') + elif not isinstance(tenant_id, basestring): + msg = _('Multiple X-Tenant-ID headers found within request.') else: msg = None @@ -195,4 +200,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 50f0d07f7a9b..e75b51f86737 100644 --- a/nova/tests/test_metadata.py +++ b/nova/tests/test_metadata.py @@ -601,6 +601,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) @@ -613,6 +614,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) @@ -627,10 +629,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, @@ -638,6 +666,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) @@ -655,6 +684,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)