From bc8705c160234fec1af322ecbb5fe0ce5a0d35b8 Mon Sep 17 00:00:00 2001 From: Julia Kreger Date: Wed, 10 Aug 2022 15:14:30 -0700 Subject: Allow project scoped admins to create/delete nodes Adds capabilites for a project scoped admin to create and delete nodes in Ironic's API. These nodes are automatically associated with the project of the requestor. Effectively, this does allow anyone with sufficient privilges, i.e. admin, in an OpenStack deployment to be able to create new baremetal nodes and delete those baremetal nodes. In this case, the user has the "owner" level of rights in the RBAC model. Change-Id: I3fd9ce5de0bc600275b5c4b7a95b0f9405342688 --- ironic/tests/unit/api/controllers/v1/test_node.py | 28 ++++++++++- ironic/tests/unit/api/test_acl.py | 10 +++- .../tests/unit/api/test_rbac_project_scoped.yaml | 58 +++++++++++++++++++--- 3 files changed, 87 insertions(+), 9 deletions(-) (limited to 'ironic/tests/unit') diff --git a/ironic/tests/unit/api/controllers/v1/test_node.py b/ironic/tests/unit/api/controllers/v1/test_node.py index d7a3d474e..6531f36e7 100644 --- a/ironic/tests/unit/api/controllers/v1/test_node.py +++ b/ironic/tests/unit/api/controllers/v1/test_node.py @@ -4898,13 +4898,39 @@ class TestPost(test_api_base.BaseApiTest): ndict = test_api_utils.post_get_test_node(owner='cowsay') response = self.post_json('/nodes', ndict, headers={api_base.Version.string: - str(api_v1.max_version())}) + str(api_v1.max_version()), + 'X-Project-Id': 'cowsay'}) self.assertEqual(http_client.CREATED, response.status_int) result = self.get_json('/nodes/%s' % ndict['uuid'], headers={api_base.Version.string: str(api_v1.max_version())}) self.assertEqual('cowsay', result['owner']) + def test_create_node_owner_system_scope(self): + ndict = test_api_utils.post_get_test_node(owner='catsay') + response = self.post_json('/nodes', ndict, + headers={api_base.Version.string: + str(api_v1.max_version()), + 'OpenStack-System-Scope': 'all', + 'X-Roles': 'admin'}) + self.assertEqual(http_client.CREATED, response.status_int) + result = self.get_json('/nodes/%s' % ndict['uuid'], + headers={api_base.Version.string: + str(api_v1.max_version())}) + self.assertEqual('catsay', result['owner']) + + def test_create_node_owner_recorded_project_scope(self): + ndict = test_api_utils.post_get_test_node() + response = self.post_json('/nodes', ndict, + headers={api_base.Version.string: + str(api_v1.max_version()), + 'X-Project-Id': 'ravensay'}) + self.assertEqual(http_client.CREATED, response.status_int) + result = self.get_json('/nodes/%s' % ndict['uuid'], + headers={api_base.Version.string: + str(api_v1.max_version())}) + self.assertEqual('ravensay', result['owner']) + def test_create_node_owner_old_api_version(self): headers = {api_base.Version.string: '1.32'} ndict = test_api_utils.post_get_test_node(owner='bob') diff --git a/ironic/tests/unit/api/test_acl.py b/ironic/tests/unit/api/test_acl.py index 5793e95a8..cdc20d477 100644 --- a/ironic/tests/unit/api/test_acl.py +++ b/ironic/tests/unit/api/test_acl.py @@ -81,10 +81,18 @@ class TestACLBase(base.BaseApiTest): body=None, assert_status=None, assert_dict_contains=None, assert_list_length=None, - deprecated=None): + deprecated=None, + self_manage_nodes=True): path = path.format(**self.format_data) self.mock_auth.side_effect = self._fake_process_request + # Set self management override + if not self_manage_nodes: + cfg.CONF.set_override( + 'project_admin_can_manage_own_nodes', + False, + 'api') + # always request the latest api version version = api_versions.max_version_string() rheaders = { diff --git a/ironic/tests/unit/api/test_rbac_project_scoped.yaml b/ironic/tests/unit/api/test_rbac_project_scoped.yaml index 802600703..b55439ad1 100644 --- a/ironic/tests/unit/api/test_rbac_project_scoped.yaml +++ b/ironic/tests/unit/api/test_rbac_project_scoped.yaml @@ -89,35 +89,71 @@ owner_admin_cannot_post_nodes: body: &node_post_body name: node driver: fake-driverz - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +owner_admin_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *owner_admin_headers + body: *node_post_body + assert_status: 503 + self_manage_nodes: True owner_manager_cannot_post_nodes: path: '/v1/nodes' method: post headers: *owner_manager_headers body: *node_post_body - assert_status: 500 + assert_status: 403 lessee_admin_cannot_post_nodes: path: '/v1/nodes' method: post headers: *lessee_admin_headers body: *node_post_body - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +lessee_admin_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *lessee_admin_headers + body: *node_post_body + assert_status: 403 + self_manage_nodes: False lessee_manager_cannot_post_nodes: path: '/v1/nodes' method: post headers: *lessee_manager_headers body: *node_post_body - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +lessee_manager_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *lessee_manager_headers + body: *node_post_body + assert_status: 403 + self_manage_nodes: True third_party_admin_cannot_post_nodes: path: '/v1/nodes' method: post headers: *third_party_admin_headers body: *node_post_body - assert_status: 500 + assert_status: 403 + self_manage_nodes: False + +third_party_admin_can_post_nodes: + path: '/v1/nodes' + method: post + headers: *third_party_admin_headers + body: *node_post_body + assert_status: 503 + self_manage_nodes: True # Based on nodes_post_member owner_member_cannot_post_nodes: @@ -125,7 +161,7 @@ owner_member_cannot_post_nodes: method: post headers: *owner_member_headers body: *node_post_body - assert_status: 500 + assert_status: 403 # Based on nodes_post_reader owner_reader_cannot_post_reader: @@ -133,7 +169,7 @@ owner_reader_cannot_post_reader: method: post headers: *owner_reader_headers body: *node_post_body - assert_status: 500 + assert_status: 403 # Based on nodes_get_admin # TODO: Create 3 nodes, 2 owned, 1 leased where it is also owned. @@ -671,6 +707,14 @@ owner_admin_cannot_delete_nodes: method: delete headers: *owner_admin_headers assert_status: 403 + self_manage_nodes: False + +owner_admin_can_delete_nodes: + path: '/v1/nodes/{owner_node_ident}' + method: delete + headers: *owner_admin_headers + assert_status: 503 + self_manage_nodes: True owner_manager_cannot_delete_nodes: path: '/v1/nodes/{owner_node_ident}' -- cgit v1.2.1