Support enables rbac policies new defaults

The Magnum service allow enables policies (RBAC) new defaults and scope by
default. The Default value of config options ``[oslo_policy] enforce_scope``
and ``[oslo_policy] oslo_policy.enforce_new_defaults`` are both to
``False``, but will change to ``True`` in following cycles.

To enable them then modify the below config options value in
``magnum.conf`` file::

  [oslo_policy]
  enforce_new_defaults=True
  enforce_scope=True

reference tc goal for more detail:
https://governance.openstack.org/tc/goals/selected/consistent-and-secure-rbac.html

Related blueprint secure-rbac

Change-Id: I249942a355577c4f1ef51b3988f0cc4979959d0b
This commit is contained in:
ricolin 2023-02-26 17:30:02 +08:00
parent fda54620ad
commit 5971243169
26 changed files with 404 additions and 124 deletions

View File

@ -153,6 +153,14 @@ function create_magnum_conf {
iniset $MAGNUM_CONF oslo_policy policy_file $MAGNUM_POLICY
if [[ "$MAGNUM_ENFORCE_SCOPE" == True ]] ; then
iniset $MAGNUM_CONF oslo_policy enforce_scope true
iniset $MAGNUM_CONF oslo_policy enforce_new_defaults true
else
iniset $MAGNUM_CONF oslo_policy enforce_scope false
iniset $MAGNUM_CONF oslo_policy enforce_new_defaults false
fi
iniset $MAGNUM_CONF keystone_auth auth_type password
iniset $MAGNUM_CONF keystone_auth username magnum
iniset $MAGNUM_CONF keystone_auth password $SERVICE_PASSWORD

View File

@ -14,6 +14,10 @@
# PHYSICAL_NETWORK=public
# OVS_PHYSICAL_BRIDGE=br-ex
# This option controls whether or not to enforce scope when evaluating policies. Learn more:
# https://docs.openstack.org/oslo.policy/latest/configuration/index.html#oslo_policy.enforce_scope
MAGNUM_ENFORCE_SCOPE=$(trueorfalse False MAGNUM_ENFORCE_SCOPE)
# Enable Magnum services
enable_service magnum-api
enable_service magnum-cond

View File

@ -52,8 +52,8 @@ class ContextHook(hooks.PecanHook):
user_id = headers.get('X-User-Id')
project = headers.get('X-Project-Name')
project_id = headers.get('X-Project-Id')
domain_id = headers.get('X-User-Domain-Id')
domain_name = headers.get('X-User-Domain-Name')
user_domain_id = headers.get('X-User-Domain-Id')
user_domain_name = headers.get('X-User-Domain-Name')
auth_token = headers.get('X-Auth-Token')
roles = headers.get('X-Roles', '').split(',')
auth_token_info = state.request.environ.get('keystone.token_info')
@ -72,8 +72,8 @@ class ContextHook(hooks.PecanHook):
user_id=user_id,
project_name=project,
project_id=project_id,
domain_id=domain_id,
domain_name=domain_name,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name,
roles=roles)

View File

