Add JSON driver for access rules config

The access rules config driver will read a JSON file that represents
rules for accessing service APIs. This is to support application
credential access rules, which will be checked against the configured
rules upon creation. The name for this new API is borrowed from Istio's
near identical concept[1].

[1] https://istio.io/docs/reference/config/authorization/istio.rbac.v1alpha1/#AccessRule

bp whitelist-extension-for-app-creds

Change-Id: If8b9c1e9df55874052dfd9b99fbcea6e06c1ca35
This commit is contained in:
Colleen Murphy 2019-01-04 14:09:15 +01:00 committed by Colleen Murphy
parent bf15bef678
commit f028ca4edd
14 changed files with 1278 additions and 0 deletions

View File

View File

@ -0,0 +1,31 @@
# Copyright 2019 SUSE Linux GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
@six.add_metaclass(abc.ABCMeta)
class AccessRulesConfigDriverBase(object):
@abc.abstractmethod
def list_access_rules_config(self, service=None):
"""List access rules config."""
raise NotImplementedError() # pragma: no cover
@abc.abstractmethod
def check_access_rule(self, service, request_path, request_method):
"""Check if an access rule exists in config."""
raise NotImplementedError() # pragma: no cover

View File

@ -0,0 +1,160 @@
# Copyright 2019 SUSE Linux GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
from oslo_log import log
from oslo_serialization import jsonutils
from keystone.access_rules_config.backends import base
import keystone.conf
from keystone import exception
CONF = keystone.conf.CONF
LOG = log.getLogger(__name__)
class AccessRulesConfig(base.AccessRulesConfigDriverBase):
"""This backend reads the access rules from a JSON file on disk.
The format of the file is a mapping from service type to rules for that
service type. For example::
{
"identity": [
{
"path": "/v3/users",
"method": "GET"
},
{
"path": "/v3/users",
"method": "POST"
},
{
"path": "/v3/users/*",
"method": "GET"
},
{
"path": "/v3/users/*",
"method": "PATCH"
},
{
"path": "/v3/users/*",
"method": "DELETE"
}
...
],
"image": [
{
"path": "/v2/images",
"method": "GET"
},
...
],
...
}
This will be transmuted in memory to a hash map that looks like this::
{
"identity": {
"GET": [
{
"path": "/v3/users"
},
{
"path": "/v3/users/*"
}
...
],
"POST": [ ... ]
},
...
}
The path may include a wildcard like '*' or '**' or a named wildcard like
{server_id}. An application credential access rule validation request for
a path like "/v3/users/uuid" will match with a configured access rule like
"/v3/users/*" or "/v3/users/{user_id}", and a request for a path like
"/v3/users/uuid/application_credentials/uuid" will match with a configured
access rule like "/v3/users/**".
"""
def __init__(self):
super(AccessRulesConfig, self).__init__()
access_rules_file = CONF.access_rules_config.rules_file
self.access_rules = dict()
self.access_rules_json = dict()
try:
with open(access_rules_file, "rb") as f:
self.access_rules_json = jsonutils.load(f)
except IOError:
LOG.warning('No config file found for access rules, application'
' credential access rules will be unavailable.')
return
except ValueError as e:
raise exception.AccessRulesConfigFileError(error=e)
for service, rules in self.access_rules_json.items():
self.access_rules[service] = dict()
for rule in rules:
try:
self.access_rules[service].setdefault(
rule['method'], []).append({
'path': rule['path']
})
except KeyError as e:
raise exception.AccessRulesConfigFileError(error=e)
def _path_matches(self, request_path, path_pattern):
# The fnmatch module doesn't provide the ability to match * versus **,
# so convert to regex.
# replace {tags} with *
pattern = r'{[^}]*}'
replace = r'*'
path_regex = re.sub(pattern, replace, path_pattern)
# temporarily sub out **
pattern = r'([^\*]*)\*\*([^\*]*)'
replace = r'\1%TMP%\2'
path_regex = re.sub(pattern, replace, path_regex)
# replace * with [^\/]* (all except /)
pattern = r'([^\*]?)\*($|[^\*])'
replace = r'\1[^\/]*\2'
path_regex = re.sub(pattern, replace, path_regex)
# replace ** with .* (includes /)
pattern = r'%TMP%'
replace = '.*'
path_regex = re.sub(pattern, replace, path_regex)
path_regex = r'^%s$' % path_regex
regex = re.compile(path_regex)
return regex.match(request_path)
def list_access_rules_config(self, service=None):
"""List access rules config in human readable form."""
if service:
if service not in self.access_rules_json:
raise exception.AccessRulesConfigNotFound(service=service)
return {service: self.access_rules_json[service]}
return self.access_rules_json
def check_access_rule(self, service, request_path, request_method):
"""Check if an access rule exists in config."""
if (service in self.access_rules
and request_method in self.access_rules[service]):
rules = self.access_rules[service][request_method]
for rule in rules:
if self._path_matches(request_path, rule['path']):
return True
return False

