diff --git a/.gitignore b/.gitignore index fe742e7..c15d620 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /dist +/venv /python_unikorn_openstack_policy.egg-info/ __pycache__ *.swp diff --git a/README.md b/README.md index 11cebc2..a77887d 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,12 @@ We need the following to be allowed (non-root): * Management of quotas * Provisioning of provider networks in managed projects +### Block Storage Service + +We need the following to be allowed (non-root): + +* Management of quotas + ### Design Problem with any service that isn't Keystone is, it has zero view of identity hierarchies. @@ -78,18 +84,25 @@ openstack network create --provider-network-type vlan --provider-physical-networ ### Installation +```bash +python3 -m venv venv +source venv/bin/activate +pip3 install build pylint +``` + > [!NOTE] > Running the following will install all the necessary dependencies. > This also includes any commands required for the the following sections. ```bash python3 -m build -pip3 install dist/python_unikorn_openstack_policy-0.1.0-py3-none-any.whl +pip3 install --force-reinstall dist/python_unikorn_openstack_policy-0.1.0-py3-none-any.whl ``` ### Generating Policy Files ```bash +oslopolicy-policy-generator --namespace unikorn_openstack_policy_blockstorage oslopolicy-policy-generator --namespace unikorn_openstack_policy_compute oslopolicy-policy-generator --namespace unikorn_openstack_policy_network ``` diff --git a/pyproject.toml b/pyproject.toml index c7db556..6a8f2c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,19 +24,22 @@ classifiers = [ "Programming Language :: Python :: 3 :: Only", ] dependencies = [ - "oslo.config", + "cinder", "neutron", - "nova" + "nova", + "oslo.config", ] [project.urls] homepage = "https://github.com/unikorn-cloud/python-unikorn-openstack-policy" [project.entry-points."oslo.policy.policies"] +unikorn_openstack_policy_blockstorage = "unikorn_openstack_policy.blockstorage:list_rules" unikorn_openstack_policy_compute = "unikorn_openstack_policy.compute:list_rules" unikorn_openstack_policy_network = "unikorn_openstack_policy.network:list_rules" [project.entry-points."oslo.policy.enforcer"] +unikorn_openstack_policy_blockstorage = "unikorn_openstack_policy.blockstorage:get_enforcer" unikorn_openstack_policy_compute = "unikorn_openstack_policy.compute:get_enforcer" unikorn_openstack_policy_network = "unikorn_openstack_policy.network:get_enforcer" diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 0275f22..0000000 --- a/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -neutron>=24.0.1 -oslo.policy>=4.4.0 diff --git a/unikorn_openstack_policy/blockstorage.py b/unikorn_openstack_policy/blockstorage.py new file mode 100644 index 0000000..ffc426e --- /dev/null +++ b/unikorn_openstack_policy/blockstorage.py @@ -0,0 +1,56 @@ +# Copyright 2024 the Unikorn Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Defines Oslo Policy Rules. +""" + +# pylint: disable=line-too-long + +from cinder import policies +from cinder.policies import quotas +from oslo_config import cfg +from oslo_policy import policy +from unikorn_openstack_policy import base + +rules = [ + # The domain manager needs to be able to alter the default quotas + # or it won't we able to fulfill any cluster creation requests. + policy.RuleDefault( + name=quotas.UPDATE_POLICY, + check_str='rule:is_project_manager', + description='Update the block storage quotas', + ) +] + + +def list_rules(): + """Implements the "oslo.policy.policies" entry point""" + + # For every defined rule, look for a corresponding one sourced directly + # from nova, this means we can augment the exact rule defined for a + # specific version of nova, + return base.inherit_rules(rules, list(policies.list_rules())) + + +def get_enforcer(): + """Implements the "oslo.policy.enforcer" entry point""" + + enforcer = policy.Enforcer(conf=cfg.CONF) + enforcer.register_defaults(list_rules()) + + return enforcer + + +# vi: ts=4 et: diff --git a/unikorn_openstack_policy/compute.py b/unikorn_openstack_policy/compute.py index 3ee71af..523e6d9 100644 --- a/unikorn_openstack_policy/compute.py +++ b/unikorn_openstack_policy/compute.py @@ -29,7 +29,7 @@ policy.RuleDefault( name='os_compute_api:os-quota-sets:update', check_str='rule:is_project_manager', - description='Update the quotas', + description='Update the compute quotas', ) ] diff --git a/unikorn_openstack_policy/tests/test_blockstorage.py b/unikorn_openstack_policy/tests/test_blockstorage.py new file mode 100644 index 0000000..8f87b4d --- /dev/null +++ b/unikorn_openstack_policy/tests/test_blockstorage.py @@ -0,0 +1,134 @@ +# Copyright 2024 the Unikorn Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Unit tests for OpenStack policies. +""" + +from cinder.policies import quotas +from oslo_policy import policy + +from unikorn_openstack_policy import blockstorage +from unikorn_openstack_policy.tests import base + +class ProjectAdminBlockStoragePolicyTests(base.PolicyTestsBase): + """ + Checks policy enforcement for project scoped admin role. + """ + + # Request context. + context = None + + def setUp(self): + """Perform setup actions for all tests""" + self.setup(blockstorage.get_enforcer()) + self.context = self.project_admin_context + + def test_update_quota_sets(self): + """Admin can update quota sets""" + self.assertTrue(self.enforce( + quotas.UPDATE_POLICY, self.target, self.context)) + + +class DomainAdminBlockStoragePolicyTests(ProjectAdminBlockStoragePolicyTests): + """ + Checks policy enforcement for domain scoped admin role + """ + + def setUp(self): + self.setup(blockstorage.get_enforcer()) + self.context = self.domain_admin_context + + +class ProjectManagerBlockStoragePolicyTests(base.PolicyTestsBase): + """ + Checks policy enforcement for project scoped manager role + """ + + # Request context. + context = None + + def setUp(self): + """Perform setup actions for all tests""" + self.setup(blockstorage.get_enforcer()) + self.context = self.project_manager_context + + def test_update_quota_sets(self): + """Project manager can update quota sets""" + self.assertTrue(self.enforce( + quotas.UPDATE_POLICY, self.target, self.context)) + self.assertRaises( + policy.PolicyNotAuthorized, + self.enforce, + quotas.UPDATE_POLICY, self.alt_target, self.context) + + +class DomainManagerBlockStoragePolicyTests(base.PolicyTestsBase): + """ + Checks policy enforcement for the manager role. + """ + + def setUp(self): + """Perform setup actions for all tests""" + self.setup(blockstorage.get_enforcer()) + self.context = self.domain_manager_context + + def test_update_quota_sets(self): + """Domain manager cannot update quota sets""" + self.assertRaises( + policy.PolicyNotAuthorized, + self.enforce, + quotas.UPDATE_POLICY, self.target, self.context) + self.assertRaises( + policy.PolicyNotAuthorized, + self.enforce, + quotas.UPDATE_POLICY, self.alt_target, self.context) + + +class ProjectMemberBlockStoragePolicyTests(base.PolicyTestsBase): + """ + Checks policy enforcement for the member role. + """ + + def setUp(self): + """Perform setup actions for all tests""" + self.setup(blockstorage.get_enforcer()) + self.context = self.project_member_context + + def test_update_quota_sets(self): + """Project member cannot create quota sets""" + self.assertRaises( + policy.PolicyNotAuthorized, + self.enforce, + quotas.UPDATE_POLICY, self.target, self.context) + + +class DomainMemberBlockStoragePolicyTests(base.PolicyTestsBase): + """ + Checks policy enforcement for the member role. + """ + + def setUp(self): + """Perform setup actions for all tests""" + self.setup(blockstorage.get_enforcer()) + self.context = self.domain_member_context + + def test_update_quota_sets(self): + """Domain member cannot create quota sets""" + self.assertRaises( + policy.PolicyNotAuthorized, + self.enforce, + quotas.UPDATE_POLICY, self.target, self.context) + +# vi: ts=4 et: