From 576e63dab5da809a6b54987a17081e556eeeb126 Mon Sep 17 00:00:00 2001 From: Michael Johnson Date: Fri, 23 Jun 2017 17:49:44 -0700 Subject: [PATCH] Correct RBAC Not Authorized status code This patch corrects the RBAC "Not Authorized" status code to be 403 instead of 401. It also switches some strings over to use constants. Change-Id: I5c2d7384d98720b875de03d311b04193bf448782 --- doc/source/main/policy.rst | 4 ++++ octavia/common/constants.py | 3 +++ octavia/common/exceptions.py | 6 +++--- octavia/common/policy.py | 8 ++++---- octavia/policies/healthmonitor.py | 12 ++++++------ octavia/policies/l7policy.py | 12 ++++++------ octavia/policies/l7rule.py | 10 +++++----- octavia/policies/listener.py | 14 +++++++------- octavia/policies/loadbalancer.py | 16 ++++++++-------- octavia/policies/member.py | 10 +++++----- octavia/policies/pool.py | 12 ++++++------ octavia/policies/quota.py | 12 ++++++------ octavia/tests/functional/api/v2/base.py | 5 +++-- .../functional/api/v2/test_health_monitor.py | 10 +++++----- .../tests/functional/api/v2/test_l7policy.py | 10 +++++----- octavia/tests/functional/api/v2/test_l7rule.py | 10 +++++----- .../tests/functional/api/v2/test_listener.py | 10 +++++----- .../functional/api/v2/test_load_balancer.py | 10 +++++----- octavia/tests/functional/api/v2/test_member.py | 10 +++++----- octavia/tests/functional/api/v2/test_pool.py | 10 +++++----- octavia/tests/functional/api/v2/test_quotas.py | 18 +++++++++--------- octavia/tests/unit/common/test_policy.py | 12 ++++++------ .../octavia_v2_RBAC-0eb2b51aa6278435.yaml | 18 ++++++++++++++++++ 23 files changed, 134 insertions(+), 108 deletions(-) create mode 100644 releasenotes/notes/octavia_v2_RBAC-0eb2b51aa6278435.yaml 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.