View File

@ -20,6 +20,7 @@ import oslo_messaging
from oslo_middleware import cors
from osprofiler import opts as profiler
from keystone.conf import access_rules_config
from keystone.conf import application_credential
from keystone.conf import assignment
from keystone.conf import auth
@ -58,6 +59,7 @@ CONF = cfg.CONF
conf_modules = [
access_rules_config,
application_credential,
assignment,
auth,

View File

@ -0,0 +1,68 @@
# Copyright 2019 SUSE Linux GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from keystone.conf import utils
driver = cfg.StrOpt(
'driver',
default='json',
help=utils.fmt("""
Entry point for the access rules config backend driver in the
`keystone.access_rules_config` namespace. Keystone only provides a `json`
driver, so there is no reason to change this unless you are providing a custom
entry point.
"""))
caching = cfg.BoolOpt(
'caching',
default=True,
help=utils.fmt("""
Toggle for access rules caching. This has no effect unless global caching is
enabled.
"""))
cache_time = cfg.IntOpt(
'cache_time',
help=utils.fmt("""
Time to cache access rule data in seconds. This has no effect unless global
caching is enabled.
"""))
rules_file = cfg.StrOpt(
'rules_file',
default='/etc/keystone/access_rules.json',
help=utils.fmt("""
Path to access rules configuration. If not present, no access rule
configuration will be loaded and application credential access rules will be
unavailable.
"""))
GROUP_NAME = __name__.split('.')[-1]
ALL_OPTS = [
driver,
caching,
cache_time,
rules_file,
]
def register_opts(conf):
conf.register_opts(ALL_OPTS, group=GROUP_NAME)
def list_opts():
return {GROUP_NAME: ALL_OPTS}

View File

@ -545,6 +545,11 @@ class ApplicationCredentialNotFound(NotFound):
"%(application_credential_id)s.")
class AccessRulesConfigNotFound(NotFound):
message_format = _(
"Could not find access rules config for service %(service)s.")
class Conflict(Error):
message_format = _("Conflict occurred attempting to store %(type)s -"
" %(details)s.")
@ -705,3 +710,8 @@ class CacheDeserializationError(Exception):
'obj': obj, 'data': data
}
)
class AccessRulesConfigFileError(UnexpectedError):
debug_message_format = _(
'Could not parse access rules config file: %(error)s')

View File