@ -42,7 +42,7 @@ class RequestContext(context.RequestContext):
"""
super(RequestContext, self).__init__(auth_token=auth_token,
user_id=user_name,
project_id=project_name,
project_id=project_id,
is_admin=is_admin,
read_only=read_only,
show_deleted=show_deleted,
@ -53,8 +53,6 @@ class RequestContext(context.RequestContext):
self.user_id = user_id
self.project_name = project_name
self.project_id = project_id
self.domain_id = domain_id
self.domain_name = domain_name
self.user_domain_id = user_domain_id
self.user_domain_name = user_domain_name
self.auth_url = auth_url
@ -71,8 +69,6 @@ class RequestContext(context.RequestContext):
value = super(RequestContext, self).to_dict()
value.update({'auth_token': self.auth_token,
'auth_url': self.auth_url,
'domain_id': self.domain_id,
'domain_name': self.domain_name,
'user_domain_id': self.user_domain_id,
'user_domain_name': self.user_domain_name,
'user_name': self.user_name,

View File

@ -13,12 +13,79 @@
# under the License.
from oslo_policy import policy
ROLE_ADMIN = 'rule:context_is_admin'
RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
RULE_ADMIN_API = 'rule:admin_api'
RULE_ADMIN_API = 'rule:context_is_admin'
RULE_ADMIN_OR_USER = 'rule:admin_or_user'
RULE_CLUSTER_USER = 'rule:cluster_user'
RULE_DENY_CLUSTER_USER = 'rule:deny_cluster_user'
RULE_USER = "rule:is_user"
# Generic check string for checking if a user is authorized on a particular
# project, specifically with the member role.
RULE_PROJECT_MEMBER = 'rule:project_member'
# Generic check string for checking if a user is authorized on a particular
# project but with read-only access. For example, this persona would be able to
# list private images owned by a project but cannot make any writeable changes
# to those images.
RULE_PROJECT_READER = 'rule:project_reader'
RULE_USER_OR_CLUSTER_USER = (
'rule:user_or_cluster_user')
RULE_ADMIN_OR_PROJECT_READER = (
'rule:admin_or_project_reader')
RULE_ADMIN_OR_PROJECT_MEMBER = (
'rule:admin_or_project_member')
RULE_ADMIN_OR_PROJECT_MEMBER_USER = (
'rule:admin_or_project_member_user')
RULE_ADMIN_OR_PROJECT_MEMBER_USER_OR_CLUSTER_USER = (
'rule:admin_or_project_member_user_or_cluster_user')
RULE_PROJECT_MEMBER_DENY_CLUSTER_USER = (
'rule:project_member_deny_cluster_user')
RULE_ADMIN_OR_PROJECT_MEMBER_DENY_CLUSTER_USER = (
'rule:admin_or_project_member_deny_cluster_user')
RULE_PROJECT_READER_DENY_CLUSTER_USER = (
'rule:project_reader_deny_cluster_user')
RULE_ADMIN_OR_PROJECT_READER_DENY_CLUSTER_USER = (
'rule:admin_or_project_reader_deny_cluster_user')
RULE_ADMIN_OR_PROJECT_READER_USER_OR_CLUSTER_USER = (
'rule:admin_or_project_reader_user_or_cluster_user')
# ==========================================================
# Deprecated Since OpenStack 2023.2(Magnum 17.0.0) and should be removed in
# The following cycle.
DEPRECATED_REASON = """
The Magnum API now enforces scoped tokens and default reader and member roles.
"""
DEPRECATED_SINCE = 'OpenStack 2023.2(Magnum 17.0.0)'
DEPRECATED_DENY_CLUSTER_USER = policy.DeprecatedRule(
name=RULE_DENY_CLUSTER_USER,
check_str='not domain_id:%(trustee_domain_id)s',
deprecated_reason=DEPRECATED_REASON,
deprecated_since=DEPRECATED_SINCE
)
DEPRECATED_RULE_ADMIN_OR_OWNER = policy.DeprecatedRule(
name=RULE_ADMIN_OR_OWNER,
check_str='is_admin:True or project_id:%(project_id)s',
deprecated_reason=DEPRECATED_REASON,
deprecated_since=DEPRECATED_SINCE
)
# Only used for DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER
RULE_ADMIN_OR_USER_OR_CLUSTER_USER = (
'rule:admin_or_user_or_cluster_user')
DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER = policy.DeprecatedRule(
name=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
check_str=f"(({RULE_ADMIN_API}) or ({RULE_USER_OR_CLUSTER_USER}))",
deprecated_reason=DEPRECATED_REASON,
deprecated_since=DEPRECATED_SINCE
)
# ==========================================================
rules = [
policy.RuleDefault(
@ -29,14 +96,14 @@ rules = [
name='admin_or_owner',
check_str='is_admin:True or project_id:%(project_id)s'
),
policy.RuleDefault(
name='admin_api',
check_str='rule:context_is_admin'
),
policy.RuleDefault(
name='admin_or_user',
check_str='is_admin:True or user_id:%(user_id)s'
),
policy.RuleDefault(
name='is_user',
check_str='user_id:%(user_id)s'
),
policy.RuleDefault(
name='cluster_user',
check_str='user_id:%(trustee_user_id)s'
@ -44,7 +111,93 @@ rules = [
policy.RuleDefault(
name='deny_cluster_user',
check_str='not domain_id:%(trustee_domain_id)s'
)
),
policy.RuleDefault(
name='project_member',
check_str='role:member and project_id:%(project_id)s'
),
policy.RuleDefault(
name='project_reader',
check_str='role:reader and project_id:%(project_id)s'
),
policy.RuleDefault(
name='admin_or_project_reader',
check_str=f"({RULE_ADMIN_API}) or ({RULE_PROJECT_READER})",
deprecated_rule=DEPRECATED_RULE_ADMIN_OR_OWNER
),
policy.RuleDefault(
name='admin_or_project_member',
check_str=f"({RULE_ADMIN_API}) or ({RULE_PROJECT_MEMBER})",
deprecated_rule=DEPRECATED_RULE_ADMIN_OR_OWNER
),
policy.RuleDefault(
name='admin_or_project_member_user',
check_str=(
f"({RULE_ADMIN_API}) or (({RULE_PROJECT_MEMBER}) and "
f"({RULE_USER}))"
)
),
policy.RuleDefault(
name='user_or_cluster_user',
check_str=(
f"(({RULE_USER}) or ({RULE_CLUSTER_USER}))"
)
),
policy.RuleDefault(
name='admin_or_user_or_cluster_user',
check_str=(
f"(({RULE_ADMIN_API}) or ({RULE_USER_OR_CLUSTER_USER}))"
)
),
policy.RuleDefault(
name='admin_or_project_member_cluster_user',
check_str=(
f"({RULE_ADMIN_API}) or (({RULE_PROJECT_MEMBER}) "
f"and ({RULE_CLUSTER_USER}))"
)
),
policy.RuleDefault(
name='admin_or_project_member_user_or_cluster_user',
check_str=(
f"({RULE_ADMIN_API}) or (({RULE_PROJECT_MEMBER}) and "
f"({RULE_USER_OR_CLUSTER_USER}))"
),
deprecated_rule=DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER
),
policy.RuleDefault(
name='project_member_deny_cluster_user',
check_str=(
f"(({RULE_PROJECT_MEMBER}) and ({RULE_DENY_CLUSTER_USER}))"
),
deprecated_rule=DEPRECATED_DENY_CLUSTER_USER
),
policy.RuleDefault(
name='admin_or_project_member_deny_cluster_user',
check_str=(
f"({RULE_ADMIN_API}) or ({RULE_PROJECT_MEMBER_DENY_CLUSTER_USER})"
)
),
policy.RuleDefault(
name='project_reader_deny_cluster_user',
check_str=(
f"(({RULE_PROJECT_READER}) and ({RULE_DENY_CLUSTER_USER}))"
),
deprecated_rule=DEPRECATED_DENY_CLUSTER_USER
),
policy.RuleDefault(
name='admin_or_project_reader_deny_cluster_user',
check_str=(
f"({RULE_ADMIN_API}) or ({RULE_PROJECT_READER_DENY_CLUSTER_USER})"
)
),
policy.RuleDefault(
name='admin_or_project_reader_user_or_cluster_user',
check_str=(
f"({RULE_ADMIN_API}) or (({RULE_PROJECT_READER}) and "
f"({RULE_USER_OR_CLUSTER_USER}))"
),
deprecated_rule=DEPRECATED_RULE_ADMIN_OR_USER_OR_CLUSTER_USER
),
]

View File

@ -16,13 +16,12 @@ from oslo_policy import policy
from magnum.common.policies import base
CERTIFICATE = 'certificate:%s'
RULE_ADMIN_OR_USER_OR_CLUSTER_USER = base.RULE_ADMIN_OR_USER + " or " + \
base.RULE_CLUSTER_USER
rules = [
policy.DocumentedRuleDefault(
name=CERTIFICATE % 'create',
check_str=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_USER_OR_CLUSTER_USER,
scope_types=["project"],
description='Sign a new certificate by the CA.',
operations=[
{
@ -33,7 +32,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CERTIFICATE % 'get',
check_str=RULE_ADMIN_OR_USER_OR_CLUSTER_USER,
check_str=base.RULE_ADMIN_OR_PROJECT_READER_USER_OR_CLUSTER_USER,
scope_types=["project"],
description='Retrieve CA information about the given cluster.',
operations=[
{
@ -44,7 +44,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CERTIFICATE % 'rotate_ca',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
scope_types=["project"],
description='Rotate the CA certificate on the given cluster.',
operations=[
{

View File

@ -20,7 +20,8 @@ CLUSTER = 'cluster:%s'
rules = [
policy.DocumentedRuleDefault(
name=CLUSTER % 'create',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Create a new cluster.',
operations=[
{
@ -31,7 +32,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'delete',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Delete a cluster.',
operations=[
{
@ -53,7 +55,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'detail',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve a list of clusters with detail.',
operations=[
{
@ -75,7 +78,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'get',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve information about the given cluster.',
operations=[
{
@ -98,7 +102,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'get_all',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve a list of clusters.',
operations=[
{
@ -120,7 +125,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'update',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Update an existing cluster.',
operations=[
{
@ -131,7 +137,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'update_health_status',
check_str=base.RULE_ADMIN_OR_USER + " or " + base.RULE_CLUSTER_USER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER_USER_OR_CLUSTER_USER,
scope_types=["project"],
description='Update the health status of an existing cluster.',
operations=[
{
@ -153,7 +160,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'resize',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Resize an existing cluster.',
operations=[
{
@ -164,7 +172,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER % 'upgrade',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Upgrade an existing cluster.',
operations=[
{

View File

@ -20,18 +20,20 @@ CLUSTER_TEMPLATE = 'clustertemplate:%s'
rules = [
policy.DocumentedRuleDefault(
name=CLUSTER_TEMPLATE % 'create',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Create a new cluster template.',
operations=[
{
'path': '/v1/clustertemplates',
'method': 'POST'
}
]
],
),
policy.DocumentedRuleDefault(
name=CLUSTER_TEMPLATE % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
scope_types=["project"],
description='Delete a cluster template.',
operations=[
{
@ -65,7 +67,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER_TEMPLATE % 'detail',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve a list of cluster templates with detail.',
operations=[
{
@ -76,7 +79,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER_TEMPLATE % 'get',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve information about the given cluster template.',
operations=[
{
@ -99,7 +103,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER_TEMPLATE % 'get_all',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve a list of cluster templates.',
operations=[
{
@ -121,7 +126,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=CLUSTER_TEMPLATE % 'update',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
scope_types=["project"],
description='Update an existing cluster template.',
operations=[
{

View File

@ -20,7 +20,8 @@ FEDERATION = 'federation:%s'
rules = [
policy.DocumentedRuleDefault(
name=FEDERATION % 'create',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Create a new federation.',
operations=[
{
@ -31,7 +32,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=FEDERATION % 'delete',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Delete a federation.',
operations=[
{
@ -42,7 +44,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=FEDERATION % 'detail',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve a list of federations with detail.',
operations=[
{
@ -53,7 +56,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=FEDERATION % 'get',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve information about the given federation.',
operations=[
{
@ -64,7 +68,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=FEDERATION % 'get_all',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_READER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Retrieve a list of federations.',
operations=[
{
@ -75,7 +80,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=FEDERATION % 'update',
check_str=base.RULE_DENY_CLUSTER_USER,
check_str=base.RULE_PROJECT_MEMBER_DENY_CLUSTER_USER,
scope_types=["project"],
description='Update an existing federation.',
operations=[
{

View File

@ -24,7 +24,8 @@ NODEGROUP = 'nodegroup:%s'
rules = [
policy.DocumentedRuleDefault(
name=NODEGROUP % 'get',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_READER,
scope_types=["project"],
description='Retrieve information about the given nodegroup.',
operations=[
{
@ -35,7 +36,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=NODEGROUP % 'get_all',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_READER,
scope_types=["project"],
description='Retrieve a list of nodegroups that belong to a cluster.',
operations=[
{
@ -68,7 +70,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=NODEGROUP % 'create',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
scope_types=["project"],
description='Create a new nodegroup.',
operations=[
{
@ -79,7 +82,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=NODEGROUP % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
scope_types=["project"],
description='Delete a nodegroup.',
operations=[
{
@ -90,7 +94,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=NODEGROUP % 'update',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_MEMBER,
scope_types=["project"],
description='Update an existing nodegroup.',
operations=[
{

View File

@ -42,7 +42,8 @@ rules = [
),
policy.DocumentedRuleDefault(
name=QUOTA % 'get',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_READER,
scope_types=["project"],
description='Retrieve Quota information for the given project_id.',
operations=[
{

View File

@ -20,7 +20,8 @@ STATS = 'stats:%s'
rules = [
policy.DocumentedRuleDefault(
name=STATS % 'get_all',
check_str=base.RULE_ADMIN_OR_OWNER,
check_str=base.RULE_ADMIN_OR_PROJECT_READER,
scope_types=["project"],
description='Retrieve magnum stats.',
operations=[
{

View File

@ -17,6 +17,7 @@
import decorator
from oslo_config import cfg
from oslo_log import log as logging
from oslo_policy import opts
from oslo_policy import policy
from oslo_utils import importutils
@ -27,6 +28,7 @@ from magnum.common import exception
from magnum.common import policies
LOG = logging.getLogger(__name__)
_ENFORCER = None
CONF = cfg.CONF
@ -105,8 +107,14 @@ def enforce(context, rule=None, target=None,
target = {'project_id': context.project_id,
'user_id': context.user_id}
add_policy_attributes(target)
return enforcer.enforce(rule, target, credentials,
do_raise=do_raise, exc=exc, *args, **kwargs)
try:
result = enforcer.enforce(rule, target, credentials,
do_raise=do_raise, exc=exc, *args, **kwargs)
except policy.InvalidScope as ex:
LOG.debug(f"Invalid scope while enforce policy :{str(ex)}")
raise exc(action=rule)
return result
def add_policy_attributes(target):

View File

@ -25,7 +25,7 @@ fakeAuthTokenHeaders = {'X-User-Id': u'773a902f022949619b5c2f32cd89d419',
'X-Roles': 'role1,role2',
'X-Auth-Url': 'fake_auth_url',
'X-Identity-Status': 'Confirmed',
'X-User-Domain-Name': 'domain',
'X-User-Domain-Name': 'user_domain_name',
'X-Project-Domain-Id': 'project_domain_id',
'X-User-Domain-Id': 'user_domain_id',
'OpenStack-API-Version': 'container-infra 1.0'

View File

@ -128,6 +128,9 @@ class FunctionalTest(base.DbTestCase):
with the request
:param status: expected status code of response
"""
# Provide member role for put request
if not headers:
headers = {"X-Roles": "member"}
return self._request_json(path=path, params=params,
expect_errors=expect_errors,
headers=headers, extra_environ=extra_environ,
@ -146,6 +149,9 @@ class FunctionalTest(base.DbTestCase):
with the request
:param status: expected status code of response
"""
# Provide member role for post request
if not headers:
headers = {"X-Roles": "member"}
return self._request_json(path=path, params=params,
expect_errors=expect_errors,
headers=headers, extra_environ=extra_environ,
@ -164,6 +170,9 @@ class FunctionalTest(base.DbTestCase):
with the request
:param status: expected status code of response
"""
# Provide member role for patch request
if not headers:
headers = {"X-Roles": "member"}
return self._request_json(path=path, params=params,
expect_errors=expect_errors,
headers=headers, extra_environ=extra_environ,
@ -184,6 +193,9 @@ class FunctionalTest(base.DbTestCase):
"""
full_path = path_prefix + path
print('DELETE: %s' % (full_path))
# Provide member role for delete request
if not headers:
headers = {"X-Roles": "member"}
response = self.app.delete(str(full_path),
headers=headers,
status=status,
@ -215,6 +227,10 @@ class FunctionalTest(base.DbTestCase):
'q.value': [],
'q.op': [],
}
# Provide reader role for get request
if not headers:
headers = {"X-Roles": "reader"}
for query in q:
for name in ['field', 'op', 'value']:
query_params['q.%s' % name].append(query.get(name, ''))

View File

@ -132,7 +132,9 @@ class TestRootController(api_base.FunctionalTest):
response = app.get('/v1/')
self.assertEqual(self.v1_expected, response.json)
response = app.get('/v1/clustertemplates')
response = app.get('/v1/clustertemplates',
headers={"X-Roles": "reader"}
)
self.assertEqual(200, response.status_int)
def test_auth_with_no_public_routes(self):

View File

@ -21,7 +21,14 @@ from magnum.tests.unit.api import utils as api_utils
from magnum.tests.unit.objects import utils as obj_utils
HEADERS = {'OpenStack-API-Version': 'container-infra latest'}
READER_HEADERS = {
'OpenStack-API-Version': 'container-infra latest',
"X-Roles": "reader"
}
HEADERS = {
'OpenStack-API-Version': 'container-infra latest',
"X-Roles": "member"
}
class TestCertObject(base.TestCase):
@ -59,7 +66,7 @@ class TestGetCaCertificate(api_base.FunctionalTest):
self.conductor_api.get_ca_certificate.return_value = mock_cert
response = self.get_json('/certificates/%s' % self.cluster.uuid,
headers=HEADERS)
headers=READER_HEADERS)
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
self.assertEqual(fake_cert['csr'], response['csr'])
@ -72,7 +79,7 @@ class TestGetCaCertificate(api_base.FunctionalTest):
self.conductor_api.get_ca_certificate.return_value = mock_cert
response = self.get_json('/certificates/%s' % self.cluster.name,
headers=HEADERS)
headers=READER_HEADERS)
self.assertEqual(self.cluster.uuid, response['cluster_uuid'])
self.assertEqual(fake_cert['csr'], response['csr'])
@ -80,7 +87,8 @@ class TestGetCaCertificate(api_base.FunctionalTest):
def test_get_one_by_name_not_found(self):
response = self.get_json('/certificates/not_found',
expect_errors=True, headers=HEADERS)
expect_errors=True,
headers=READER_HEADERS)
self.assertEqual(404, response.status_int)
self.assertEqual('application/json', response.content_type)
@ -93,7 +101,8 @@ class TestGetCaCertificate(api_base.FunctionalTest):
uuid=uuidutils.generate_uuid())
response = self.get_json('/certificates/test_cluster',
expect_errors=True, headers=HEADERS)
expect_errors=True,
headers=READER_HEADERS)
self.assertEqual(409, response.status_int)
self.assertEqual('application/json', response.content_type)
@ -106,7 +115,7 @@ class TestGetCaCertificate(api_base.FunctionalTest):
self.conductor_api.get_ca_certificate.return_value = mock_cert
response = self.get_json('/certificates/%s' % self.cluster.uuid,
headers=HEADERS)
headers=READER_HEADERS)
self.assertIn('links', response.keys())
self.assertEqual(2, len(response['links']))
@ -244,7 +253,7 @@ class TestCertPolicyEnforcement(api_base.FunctionalTest):
self._common_policy_check(
"certificate:get", self.get_json,
'/certificates/%s' % cluster.uuid,
expect_errors=True, headers=HEADERS)
expect_errors=True, headers=READER_HEADERS)
def test_policy_disallow_create(self):
cluster = obj_utils.create_test_cluster(self.context)

View File

@ -494,7 +494,9 @@ class TestPatch(api_base.FunctionalTest):
'/clusters/%s/?rollback=True' % self.cluster_obj.uuid,
[{'path': '/node_count', 'value': node_count,
'op': 'replace'}],
headers={'OpenStack-API-Version': 'container-infra 1.3'})
headers={'OpenStack-API-Version': 'container-infra 1.3',
"X-Roles": "member"
})
self.mock_cluster_update.assert_called_once_with(
mock.ANY, node_count, self.cluster_obj.health_status,
@ -507,7 +509,9 @@ class TestPatch(api_base.FunctionalTest):
'/clusters/%s/?rollback=False' % self.cluster_obj.uuid,
[{'path': '/node_count', 'value': node_count,
'op': 'replace'}],
headers={'OpenStack-API-Version': 'container-infra 1.3'})
headers={'OpenStack-API-Version': 'container-infra 1.3',
"X-Roles": "member"
})
self.mock_cluster_update.assert_called_once_with(
mock.ANY, node_count, self.cluster_obj.health_status,
@ -520,7 +524,9 @@ class TestPatch(api_base.FunctionalTest):
'/clusters/%s' % self.cluster_obj.uuid,
[{'path': '/node_count', 'value': node_count,
'op': 'replace'}],
headers={'OpenStack-API-Version': 'container-infra 1.9'},
headers={'OpenStack-API-Version': 'container-infra 1.9',
"X-Roles": "member"
},
expect_errors=True)
self.assertEqual(400, response.status_code)
@ -531,7 +537,9 @@ class TestPatch(api_base.FunctionalTest):
'/clusters/%s' % self.cluster_obj.uuid,
[{'path': '/node_count', 'value': node_count,
'op': 'replace'}],
headers={'OpenStack-API-Version': 'container-infra 1.10'})
headers={'OpenStack-API-Version': 'container-infra 1.10',
"X-Roles": "member"
})
self.mock_cluster_update.assert_called_once_with(
mock.ANY, node_count, self.cluster_obj.health_status,
@ -708,18 +716,24 @@ class TestPost(api_base.FunctionalTest):
def test_create_cluster_with_zero_node_count_fail(self):
bdict = apiutils.cluster_post_data()
bdict['node_count'] = 0
response = self.post_json('/clusters', bdict, expect_errors=True,
headers={"Openstack-Api-Version":
"container-infra 1.9"})
response = self.post_json(
'/clusters', bdict, expect_errors=True,
headers={
"Openstack-Api-Version": "container-infra 1.9",
"X-Roles": "member"
})
self.assertEqual('application/json', response.content_type)
self.assertEqual(400, response.status_int)
def test_create_cluster_with_zero_node_count(self):
bdict = apiutils.cluster_post_data()
bdict['node_count'] = 0
response = self.post_json('/clusters', bdict,
headers={"Openstack-Api-Version":
"container-infra 1.10"})
response = self.post_json(
'/clusters', bdict,
headers={
"Openstack-Api-Version": "container-infra 1.10",
"X-Roles": "member"
})
self.assertEqual('application/json', response.content_type)
self.assertEqual(202, response.status_int)

View File

@ -46,7 +46,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
{"node_count": new_node_count},
headers={"Openstack-Api-Version":
"container-infra 1.7"})
"container-infra 1.7",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
response = self.get_json('/clusters/%s' % self.cluster_obj.uuid)
@ -69,7 +70,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_resize_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"})
"container-infra 1.9",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
response = self.get_json('/clusters/%s' % self.cluster_obj.uuid)
@ -89,7 +91,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_resize_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"},
"container-infra 1.9",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(400, response.status_code)
@ -106,7 +109,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_resize_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"},
"container-infra 1.9",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(400, response.status_code)
@ -123,7 +127,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_resize_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"},
"container-infra 1.9",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(400, response.status_code)
@ -140,7 +145,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_resize_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"},
"container-infra 1.9",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(400, response.status_code)
@ -157,7 +163,8 @@ class TestClusterResize(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_resize_req,
headers={"Openstack-Api-Version":
"container-infra 1.10"})
"container-infra 1.10",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
@ -195,7 +202,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.8"})
"container-infra 1.8",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
def test_upgrade_cluster_as_admin(self):
@ -226,7 +234,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
'/clusters/%s/actions/upgrade' %
cluster_uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version": "container-infra 1.8"})
headers={"Openstack-Api-Version": "container-infra 1.8",
"X-Roles": "member"})
self.assertEqual(202, response.status_int)
@ -239,7 +248,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"})
"container-infra 1.9",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
def test_upgrade_default_master(self):
@ -251,7 +261,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"})
"container-infra 1.9",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
def test_upgrade_non_default_ng(self):
@ -263,7 +274,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"})
"container-infra 1.9",
"X-Roles": "member"})
self.assertEqual(202, response.status_code)
def test_upgrade_cluster_not_found(self):
@ -273,7 +285,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
response = self.post_json('/clusters/not_there/actions/upgrade',
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.8"},
"container-infra 1.8",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(404, response.status_code)
@ -285,7 +298,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.8"},
"container-infra 1.8",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(404, response.status_code)
@ -298,7 +312,8 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"},
"container-infra 1.9",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(404, response.status_code)
@ -311,6 +326,7 @@ class TestClusterUpgrade(api_base.FunctionalTest):
self.cluster_obj.uuid,
cluster_upgrade_req,
headers={"Openstack-Api-Version":
"container-infra 1.9"},
"container-infra 1.9",
"X-Roles": "member"},
expect_errors=True)
self.assertEqual(409, response.status_code)

View File

@ -47,24 +47,26 @@ class TestNodegroupObject(base.TestCase):
class NodeGroupControllerTest(api_base.FunctionalTest):
headers = {"Openstack-Api-Version": "container-infra latest"}
def _add_headers(self, kwargs):
def _add_headers(self, kwargs, roles=None):
if 'headers' not in kwargs:
kwargs['headers'] = self.headers
if roles:
kwargs['headers']['X-Roles'] = ",".join(roles)
def get_json(self, *args, **kwargs):
self._add_headers(kwargs)
self._add_headers(kwargs, roles=['reader'])
return super(NodeGroupControllerTest, self).get_json(*args, **kwargs)
def post_json(self, *args, **kwargs):
self._add_headers(kwargs)
self._add_headers(kwargs, roles=['member'])
return super(NodeGroupControllerTest, self).post_json(*args, **kwargs)
def delete(self, *args, **kwargs):
self._add_headers(kwargs)
self._add_headers(kwargs, roles=['member'])
return super(NodeGroupControllerTest, self).delete(*args, **kwargs)
def patch_json(self, *args, **kwargs):
self._add_headers(kwargs)
self._add_headers(kwargs, roles=['member'])
return super(NodeGroupControllerTest, self).patch_json(*args, **kwargs)

View File

@ -207,7 +207,7 @@ class TestQuota(api_base.FunctionalTest):
project_id="proj-id-"+str(i))
quota_list.append(quota)
headers = {'X-Project-Id': 'proj-id-2'}
headers = {'X-Project-Id': 'proj-id-2', "X-Roles": "member"}
response = self.get_json('/quotas', headers=headers)
self.assertEqual(1, len(response['quotas']))
self.assertEqual('proj-id-2', response['quotas'][0]['project_id'])

View File

@ -21,7 +21,14 @@ from magnum.tests.unit.objects import utils as obj_utils
class TestStatsController(api_base.FunctionalTest):
def setUp(self):
self.base_headers = {'OpenStack-API-Version': 'container-infra 1.4'}
self.base_headers = {
"X-Roles": "reader",
"OpenStack-API-Version": "container-infra 1.4"
}
self.base_admin_headers = {
"X-Roles": "admin",
"OpenStack-API-Version": "container-infra 1.4"
}
super(TestStatsController, self).setUp()
obj_utils.create_test_cluster_template(self.context)
@ -39,7 +46,7 @@ class TestStatsController(api_base.FunctionalTest):
obj_utils.create_test_cluster(self.context,
project_id=234,
uuid='uuid2')
response = self.get_json('/stats', headers=self.base_headers)
response = self.get_json('/stats', headers=self.base_admin_headers)
expected = {u'clusters': 2, u'nodes': 12}
self.assertEqual(expected, response)
@ -54,7 +61,7 @@ class TestStatsController(api_base.FunctionalTest):
uuid='uuid2')
self.context.is_admin = True
response = self.get_json('/stats?project_id=234',
headers=self.base_headers)
headers=self.base_admin_headers)
expected = {u'clusters': 1, u'nodes': 6}
self.assertEqual(expected, response)
@ -69,7 +76,7 @@ class TestStatsController(api_base.FunctionalTest):
uuid='uuid2')
self.context.is_admin = True
response = self.get_json('/stats?project_id=34',
headers=self.base_headers)
headers=self.base_admin_headers)
expected = {u'clusters': 0, u'nodes': 0}
self.assertEqual(expected, response)

View File

@ -34,7 +34,8 @@ class TestContextHook(base.BaseTestCase):
super(TestContextHook, self).setUp()
self.app = fakes.FakeApp()
def test_context_hook_before_method(self):
@mock.patch("magnum.common.policy.check_is_admin")
def test_context_hook_before_method(self, m_c):
state = mock.Mock(request=fakes.FakePecanRequest())
hook = hooks.ContextHook()
hook.before(state)
@ -51,12 +52,13 @@ class TestContextHook(base.BaseTestCase):
self.assertEqual(fakes.fakeAuthTokenHeaders['X-Roles'],
','.join(ctx.roles))
self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Domain-Name'],
ctx.domain_name)
ctx.user_domain_name)
self.assertEqual(fakes.fakeAuthTokenHeaders['X-User-Domain-Id'],
ctx.domain_id)
ctx.user_domain_id)
self.assertIsNone(ctx.auth_token_info)
def test_context_hook_before_method_auth_info(self):
@mock.patch("magnum.common.policy.check_is_admin")
def test_context_hook_before_method_auth_info(self, c_m):
state = mock.Mock(request=fakes.FakePecanRequest())
state.request.environ['keystone.token_info'] = 'assert_this'
hook = hooks.ContextHook()

View File

@ -19,29 +19,30 @@ from magnum.tests import base
class ContextTestCase(base.TestCase):
def _create_context(self, roles=None):
return magnum_context.RequestContext(auth_token='auth_token1',
auth_url='auth_url1',
domain_id='domain_id1',
domain_name='domain_name1',
user_name='user1',
user_id='user-id1',
project_name='tenant1',
project_id='tenant-id1',
roles=roles,
is_admin=True,
read_only=True,
show_deleted=True,
request_id='request_id1',
trust_id='trust_id1',
auth_token_info='token_info1')
return magnum_context.RequestContext(
auth_token='auth_token1',
auth_url='auth_url1',
user_domain_id='user_domain_id1',
user_domain_name='user_domain_name1',
user_name='user1',
user_id='user-id1',
project_name='tenant1',
project_id='tenant-id1',
roles=roles,
is_admin=True,
read_only=True,
show_deleted=True,
request_id='request_id1',
trust_id='trust_id1',
auth_token_info='token_info1')
def test_context(self):
ctx = self._create_context()
self.assertEqual("auth_token1", ctx.auth_token)
self.assertEqual("auth_url1", ctx.auth_url)
self.assertEqual("domain_id1", ctx.domain_id)
self.assertEqual("domain_name1", ctx.domain_name)
self.assertEqual("user_domain_id1", ctx.user_domain_id)
self.assertEqual("user_domain_name1", ctx.user_domain_name)
self.assertEqual("user1", ctx.user_name)
self.assertEqual("user-id1", ctx.user_id)
self.assertEqual("tenant1", ctx.project_name)
@ -59,8 +60,8 @@ class ContextTestCase(base.TestCase):
self.assertEqual("auth_token1", ctx.auth_token)
self.assertEqual("auth_url1", ctx.auth_url)
self.assertEqual("domain_id1", ctx.domain_id)
self.assertEqual("domain_name1", ctx.domain_name)
self.assertEqual("user_domain_id1", ctx.user_domain_id)
self.assertEqual("user_domain_name1", ctx.user_domain_name)
self.assertEqual("user1", ctx.user_name)
self.assertEqual("user-id1", ctx.user_id)
self.assertEqual("tenant1", ctx.project_name)
@ -80,8 +81,8 @@ class ContextTestCase(base.TestCase):
self.assertEqual(ctx.auth_token, ctx2.auth_token)
self.assertEqual(ctx.auth_url, ctx2.auth_url)
self.assertEqual(ctx.domain_id, ctx2.domain_id)
self.assertEqual(ctx.domain_name, ctx2.domain_name)
self.assertEqual(ctx.user_domain_id, ctx2.user_domain_id)
self.assertEqual(ctx.user_domain_name, ctx2.user_domain_name)
self.assertEqual(ctx.user_name, ctx2.user_name)
self.assertEqual(ctx.user_id, ctx2.user_id)
self.assertEqual(ctx.project_id, ctx2.project_id)

View File

@ -0,0 +1,13 @@
---
upgrade:
- |
The Magnum service now allows enables policies (RBAC) new defaults
and scope checks. These are controlled by the following (default) config
options in ``magnum.conf`` file::
[oslo_policy]
enforce_new_defaults=False
enforce_scope=False
We will change the default to True in 2024.1 (Caracal) cycle.
If you want to enable them then modify both values to True.

View File

@ -28,10 +28,10 @@ oslo.config>=8.1.0 # Apache-2.0
oslo.context>=3.1.0 # Apache-2.0
oslo.db>=8.2.0 # Apache-2.0
oslo.i18n>=5.0.0 # Apache-2.0
oslo.log>=4.2.0 # Apache-2.0
oslo.log>=4.8.0 # Apache-2.0
oslo.messaging>=14.1.0 # Apache-2.0
oslo.middleware>=4.1.0 # Apache-2.0
oslo.policy>=3.6.0 # Apache-2.0
oslo.policy>=3.11.0 # Apache-2.0
oslo.reports>=2.1.0 # Apache-2.0
oslo.serialization>=3.2.0 # Apache-2.0
oslo.service>=2.2.0 # Apache-2.0