diff --git a/doc/source/main/policy.rst b/doc/source/main/policy.rst index d23dd56295..ec5d3294ef 100644 --- a/doc/source/main/policy.rst +++ b/doc/source/main/policy.rst @@ -36,6 +36,10 @@ the load-balancer API: It is equivalent to 'rule:context_is_admin or {auth_strategy == noauth}' if that would be valid syntax. +An alternate policy file has been provided in octavia/etc/policy called +admin_or_owner-policy.json that removes the load-balancer RBAC role +requirement. Please see the README.rst in that directory for more information. + Sample File Generation ---------------------- diff --git a/octavia/common/constants.py b/octavia/common/constants.py index 7e9c66aae7..4250903db9 100644 --- a/octavia/common/constants.py +++ b/octavia/common/constants.py @@ -446,6 +446,9 @@ RBAC_DELETE = 'delete' RBAC_GET_ONE = 'get_one' RBAC_GET_ALL = 'get_all' RBAC_GET_ALL_GLOBAL = 'get_all-global' +RBAC_GET_DEFAULTS = 'get_defaults' +RBAC_GET_STATS = 'get_stats' +RBAC_GET_STATUS = 'get_status' # PROVIDERS # TODO(johnsom) When providers are implemented, this should be removed. diff --git a/octavia/common/exceptions.py b/octavia/common/exceptions.py index f97c6e8558..9869d65c17 100644 --- a/octavia/common/exceptions.py +++ b/octavia/common/exceptions.py @@ -70,9 +70,9 @@ class NotFound(APIException): code = 404 -class NotAuthorized(APIException): - msg = _("Not authorized.") - code = 401 +class PolicyForbidden(APIException): + msg = _("Policy does not allow this request to be performed.") + code = 403 class InvalidOption(APIException): diff --git a/octavia/common/policy.py b/octavia/common/policy.py index a2d57021c1..eef5f27f70 100644 --- a/octavia/common/policy.py +++ b/octavia/common/policy.py @@ -67,15 +67,15 @@ class Policy(oslo_policy.Enforcer): for object creation this should be a dictionary representing the location of the object e.g. ``{'project_id': context.project_id}`` - :param do_raise: if True (the default), raises PolicyNotAuthorized; + :param do_raise: if True (the default), raises PolicyForbidden; if False, returns False :param exc: Class of the exceptions to raise if the check fails. Any remaining arguments passed to :meth:`enforce` (both positional and keyword arguments) will be passed to the exceptions class. If not specified, - :class:`PolicyNotAuthorized` will be used. + :class:`PolicyForbidden` will be used. - :raises nova.exceptions.PolicyNotAuthorized: if verification fails + :raises PolicyForbidden: if verification fails and do_raise is True. Or if 'exc' is specified it will raise an exceptions of that type. @@ -89,7 +89,7 @@ class Policy(oslo_policy.Enforcer): credentials['is_admin'] = self.context.is_admin if not exc: - exc = exceptions.NotAuthorized + exc = exceptions.PolicyForbidden try: return super(Policy, self).authorize( diff --git a/octavia/policies/healthmonitor.py b/octavia/policies/healthmonitor.py index d27061d795..5e56949643 100644 --- a/octavia/policies/healthmonitor.py +++ b/octavia/policies/healthmonitor.py @@ -17,28 +17,28 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_HEALTHMONITOR, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List Health Monitors of a Pool", [{'method': 'GET', 'path': '/v2.0/lbaas/healthmonitors'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_HEALTHMONITOR, - action='get_all-global'), + action=constants.RBAC_GET_ALL_GLOBAL), constants.RULE_API_READ_GLOBAL, "List Health Monitors including resources owned by others", [{'method': 'GET', 'path': '/v2.0/lbaas/healthmonitors'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_HEALTHMONITOR, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a Health Monitor", [{'method': 'POST', 'path': '/v2.0/lbaas/healthmonitors'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_HEALTHMONITOR, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show Health Monitor details", [{'method': 'GET', @@ -46,7 +46,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_HEALTHMONITOR, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a Health Monitor", [{'method': 'PUT', @@ -54,7 +54,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_HEALTHMONITOR, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a Health Monitor", [{'method': 'DELETE', diff --git a/octavia/policies/l7policy.py b/octavia/policies/l7policy.py index c5968f549f..cbcb3b9be0 100644 --- a/octavia/policies/l7policy.py +++ b/octavia/policies/l7policy.py @@ -17,28 +17,28 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7POLICY, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List L7 Policys", [{'method': 'GET', 'path': '/v2.0/lbaas/l7policies'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7POLICY, - action='get_all-global'), + action=constants.RBAC_GET_ALL_GLOBAL), constants.RULE_API_READ_GLOBAL, "List L7 Policys including resources owned by others", [{'method': 'GET', 'path': '/v2.0/lbaas/l7policies'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7POLICY, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a L7 Policy", [{'method': 'POST', 'path': '/v2.0/lbaas/l7policies'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7POLICY, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show L7 Policy details", [{'method': 'GET', @@ -46,7 +46,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7POLICY, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a L7 Policy", [{'method': 'PUT', @@ -54,7 +54,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7POLICY, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a L7 Policy", [{'method': 'DELETE', diff --git a/octavia/policies/l7rule.py b/octavia/policies/l7rule.py index 1339337128..7fb72ed695 100644 --- a/octavia/policies/l7rule.py +++ b/octavia/policies/l7rule.py @@ -17,7 +17,7 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7RULE, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List L7 Rules", [{'method': 'GET', @@ -25,7 +25,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7RULE, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a L7 Rule", [{'method': 'POST', @@ -33,7 +33,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7RULE, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show L7 Rule details", [{'method': 'GET', @@ -41,7 +41,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7RULE, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a L7 Rule", [{'method': 'PUT', @@ -49,7 +49,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_L7RULE, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a L7 Rule", [{'method': 'DELETE', diff --git a/octavia/policies/listener.py b/octavia/policies/listener.py index 90d87ba789..124000629a 100644 --- a/octavia/policies/listener.py +++ b/octavia/policies/listener.py @@ -17,28 +17,28 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List Listeners", [{'method': 'GET', 'path': '/v2.0/lbaas/listeners'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='get_all-global'), + action=constants.RBAC_GET_ALL_GLOBAL), constants.RULE_API_READ_GLOBAL, "List Listeners including resources owned by others", [{'method': 'GET', 'path': '/v2.0/lbaas/listeners'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a Listener", [{'method': 'POST', 'path': '/v2.0/lbaas/listeners'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show Listener details", [{'method': 'GET', @@ -46,7 +46,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a Listener", [{'method': 'PUT', @@ -54,7 +54,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a Listener", [{'method': 'DELETE', @@ -62,7 +62,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LISTENER, - action='get_stats'), + action=constants.RBAC_GET_STATS), constants.RULE_API_READ, "Show Listener statistics", [{'method': 'GET', diff --git a/octavia/policies/loadbalancer.py b/octavia/policies/loadbalancer.py index 78db916dfe..403f121dd8 100644 --- a/octavia/policies/loadbalancer.py +++ b/octavia/policies/loadbalancer.py @@ -17,28 +17,28 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List Load Balancers", [{'method': 'GET', 'path': '/v2.0/lbaas/loadbalancers'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='get_all-global'), + action=constants.RBAC_GET_ALL_GLOBAL), constants.RULE_API_READ_GLOBAL, "List Load Balancers including resources owned by others", [{'method': 'GET', 'path': '/v2.0/lbaas/loadbalancers'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a Load Balancer", [{'method': 'POST', 'path': '/v2.0/lbaas/loadbalancers'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show Load Balancer details", [{'method': 'GET', @@ -46,7 +46,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a Load Balancer", [{'method': 'PUT', @@ -54,7 +54,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a Load Balancer", [{'method': 'DELETE', @@ -62,7 +62,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='get_stats'), + action=constants.RBAC_GET_STATS), constants.RULE_API_READ, "Show Load Balancer statistics", [{'method': 'GET', @@ -70,7 +70,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_LOADBALANCER, - action='get_status'), + action=constants.RBAC_GET_STATUS), constants.RULE_API_READ, "Show Load Balancer status", [{'method': 'GET', diff --git a/octavia/policies/member.py b/octavia/policies/member.py index eb076c88a8..7cdacbe445 100644 --- a/octavia/policies/member.py +++ b/octavia/policies/member.py @@ -17,21 +17,21 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_MEMBER, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List Members of a Pool", [{'method': 'GET', 'path': '/v2.0/lbaas/pools/{pool_id}/members'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_MEMBER, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a Member", [{'method': 'POST', 'path': '/v2.0/lbaas/pools/{pool_id}/members'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_MEMBER, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show Member details", [{'method': 'GET', @@ -39,7 +39,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_MEMBER, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a Member", [{'method': 'PUT', @@ -47,7 +47,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_MEMBER, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a Member", [{'method': 'DELETE', diff --git a/octavia/policies/pool.py b/octavia/policies/pool.py index e3c46ad40b..8646da5f29 100644 --- a/octavia/policies/pool.py +++ b/octavia/policies/pool.py @@ -17,28 +17,28 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ, "List Pools", [{'method': 'GET', 'path': '/v2.0/lbaas/pools'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL, - action='get_all-global'), + action=constants.RBAC_GET_ALL_GLOBAL), constants.RULE_API_READ_GLOBAL, "List Pools including resources owned by others", [{'method': 'GET', 'path': '/v2.0/lbaas/pools'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL, - action='post'), + action=constants.RBAC_POST), constants.RULE_API_WRITE, "Create a Pool", [{'method': 'POST', 'path': '/v2.0/lbaas/pools'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ, "Show Pool details", [{'method': 'GET', @@ -46,7 +46,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE, "Update a Pool", [{'method': 'PUT', @@ -54,7 +54,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_POOL, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE, "Remove a Pool", [{'method': 'DELETE', diff --git a/octavia/policies/quota.py b/octavia/policies/quota.py index f9c49437b2..2cee863493 100644 --- a/octavia/policies/quota.py +++ b/octavia/policies/quota.py @@ -17,21 +17,21 @@ from oslo_policy import policy rules = [ policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_QUOTA, - action='get_all'), + action=constants.RBAC_GET_ALL), constants.RULE_API_READ_QUOTA, "List Quotas", [{'method': 'GET', 'path': '/v2.0/lbaas/quotas'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_QUOTA, - action='get_all-global'), + action=constants.RBAC_GET_ALL_GLOBAL), constants.RULE_API_READ_QUOTA_GLOBAL, "List Quotas including resources owned by others", [{'method': 'GET', 'path': '/v2.0/lbaas/quotas'}] ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_QUOTA, - action='get_one'), + action=constants.RBAC_GET_ONE), constants.RULE_API_READ_QUOTA, "Show Quota details", [{'method': 'GET', @@ -39,7 +39,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_QUOTA, - action='put'), + action=constants.RBAC_PUT), constants.RULE_API_WRITE_QUOTA, "Update a Quota", [{'method': 'PUT', @@ -47,7 +47,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_QUOTA, - action='delete'), + action=constants.RBAC_DELETE), constants.RULE_API_WRITE_QUOTA, "Remove a Quota", [{'method': 'DELETE', @@ -55,7 +55,7 @@ rules = [ ), policy.DocumentedRuleDefault( '{rbac_obj}{action}'.format(rbac_obj=constants.RBAC_QUOTA, - action='get_defaults'), + action=constants.RBAC_GET_DEFAULTS), constants.RULE_API_READ_QUOTA, "Show Default Quota for a Project", [{'method': 'GET', diff --git a/octavia/tests/functional/api/v2/base.py b/octavia/tests/functional/api/v2/base.py index 26eb9a42c9..fa3db2838c 100644 --- a/octavia/tests/functional/api/v2/base.py +++ b/octavia/tests/functional/api/v2/base.py @@ -63,8 +63,9 @@ class BaseAPITest(base_db_test.OctaviaDBTestBase): QUOTA_PATH = QUOTAS_PATH + '/{project_id}' QUOTA_DEFAULT_PATH = QUOTAS_PATH + '/{project_id}/default' - NOT_AUTHORIZED_BODY = {'debuginfo': None, 'faultcode': 'Client', - 'faultstring': 'Not authorized.'} + NOT_AUTHORIZED_BODY = { + 'debuginfo': None, 'faultcode': 'Client', + 'faultstring': 'Policy does not allow this request to be performed.'} def setUp(self): super(BaseAPITest, self).setUp() diff --git a/octavia/tests/functional/api/v2/test_health_monitor.py b/octavia/tests/functional/api/v2/test_health_monitor.py index ca16a1dae5..0ebbc799fe 100644 --- a/octavia/tests/functional/api/v2/test_health_monitor.py +++ b/octavia/tests/functional/api/v2/test_health_monitor.py @@ -122,7 +122,7 @@ class TestHealthMonitor(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.HM_PATH.format( - healthmonitor_id=api_hm.get('id')), status=401) + healthmonitor_id=api_hm.get('id')), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -164,7 +164,7 @@ class TestHealthMonitor(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - hms = self.get(self.HMS_PATH, status=401).json + hms = self.get(self.HMS_PATH, status=403).json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, hms) @@ -652,7 +652,7 @@ class TestHealthMonitor(base.BaseAPITest): uuidutils.generate_uuid()): api_hm = self.create_health_monitor( self.pool_id, constants.HEALTH_MONITOR_HTTP, - 1, 1, 1, 1, status=401) + 1, 1, 1, 1, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_hm) @@ -814,7 +814,7 @@ class TestHealthMonitor(base.BaseAPITest): uuidutils.generate_uuid()): response = self.put( self.HM_PATH.format(healthmonitor_id=api_hm.get('id')), - self._build_body(new_hm), status=401) + self._build_body(new_hm), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -937,7 +937,7 @@ class TestHealthMonitor(base.BaseAPITest): uuidutils.generate_uuid()): self.delete( self.HM_PATH.format(healthmonitor_id=api_hm.get('id')), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assert_correct_status( lb_id=self.lb_id, listener_id=self.listener_id, diff --git a/octavia/tests/functional/api/v2/test_l7policy.py b/octavia/tests/functional/api/v2/test_l7policy.py index 1bbc950685..78ee9f90cc 100644 --- a/octavia/tests/functional/api/v2/test_l7policy.py +++ b/octavia/tests/functional/api/v2/test_l7policy.py @@ -98,7 +98,7 @@ class TestL7Policy(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.L7POLICY_PATH.format( - l7policy_id=api_l7policy.get('id')), status=401) + l7policy_id=api_l7policy.get('id')), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -294,7 +294,7 @@ class TestL7Policy(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - policies = self.get(self.L7POLICIES_PATH, status=401).json + policies = self.get(self.L7POLICIES_PATH, status=403).json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, policies) @@ -548,7 +548,7 @@ class TestL7Policy(base.BaseAPITest): self.project_id): api_l7policy = self.create_l7policy( self.listener_id, - constants.L7POLICY_ACTION_REJECT, status=401) + constants.L7POLICY_ACTION_REJECT, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_l7policy) @@ -706,7 +706,7 @@ class TestL7Policy(base.BaseAPITest): self.project_id): response = self.put(self.L7POLICY_PATH.format( l7policy_id=api_l7policy.get('id')), - self._build_body(new_l7policy), status=401) + self._build_body(new_l7policy), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -898,7 +898,7 @@ class TestL7Policy(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): self.delete(self.L7POLICY_PATH.format( - l7policy_id=api_l7policy.get('id')), status=401) + l7policy_id=api_l7policy.get('id')), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assert_correct_status( diff --git a/octavia/tests/functional/api/v2/test_l7rule.py b/octavia/tests/functional/api/v2/test_l7rule.py index 59148042ef..1db3161956 100644 --- a/octavia/tests/functional/api/v2/test_l7rule.py +++ b/octavia/tests/functional/api/v2/test_l7rule.py @@ -97,7 +97,7 @@ class TestL7Rule(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', self.project_id): response = self.get(self.l7rule_path.format( - l7rule_id=l7rule.get('id')), status=401).json + l7rule_id=l7rule.get('id')), status=403).json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response) @@ -207,7 +207,7 @@ class TestL7Rule(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', self.project_id): - rules = self.get(self.l7rules_path, status=401) + rules = self.get(self.l7rules_path, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, rules.json) @@ -419,7 +419,7 @@ class TestL7Rule(base.BaseAPITest): api_l7rule = self.create_l7rule( self.l7policy_id, constants.L7RULE_TYPE_HOST_NAME, constants.L7RULE_COMPARE_TYPE_EQUAL_TO, - 'www.example.com', status=401) + 'www.example.com', status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_l7rule) @@ -637,7 +637,7 @@ class TestL7Rule(base.BaseAPITest): self.project_id): response = self.put(self.l7rule_path.format( l7rule_id=api_l7rule.get('id')), - self._build_body(new_l7rule), status=401) + self._build_body(new_l7rule), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) self.assert_correct_status( @@ -787,7 +787,7 @@ class TestL7Rule(base.BaseAPITest): self.project_id): self.delete( self.l7rule_path.format(l7rule_id=api_l7rule.get('id')), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assert_correct_status( lb_id=self.lb_id, listener_id=self.listener_id, diff --git a/octavia/tests/functional/api/v2/test_listener.py b/octavia/tests/functional/api/v2/test_listener.py index 7ba4fec1ec..228acacc9f 100644 --- a/octavia/tests/functional/api/v2/test_listener.py +++ b/octavia/tests/functional/api/v2/test_listener.py @@ -189,7 +189,7 @@ class TestListener(base.BaseAPITest): auth_strategy=constants.KEYSTONE) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - listeners = self.get(self.LISTENERS_PATH, status=401).json + listeners = self.get(self.LISTENERS_PATH, status=403).json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, listeners) @@ -402,7 +402,7 @@ class TestListener(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.listener_path.format( - listener_id=listener['id']), status=401) + listener_id=listener['id']), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -650,7 +650,7 @@ class TestListener(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - response = self.post(self.LISTENERS_PATH, body, status=401) + response = self.post(self.LISTENERS_PATH, body, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -816,7 +816,7 @@ class TestListener(base.BaseAPITest): auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - api_listener = self.put(listener_path, body, status=401) + api_listener = self.put(listener_path, body, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_listener.json) self.assert_correct_lb_status(self.lb_id, constants.ONLINE, @@ -919,7 +919,7 @@ class TestListener(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - self.delete(listener_path, status=401) + self.delete(listener_path, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assert_correct_lb_status(self.lb_id, constants.ONLINE, constants.ACTIVE) diff --git a/octavia/tests/functional/api/v2/test_load_balancer.py b/octavia/tests/functional/api/v2/test_load_balancer.py index fb239c599b..b4e45ea7ce 100644 --- a/octavia/tests/functional/api/v2/test_load_balancer.py +++ b/octavia/tests/functional/api/v2/test_load_balancer.py @@ -391,7 +391,7 @@ class TestLoadBalancer(base.BaseAPITest): body = self._build_body(lb_json) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - response = self.post(self.LBS_PATH, body, status=401) + response = self.post(self.LBS_PATH, body, status=403) api_lb = response.json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_lb) @@ -550,7 +550,7 @@ class TestLoadBalancer(base.BaseAPITest): LB_PROJECT_PATH = '{}?project_id={}'.format(self.LBS_PATH, project_id) with mock.patch.object(octavia.common.context.Context, 'project_id', self.project_id): - response = self.get(LB_PROJECT_PATH, status=401) + response = self.get(LB_PROJECT_PATH, status=403) api_lb = response.json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_lb) @@ -826,7 +826,7 @@ class TestLoadBalancer(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.LB_PATH.format(lb_id=lb_dict.get('id')), - status=401) + status=403) api_lb = response.json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_lb) @@ -954,7 +954,7 @@ class TestLoadBalancer(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.put(self.LB_PATH.format(lb_id=lb_dict.get('id')), - lb_json, status=401) + lb_json, status=403) api_lb = response.json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_lb) @@ -1111,7 +1111,7 @@ class TestLoadBalancer(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): self.delete(self.LB_PATH.format(lb_id=lb_dict.get('id')), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) response = self.get(self.LB_PATH.format(lb_id=lb_dict.get('id'))) diff --git a/octavia/tests/functional/api/v2/test_member.py b/octavia/tests/functional/api/v2/test_member.py index 1300d4effa..14473b1bbd 100644 --- a/octavia/tests/functional/api/v2/test_member.py +++ b/octavia/tests/functional/api/v2/test_member.py @@ -108,7 +108,7 @@ class TestMember(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.member_path.format( - member_id=api_member.get('id')), status=401).json + member_id=api_member.get('id')), status=403).json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response) @@ -215,7 +215,7 @@ class TestMember(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - response = self.get(self.members_path, status=401) + response = self.get(self.members_path, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -398,7 +398,7 @@ class TestMember(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', self.project_id): api_member = self.create_member( - self.pool_id, '10.0.0.1', 80, status=401) + self.pool_id, '10.0.0.1', 80, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_member) @@ -627,7 +627,7 @@ class TestMember(base.BaseAPITest): member_id=api_member.get('id')) response = self.put( member_path, - self._build_body(new_member), status=401) + self._build_body(new_member), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -789,7 +789,7 @@ class TestMember(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', self.project_id): self.delete(self.member_path_listener.format( - member_id=api_member.get('id')), status=401) + member_id=api_member.get('id')), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assert_correct_status( diff --git a/octavia/tests/functional/api/v2/test_pool.py b/octavia/tests/functional/api/v2/test_pool.py index a055f2a5bd..ff61443410 100644 --- a/octavia/tests/functional/api/v2/test_pool.py +++ b/octavia/tests/functional/api/v2/test_pool.py @@ -122,7 +122,7 @@ class TestPool(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.POOL_PATH.format( - pool_id=api_pool.get('id')), status=401) + pool_id=api_pool.get('id')), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -308,7 +308,7 @@ class TestPool(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - pools = self.get(self.POOLS_PATH, status=401).json + pools = self.get(self.POOLS_PATH, status=403).json self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, pools) @@ -592,7 +592,7 @@ class TestPool(base.BaseAPITest): self.lb_id, constants.PROTOCOL_HTTP, constants.LB_ALGORITHM_ROUND_ROBIN, - listener_id=self.listener_id, status=401) + listener_id=self.listener_id, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_pool) @@ -834,7 +834,7 @@ class TestPool(base.BaseAPITest): uuidutils.generate_uuid()): api_pool = self.put( self.POOL_PATH.format(pool_id=api_pool.get('id')), - self._build_body(new_pool), status=401) + self._build_body(new_pool), status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, api_pool.json) @@ -967,7 +967,7 @@ class TestPool(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): self.delete(self.POOL_PATH.format(pool_id=api_pool.get('id')), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assert_correct_status( diff --git a/octavia/tests/functional/api/v2/test_quotas.py b/octavia/tests/functional/api/v2/test_quotas.py index 238c5dbe12..647578fc1c 100644 --- a/octavia/tests/functional/api/v2/test_quotas.py +++ b/octavia/tests/functional/api/v2/test_quotas.py @@ -118,7 +118,7 @@ class TestQuotas(base.BaseAPITest): self.conf.config(group='api_settings', auth_strategy=constants.TESTING) with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): - response = self.get(self.QUOTAS_PATH, status=401) + response = self.get(self.QUOTAS_PATH, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -150,7 +150,7 @@ class TestQuotas(base.BaseAPITest): with mock.patch( "oslo_context.context.RequestContext.to_policy_values", return_value=override_credentials): - response = self.get(self.QUOTAS_PATH, status=401) + response = self.get(self.QUOTAS_PATH, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -188,7 +188,7 @@ class TestQuotas(base.BaseAPITest): with mock.patch( "oslo_context.context.RequestContext.to_policy_values", return_value=override_credentials): - response = self.get(self.QUOTAS_PATH, status=401) + response = self.get(self.QUOTAS_PATH, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -474,7 +474,7 @@ class TestQuotas(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): quotas = self.get(self.QUOTA_PATH.format(project_id=project1_id), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, quotas.json) @@ -507,7 +507,7 @@ class TestQuotas(base.BaseAPITest): return_value=override_credentials): quotas = self.get( self.QUOTA_PATH.format(project_id=project1_id), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, quotas.json) @@ -540,7 +540,7 @@ class TestQuotas(base.BaseAPITest): return_value=override_credentials): quotas = self.get( self.QUOTA_PATH.format(project_id=project1_id), - status=401) + status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, quotas.json) @@ -659,7 +659,7 @@ class TestQuotas(base.BaseAPITest): with mock.patch.object(octavia.common.context.Context, 'project_id', uuidutils.generate_uuid()): response = self.get(self.QUOTA_DEFAULT_PATH.format( - project_id=self.project_id), status=401) + project_id=self.project_id), status=403) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) self.conf.config(group='api_settings', auth_strategy=auth_strategy) @@ -728,7 +728,7 @@ class TestQuotas(base.BaseAPITest): with mock.patch( "oslo_context.context.RequestContext.to_policy_values", return_value=override_credentials): - response = self.put(quota_path, body, status=401) + response = self.put(quota_path, body, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) self.assertEqual(self.NOT_AUTHORIZED_BODY, response.json) @@ -837,7 +837,7 @@ class TestQuotas(base.BaseAPITest): with mock.patch( "oslo_context.context.RequestContext.to_policy_values", return_value=override_credentials): - self.delete(quota_path, status=401) + self.delete(quota_path, status=403) self.conf.config(group='api_settings', auth_strategy=auth_strategy) response = self.get(quota_path) quota_dict = response.json diff --git a/octavia/tests/unit/common/test_policy.py b/octavia/tests/unit/common/test_policy.py index 514e97726b..6151a478b5 100644 --- a/octavia/tests/unit/common/test_policy.py +++ b/octavia/tests/unit/common/test_policy.py @@ -55,7 +55,7 @@ class PolicyFileTestCase(base.TestCase): tmp.write('{"example:test": "!"}') tmp.flush() self.context.policy.load_rules(True) - self.assertRaises(exceptions.NotAuthorized, + self.assertRaises(exceptions.PolicyForbidden, self.context.policy.authorize, action, self.target) @@ -99,7 +99,7 @@ class PolicyTestCase(base.TestCase): def test_authorize_bad_action_throws(self): action = "example:denied" self.assertRaises( - exceptions.NotAuthorized, self.context.policy.authorize, + exceptions.PolicyForbidden, self.context.policy.authorize, action, self.target) def test_authorize_bad_action_noraise(self): @@ -116,7 +116,7 @@ class PolicyTestCase(base.TestCase): def test_authorize_http(self, req_mock): req_mock.post('http://www.example.com/', text='False') action = "example:get_http" - self.assertRaises(exceptions.NotAuthorized, + self.assertRaises(exceptions.PolicyForbidden, self.context.policy.authorize, action, self.target) def test_templatized_authorization(self): @@ -125,13 +125,13 @@ class PolicyTestCase(base.TestCase): action = "example:my_file" self.context.policy.authorize(action, target_mine) - self.assertRaises(exceptions.NotAuthorized, + self.assertRaises(exceptions.PolicyForbidden, self.context.policy.authorize, action, target_not_mine) def test_early_AND_authorization(self): action = "example:early_and_fail" - self.assertRaises(exceptions.NotAuthorized, + self.assertRaises(exceptions.PolicyForbidden, self.context.policy.authorize, action, self.target) def test_early_OR_authorization(self): @@ -228,5 +228,5 @@ class AdminRolePolicyTestCase(base.TestCase): """ for action in self.actions: self.assertRaises( - exceptions.NotAuthorized, self.context.policy.authorize, + exceptions.PolicyForbidden, self.context.policy.authorize, action, self.target) diff --git a/releasenotes/notes/octavia_v2_RBAC-0eb2b51aa6278435.yaml b/releasenotes/notes/octavia_v2_RBAC-0eb2b51aa6278435.yaml new file mode 100644 index 0000000000..7ca52cfec0 --- /dev/null +++ b/releasenotes/notes/octavia_v2_RBAC-0eb2b51aa6278435.yaml @@ -0,0 +1,18 @@ +--- +features: + - | + The Octavia v2 API now supports Role Based Access Control (RBAC). + The default rules require users to have a load-balancer_* role to be + able to access the Octavia v2 API. This can be overriden with the + admin_or_owner-policy.json sample file provided. + See the `Octavia Policies + `_ + document for more information. +security: + - | + Note that while the Octavia v2 API now supports Role Bassed Access + Control (RBAC), the Octavia v1.0 API does not. The Octavia v1.0 API + should not be exposed publicly and should only be used internally + such as for the neutron-lbaas octavia driver. Publicly accessible + instances of the Octavia API should have the v1.0 API disabled via the + Octavia configuration file.