@ -0,0 +1,84 @@
# Copyright 2019 SUSE Linux GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import uuid
from keystone.access_rules_config.backends import json as json_driver
from keystone import exception
from keystone.tests import unit
from keystone.tests.unit.ksfixtures import access_rules_config
from keystone.tests.unit.ksfixtures import temporaryfile
class JSONDriverTestCase(unit.TestCase):
"""Tests for validating the access rules config driver."""
def setUp(self):
super(JSONDriverTestCase, self).setUp()
rules_file = '%s/access_rules.json' % unit.TESTCONF
self.useFixture(access_rules_config.AccessRulesConfig(
self.config_fixture, rules_file=rules_file))
self.driver = json_driver.AccessRulesConfig()
def test_invalid_json_raises_error(self):
tmpfile = self.useFixture(temporaryfile.SecureTempFile())
invalid_access_rules = tmpfile.file_name
with open(invalid_access_rules, 'w') as f:
f.write("This is an invalid data")
self.useFixture(access_rules_config.AccessRulesConfig(
self.config_fixture, rules_file=invalid_access_rules))
self.assertRaises(exception.AccessRulesConfigFileError,
json_driver.AccessRulesConfig)
def test_list_access_rules_config(self):
rules = self.driver.list_access_rules_config()
self.assertIn('identity', rules)
self.assertIn('image', rules)
def test_list_access_rules_config_for_service(self):
rules = self.driver.list_access_rules_config(service='image')
self.assertNotIn('identity', rules)
self.assertIn('image', rules)
def test_check_access_rule(self):
result = self.driver.check_access_rule('identity', '/v3/users', 'GET')
self.assertTrue(result)
userid = uuid.uuid4().hex
check_path = '/v3/users/%(userid)s' % {'userid': userid}
result = self.driver.check_access_rule('identity', check_path, 'GET')
self.assertTrue(result)
img = uuid.uuid4().hex
memb = uuid.uuid4().hex
check_path = '/v2/images/%(img)s/members/%(memb)s' % {'img': img,
'memb': memb}
result = self.driver.check_access_rule('image', check_path, 'PUT')
self.assertTrue(result)
result = self.driver.check_access_rule('image', '/servers', 'GET')
self.assertFalse(result)
result = self.driver.check_access_rule('glance', '/v2/images', 'GET')
self.assertFalse(result)
result = self.driver.check_access_rule('image', 'images', 'POST')
self.assertFalse(result)
projectid = uuid.uuid4().hex
check_path = '/v3/%(projectid)s/volumes' % {'projectid': projectid}
result = self.driver.check_access_rule('block-storage', check_path,
'GET')
self.assertTrue(result)
check_path = '/v2/%(projectid)s/volumes' % {'projectid': projectid}
result = self.driver.check_access_rule('block-storage', check_path,
'GET')
self.assertFalse(result)
result = self.driver.check_access_rule('compute', '/v2.1/servers',
'GET')
self.assertTrue(result)

View File

