Fix os-attach-interfaces policy to be admin_or_owner

os-attach-interfaces APi policy is default to admin_or_owner[1] but API
is allowed for everyone.

We can see the test trying with other project context can access the API
- https://review.opendev.org/#/c/705126/1

This is because API does not pass the server project_id in policy target[2]
and if no target is passed then, policy.py add the default targets which is
nothing but context.project_id (allow for everyone who try to access)[3]

This commit fix this policy by passing the server's project_id in policy
target.

[1] c16315165c/nova/policies/attach_interfaces.py (L28)
[2] c16315165c/nova/api/openstack/compute/attach_interfaces.py (L70)
[3] c16315165c/nova/policy.py (L191)
Closes-bug: #1861464

Change-Id: I1e2247884169e6ba3e5302be4323428c67ce7a10
This commit is contained in:
Ghanshyam 2020-01-30 18:18:39 -06:00 committed by John Garbutt
parent 9dbba6915c
commit 728f2b215e
2 changed files with 45 additions and 23 deletions

View File

@ -67,9 +67,10 @@ class InterfaceAttachmentController(wsgi.Controller):
def index(self, req, server_id):
"""Returns the list of interface attachments for a given instance."""
context = req.environ['nova.context']
context.can(ai_policies.BASE_POLICY_NAME)
instance = common.get_instance(self.compute_api, context, server_id)
context.can(ai_policies.BASE_POLICY_NAME,
target={'project_id': instance.project_id})
search_opts = {'device_id': instance.uuid}
try:
@ -108,13 +109,11 @@ class InterfaceAttachmentController(wsgi.Controller):
def show(self, req, server_id, id):
"""Return data about the given interface attachment."""
context = req.environ['nova.context']
context.can(ai_policies.BASE_POLICY_NAME)
instance = common.get_instance(self.compute_api, context, server_id)
context.can(ai_policies.BASE_POLICY_NAME,
target={'project_id': instance.project_id})
port_id = id
# NOTE(mriedem): We need to verify the instance actually exists from
# the server_id even though we're not using the instance for anything,
# just the port id.
common.get_instance(self.compute_api, context, server_id)
try:
port_info = self.network_api.show_port(context, port_id)
@ -139,8 +138,12 @@ class InterfaceAttachmentController(wsgi.Controller):
def create(self, req, server_id, body):
"""Attach an interface to an instance."""
context = req.environ['nova.context']
context.can(ai_policies.BASE_POLICY_NAME)
context.can(ai_policies.POLICY_ROOT % 'create')
instance = common.get_instance(self.compute_api, context, server_id)
context.can(ai_policies.BASE_POLICY_NAME,
target={'project_id': instance.project_id})
context.can(ai_policies.POLICY_ROOT % 'create',
target={'project_id': instance.project_id})
network_id = None
port_id = None
@ -163,7 +166,6 @@ class InterfaceAttachmentController(wsgi.Controller):
msg = _("Must input network_id when request IP address")
raise exc.HTTPBadRequest(explanation=msg)
instance = common.get_instance(self.compute_api, context, server_id)
try:
vif = self.compute_api.attach_interface(context,
instance, network_id, port_id, req_ip, tag=tag)
@ -199,12 +201,16 @@ class InterfaceAttachmentController(wsgi.Controller):
def delete(self, req, server_id, id):
"""Detach an interface from an instance."""
context = req.environ['nova.context']
context.can(ai_policies.BASE_POLICY_NAME)
context.can(ai_policies.POLICY_ROOT % 'delete')
port_id = id
instance = common.get_instance(self.compute_api, context, server_id,
expected_attrs=['device_metadata'])
context.can(ai_policies.BASE_POLICY_NAME,
target={'project_id': instance.project_id})
context.can(ai_policies.POLICY_ROOT % 'delete',
target={'project_id': instance.project_id})
port_id = id
try:
self.compute_api.detach_interface(context,
instance, port_id=port_id)

View File