@ -0,0 +1,890 @@
{
"identity": [
{
"path": "/v3/users/*/application_credentials",
"method": "POST"
},
{
"path": "/v3/users/*/application_credentials",
"method": "GET"
},
{
"path": "/v3/users/*/application_credentials/*",
"method": "GET"
},
{
"path": "/v3/users/*/application_credentials/*",
"method": "DELETE"
},
{
"path": "/v3/auth/catalog",
"method": "GET"
},
{
"path": "/v3/auth/projects",
"method": "GET"
},
{
"path": "/v3/auth/domains",
"method": "GET"
},
{
"path": "/v3/auth/system",
"method": "GET"
},
{
"path": "/v3/credentials",
"method": "POST"
},
{
"path": "/v3/credentials",
"method": "GET"
},
{
"path": "/v3/credentials/*",
"method": "GET"
},
{
"path": "/v3/credentials/*",
"method": "PATCH"
},
{
"path": "/v3/credentials/*",
"method": "DELETE"
},
{
"path": "/v3/domains/config/default",
"method": "GET"
},
{
"path": "/v3/domains/config/*/default",
"method": "GET"
},
{
"path": "/v3/domains/config/*/*/default",
"method": "GET"
},
{
"path": "/v3/domains/*/config/*/*",
"method": "GET"
},
{
"path": "/v3/domains/*/config/*/*",
"method": "PATCH"
},
{
"path": "/v3/domains/*/config/*/*",
"method": "DELETE"
},
{
"path": "/v3/domains/*/config/*",
"method": "GET"
},
{
"path": "/v3/domains/*/config/*",
"method": "PATCH"
},
{
"path": "/v3/domains/*/config/*",
"method": "DELETE"
},
{
"path": "/v3/domains/*/config",
"method": "PUT"
},
{
"path": "/v3/domains/*/config",
"method": "GET"
},
{
"path": "/v3/domains/*/config",
"method": "PATCH"
},
{
"path": "/v3/domains/*/config",
"method": "DELETE"
},
{
"path": "/v3/domains",
"method": "GET"
},
{
"path": "/v3/domains",
"method": "POST"
},
{
"path": "/v3/domains/*",
"method": "GET"
},
{
"path": "/v3/domains/*",
"method": "PATCH"
},
{
"path": "/v3/domains/*",
"method": "DELETE"
},
{
"path": "/v3/groups",
"method": "GET"
},
{
"path": "/v3/groups",
"method": "POST"
},
{
"path": "/v3/groups/*",
"method": "GET"
},
{
"path": "/v3/groups/*",
"method": "PATCH"
},
{
"path": "/v3/groups/*",
"method": "DELETE"
},
{
"path": "/v3/groups/*/users",
"method": "GET"
},
{
"path": "/v3/groups/*/users/*",
"method": "PUT"
},
{
"path": "/v3/groups/*/users/*",
"method": "HEAD"
},
{
"path": "/v3/groups/*/users/*",
"method": "DELETE"
},
{
"path": "/v3/OS-INHERIT/domains/*/users/*/roles/*/inherited_to_projects",
"method": "PUT"
},
{
"path": "/v3/OS-INHERIT/domains/*/groups/*/roles/*/inherited_to_projects",
"method": "PUT"
},
{
"path": "/v3/OS-INHERIT/domains/*/users/*/roles/inherited_to_projects",
"method": "GET"
},
{
"path": "/v3/OS-INHERIT/domains/*/groups/*/roles/inherited_to_projects",
"method": "GET"
},
{
"path": "/v3/OS-INHERIT/domains/*/users/*/roles/*/inherited_to_projects",
"method": "HEAD"
},
{
"path": "/v3/OS-INHERIT/domains/*/groups/*/roles/*/inherited_to_projects",
"method": "HEAD"
},
{
"path": "/v3/OS-INHERIT/domains/*/users/*/roles/*/inherited_to_projects",
"method": "DELETE"
},
{
"path": "/v3/OS-INHERIT/domains/*/groups/*/roles/*/inherited_to_projects",
"method": "DELETE"
},
{
"path": "/v3/OS-INHERIT/projects/*/users/*/roles/*/inherited_to_projects",
"method": "PUT"
},
{
"path": "/v3/OS-INHERIT/projects/*/groups/*/roles/*/inherited_to_projects",
"method": "PUT"
},
{
"path": "/v3/OS-INHERIT/projects/*/users/*/roles/*/inherited_to_projects",
"method": "HEAD"
},
{
"path": "/v3/OS-INHERIT/projects/*/groups/*/roles/*/inherited_to_projects",
"method": "HEAD"
},
{
"path": "/v3/OS-INHERIT/projects/*/users/*/roles/*/inherited_to_projects",
"method": "DELETE"
},
{
"path": "/v3/OS-INHERIT/projects/*/groups/*/roles/*/inherited_to_projects",
"method": "DELETE"
},
{
"path": "/v3/role_assignments",
"method": "GET"
},
{
"path": "/v3/auth/tokens/OS-PKI/revoked",
"method": "GET"
},
{
"path": "/v3/policies",
"method": "POST"
},
{
"path": "/v3/policies",
"method": "GET"
},
{
"path": "/v3/policies/*",
"method": "GET"
},
{
"path": "/v3/policies/*",
"method": "PATCH"
},
{
"path": "/v3/policies/*",
"method": "DELETE"
},
{
"path": "/v3/projects/*/tags",
"method": "GET"
},
{
"path": "/v3/projects/*/tags",
"method": "PUT"
},
{
"path": "/v3/projects/*/tags",
"method": "DELETE"
},
{
"path": "/v3/projects/*/tags/*",
"method": "GET"
},
{
"path": "/v3/projects/*/tags/*",
"method": "PUT"
},
{
"path": "/v3/projects/*/tags/*",
"method": "DELETE"
},
{
"path": "/v3/projects",
"method": "GET"
},
{
"path": "/v3/projects",
"method": "POST"
},
{
"path": "/v3/projects/*",
"method": "GET"
},
{
"path": "/v3/projects/*",
"method": "PATCH"
},
{
"path": "/v3/projects/*",
"method": "DELETE"
},
{
"path": "/v3/regions/*",
"method": "GET"
},
{
"path": "/v3/regions/*",
"method": "PATCH"
},
{
"path": "/v3/regions/*",
"method": "DELETE"
},
{
"path": "/v3/regions",
"method": "GET"
},
{
"path": "/v3/regions",
"method": "POST"
},
{
"path": "/v3/roles",
"method": "GET"
},
{
"path": "/v3/roles",
"method": "POST"
},
{
"path": "/v3/roles/*",
"method": "GET"
},
{
"path": "/v3/roles/*",
"method": "PATCH"
},
{
"path": "/v3/roles/*",
"method": "DELETE"
},
{
"path": "/v3/domains/*/groups/*/roles",
"method": "GET"
},
{
"path": "/v3/domains/*/groups/*/roles/*",
"method": "PUT"
},
{
"path": "/v3/domains/*/groups/*/roles/*",
"method": "HEAD"
},
{
"path": "/v3/domains/*/groups/*/roles/*",
"method": "DELETE"
},
{
"path": "/v3/domains/*/users/*/roles",
"method": "GET"
},
{
"path": "/v3/domains/*/users/*/roles/*",
"method": "PUT"
},
{
"path": "/v3/domains/*/users/*/roles/*",
"method": "HEAD"
},
{
"path": "/v3/domains/*/users/*/roles/*",
"method": "DELETE"
},
{
"path": "/v3/projects/*/groups/*/roles",
"method": "GET"
},
{
"path": "/v3/projects/*/groups/*/roles/*",
"method": "PUT"
},
{
"path": "/v3/projects/*/groups/*/roles/*",
"method": "HEAD"
},
{
"path": "/v3/projects/*/groups/*/roles/*",
"method": "DELETE"
},
{
"path": "/v3/projects/*/users/*/roles",
"method": "GET"
},
{
"path": "/v3/projects/*/users/*/roles/*",
"method": "PUT"
},
{
"path": "/v3/projects/*/users/*/roles/*",
"method": "HEAD"
},
{
"path": "/v3/projects/*/users/*/roles/*",
"method": "DELETE"
},
{
"path": "/v3/roles/*/implies",
"method": "GET"
},
{
"path": "/v3/roles/*/implies/*",
"method": "PUT"
},
{
"path": "/v3/roles/*/implies/*",
"method": "GET"
},
{
"path": "/v3/roles/*/implies/*",
"method": "HEAD"
},
{
"path": "/v3/roles/*/implies/*",
"method": "DELETE"
},
{
"path": "/v3/role_assignments",
"method": "GET"
},
{
"path": "/v3/role_inferences",
"method": "GET"
},
{
"path": "/v3/services",
"method": "GET"
},
{
"path": "/v3/services",
"method": "POST"
},
{
"path": "/v3/services/*",
"method": "GET"
},
{
"path": "/v3/services/*",
"method": "PATCH"
},
{
"path": "/v3/services/*",
"method": "DELETE"
},
{
"path": "/v3/endpoints",
"method": "GET"
},
{
"path": "/v3/endpoints",
"method": "POST"
},
{
"path": "/v3/endpoints/*",
"method": "GET"
},
{
"path": "/v3/endpoints/*",
"method": "PATCH"
},
{
"path": "/v3/endpoints/*",
"method": "DELETE"
},
{
"path": "/v3/system/users/*/roles",
"method": "GET"
},
{
"path": "/v3/system/users/*/roles/*",
"method": "PUT"
},
{
"path": "/v3/system/users/*/roles/*",
"method": "HEAD"
},
{
"path": "/v3/system/users/*/roles/*",
"method": "GET"
},
{
"path": "/v3/system/users/*/roles/*",
"method": "DELETE"
},
{
"path": "/v3/system/groups/*/roles",
"method": "GET"
},
{
"path": "/v3/system/groups/*/roles/*",
"method": "PUT"
},
{
"path": "/v3/system/groups/*/roles/*",
"method": "HEAD"
},
{
"path": "/v3/system/groups/*/roles/*",
"method": "GET"
},
{
"path": "/v3/system/groups/*/roles/*",
"method": "DELETE"
},
{
"path": "/v3/registered_limits",
"method": "GET"
},
{
"path": "/v3/registered_limits",
"method": "POST"
},
{
"path": "/v3/registered_limits/*",
"method": "PATCH"
},
{
"path": "/v3/registered_limits/*",
"method": "GET"
},
{
"path": "/v3/registered_limits/*",
"method": "DELETE"
},
{
"path": "/v3/limits/model",
"method": "GET"
},
{
"path": "/v3/limits",
"method": "GET"
},
{
"path": "/v3/limits",
"method": "POST"
},
{
"path": "/v3/limits/*",
"method": "PATCH"
},
{
"path": "/v3/limits/*",
"method": "GET"
},
{
"path": "/v3/limits/*",
"method": "DELETE"
},
{
"path": "/v3/users",
"method": "GET"
},
{
"path": "/v3/users",
"method": "POST"
},
{
"path": "/v3/users/*",
"method": "GET"
},
{
"path": "/v3/users/*",
"method": "PATCH"
},
{
"path": "/v3/users/*",
"method": "DELETE"
},
{
"path": "/v3/users/*/groups",
"method": "GET"
},
{
"path": "/v3/users/*/projects",
"method": "GET"
},
{
"path": "/v3/users/*/password",
"method": "POST"
}
],
"image": [
{
"path": "/v1/images",
"method": "POST"
},
{
"path": "/v1/images",
"method": "GET"
},
{
"path": "/v1/images/detail",
"method": "GET"
},
{
"path": "/v1/images/*",
"method": "PUT"
},
{
"path": "/v1/images/*",
"method": "GET"
},
{
"path": "/v1/images/*",
"method": "HEAD"
},
{
"path": "/v1/images/*",
"method": "DELETE"
},
{
"path": "/v1/images/*/members/*",
"method": "PUT"
},
{
"path": "/v1/images/*/members",
"method": "PUT"
},
{
"path": "/v1/images/*/members/*",
"method": "DELETE"
},
{
"path": "/v1/shared-images/*",
"method": "GET"
},
{
"path": "/v2/images/*/file",
"method": "PUT"
},
{
"path": "/v2/images/*/file",
"method": "GET"
},
{
"path": "/v2/images",
"method": "POST"
},
{
"path": "/v2/images/*",
"method": "GET"
},
{
"path": "/v2/images",
"method": "GET"
},
{
"path": "/v2/images/*",
"method": "PATCH"
},
{
"path": "/v2/images/*",
"method": "DELETE"
},
{
"path": "/v2/images/*/actions/deactivate",
"method": "POST"
},
{
"path": "/v2/images/*/actions/reactivate",
"method": "POST"
},
{
"path": "/v2/schemas/images",
"method": "GET"
},
{
"path": "/v2/schemas/image",
"method": "GET"
},
{
"path": "/v2/schemas/members",
"method": "GET"
},
{
"path": "/v2/schemas/member",
"method": "GET"
},
{
"path": "/v2/images/*/members",
"method": "POST"
},
{
"path": "/v2/images/*/members/*",
"method": "GET"
},
{
"path": "/v2/images/*/members",
"method": "GET"
},
{
"path": "/v2/images/*/members/*",
"method": "PUT"
},
{
"path": "/v2/images/*/members/*",
"method": "DELETE"
},
{
"path": "/v2/images/*/tags/*",
"method": "PUT"
},
{
"path": "/v2/images/*/tags/*",
"method": "DELETE"
},
{
"path": "/v2/metadefs/namespaces/*/objects",
"method": "POST"
},
{
"path": "/v2/metadefs/namespaces/*/objects",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/objects/*",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/objects/*",
"method": "PUT"
},
{
"path": "/v2/metadefs/namespaces/*/objects/*",
"method": "DELETE"
},
{
"path": "/v2/metadefs/namespaces/*/properties",
"method": "POST"
},
{
"path": "/v2/metadefs/namespaces/*/properties",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/properties/*",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/properties/*",
"method": "PUT"
},
{
"path": "/v2/metadefs/namespaces/*/properties/*",
"method": "DELETE"
},
{
"path": "/v2/metadefs/namespaces/*/tags/*",
"method": "POST"
},
{
"path": "/v2/metadefs/namespaces/*/tags/*",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/tags/*",
"method": "PUT"
},
{
"path": "/v2/metadefs/namespaces/*/tags/*",
"method": "DELETE"
},
{
"path": "/v2/metadefs/namespaces/*/tags",
"method": "POST"
},
{
"path": "/v2/metadefs/namespaces/*/tags",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/tags",
"method": "DELETE"
},
{
"path": "/v2/metadefs/namespaces",
"method": "POST"
},
{
"path": "/v2/metadefs/namespaces",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*",
"method": "PUT"
},
{
"path": "/v2/metadefs/namespaces/*",
"method": "DELETE"
},
{
"path": "/v2/metadefs/resource_types",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/resource_types",
"method": "POST"
},
{
"path": "/v2/metadefs/namespaces/*/resource_types",
"method": "GET"
},
{
"path": "/v2/metadefs/namespaces/*/resource_types/*",
"method": "DELETE"
},
{
"path": "/v2/schemas/metadefs/namespace",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/namespaces",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/resource_type",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/resource_types",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/object",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/objects",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/property",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/properties",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/tag",
"method": "GET"
},
{
"path": "/v2/schemas/metadefs/tags",
"method": "GET"
},
{
"path": "/v2/schemas/tasks",
"method": "GET"
},
{
"path": "/v2/schemas/task",
"method": "GET"
},
{
"path": "/v2/tasks",
"method": "POST"
},
{
"path": "/v2/tasks",
"method": "GET"
},
{
"path": "/v2/tasks/*",
"method": "GET"
},
{
"path": "/versions",
"method": "GET"
},
{
"path": "/",
"method": "GET"
}
],
"block-storage": [
{
"path": "/v3/**",
"method": "GET"
}
],
"compute": [
{
"path": "**",
"method": "GET"
}
]
}

View File

@ -11,6 +11,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from keystone.tests.unit.ksfixtures.access_rules_config import AccessRulesConfig # noqa
from keystone.tests.unit.ksfixtures.auth_plugins import ConfigAuthPlugins # noqa
from keystone.tests.unit.ksfixtures.backendloader import BackendLoader # noqa
from keystone.tests.unit.ksfixtures.cache import Cache # noqa

View File

@ -0,0 +1,29 @@
# Copyright 2019 SUSE Linux GmbH
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import fixtures
class AccessRulesConfig(fixtures.Fixture):
"""A fixture for working with JSON access rules config."""
def __init__(self, config_fixture, rules_file=None):
self._config_fixture = config_fixture
self._rules_file = rules_file
def setUp(self):
super(AccessRulesConfig, self).setUp()
self._config_fixture.config(group='access_rules_config',
rules_file=self._rules_file)

View File

@ -176,6 +176,9 @@ keystone.unified_limit.model =
flat = keystone.limit.models.flat:FlatModel
strict_two_level = keystone.limit.models.strict_two_level:StrictTwoLevelModel
keystone.access_rules_config =
json = keystone.access_rules_config.backends.json:AccessRulesConfig
oslo.config.opts =
keystone = keystone.conf.opts:list_opts