@ -25,6 +25,7 @@ from nova import exception
from nova import objects
from nova import test
from nova.tests.unit.api.openstack import fakes
from nova.tests.unit import fake_instance
from nova.tests.unit import fake_network_cache_model
@ -109,8 +110,10 @@ def fake_detach_interface(self, context, instance, port_id):
raise exception.PortNotFound(port_id=port_id)
def fake_get_instance(self, *args, **kwargs):
return objects.Instance(uuid=FAKE_UUID1)
def fake_get_instance(self, context, instance_id, expected_attrs=None,
cell_down_support=False):
return fake_instance.fake_instance_obj(
context, id=1, uuid=instance_id, project_id=context.project_id)
class InterfaceAttachTestsV21(test.NoDBTestCase):
@ -178,8 +181,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
def test_delete(self):
self.stub_out('nova.compute.api.API.detach_interface',
fake_detach_interface)
inst = objects.Instance(uuid=FAKE_UUID1)
req_context = self.req.environ['nova.context']
inst = objects.Instance(uuid=FAKE_UUID1,
project_id=req_context.project_id)
with mock.patch.object(common, 'get_instance',
return_value=inst) as mock_get_instance:
result = self.attachments.delete(self.req, FAKE_UUID1,
@ -360,7 +364,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
def test_attach_interface_fixed_ip_already_in_use(self,
attach_mock,
get_mock):
fake_instance = objects.Instance(uuid=FAKE_UUID1)
req_context = self.req.environ['nova.context']
fake_instance = objects.Instance(uuid=FAKE_UUID1,
project_id=req_context.project_id)
get_mock.return_value = fake_instance
attach_mock.side_effect = exception.FixedIpAlreadyInUse(
address='10.0.2.2', instance_uuid=FAKE_UUID1)
@ -380,7 +386,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
def test_attach_interface_port_in_use(self,
attach_mock,
get_mock):
fake_instance = objects.Instance(uuid=FAKE_UUID1)
req_context = self.req.environ['nova.context']
fake_instance = objects.Instance(uuid=FAKE_UUID1,
project_id=req_context.project_id)
get_mock.return_value = fake_instance
attach_mock.side_effect = exception.PortInUse(
port_id=FAKE_PORT_ID1)
@ -400,7 +408,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
def test_attach_interface_port_not_usable(self,
attach_mock,
get_mock):
fake_instance = objects.Instance(uuid=FAKE_UUID1)
req_context = self.req.environ['nova.context']
fake_instance = objects.Instance(uuid=FAKE_UUID1,
project_id=req_context.project_id)
get_mock.return_value = fake_instance
attach_mock.side_effect = exception.PortNotUsable(
port_id=FAKE_PORT_ID1,
@ -419,8 +429,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
@mock.patch.object(compute_api.API, 'get')
@mock.patch.object(compute_api.API, 'attach_interface')
def test_attach_interface_failed_no_network(self, attach_mock, get_mock):
req_context = self.req.environ['nova.context']
fake_instance = objects.Instance(uuid=FAKE_UUID1,
project_id=FAKE_UUID2)
project_id=req_context.project_id)
get_mock.return_value = fake_instance
attach_mock.side_effect = (
exception.InterfaceAttachFailedNoNetwork(project_id=FAKE_UUID2))
@ -438,7 +449,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
def test_attach_interface_no_more_fixed_ips(self,
attach_mock,
get_mock):
fake_instance = objects.Instance(uuid=FAKE_UUID1)
req_context = self.req.environ['nova.context']
fake_instance = objects.Instance(uuid=FAKE_UUID1,
project_id=req_context.project_id)
get_mock.return_value = fake_instance
attach_mock.side_effect = exception.NoMoreFixedIps(
net=FAKE_NET_ID1)
@ -457,8 +470,9 @@ class InterfaceAttachTestsV21(test.NoDBTestCase):
@mock.patch.object(compute_api.API, 'attach_interface')
def test_attach_interface_failed_securitygroup_cannot_be_applied(
self, attach_mock, get_mock):
req_context = self.req.environ['nova.context']
fake_instance = objects.Instance(uuid=FAKE_UUID1,
project_id=FAKE_UUID2)
project_id=req_context.project_id)
get_mock.return_value = fake_instance
attach_mock.side_effect = (
exception.SecurityGroupCannotBeApplied())
@ -580,6 +594,8 @@ class AttachInterfacesPolicyEnforcementv21(test.NoDBTestCase):
self.rule_name = "os_compute_api:os-attach-interfaces"
self.policy.set_rules({self.rule_name: "project:non_fake"})
self.stub_out('nova.compute.api.API.get', fake_get_instance)
def test_index_attach_interfaces_policy_failed(self):
exc = self.assertRaises(
exception.PolicyNotAuthorized,