rest: introduce auth_helper to filter resources
This introduce a new plugin system called auth_helper, that can be used to decide how to filter resources depending on the authentication done. That does not change the current filtering rules applied with Keystone authentication, but it removes the artificial filtering that was done when no authentication method was configured. Change-Id: I8b6834a10812f16aed808d3a219be9fd86214f4e
This commit is contained in:
parent
98a49616c2
commit
da0498e3f6
|
@ -58,8 +58,8 @@ Gnocchi provides these indexer drivers:
|
|||
Configuring authentication
|
||||
-----------------------------
|
||||
|
||||
The API server supports different authentication methods. `OpenStack Keystone`_
|
||||
is supported but by default, no authentication is enabled. If you successfully
|
||||
The API server supports different authentication methods: `noauth` (the
|
||||
default) or `keystone` to use `OpenStack Keystone`_. If you successfully
|
||||
installed the `keystone` flavor using `pip` (see :ref:`installation`), you can
|
||||
set `api.auth_mode` to `keystone` to enable Keystone authentication.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import uuid
|
|||
|
||||
from oslo_config import cfg
|
||||
from oslo_middleware import cors
|
||||
from stevedore import extension
|
||||
|
||||
import gnocchi.archive_policy
|
||||
import gnocchi.indexer
|
||||
|
@ -69,6 +70,9 @@ def list_opts():
|
|||
"rest", "api-paste.ini")),
|
||||
help='Path to API Paste configuration.'),
|
||||
cfg.StrOpt('auth_mode',
|
||||
default="noauth",
|
||||
choices=extension.ExtensionManager(
|
||||
"gnocchi.rest.auth_helper").names(),
|
||||
help='Authentication mode to use.'),
|
||||
cfg.IntOpt('max_limit',
|
||||
default=1000,
|
||||
|
|
|
@ -90,44 +90,6 @@ def enforce(rule, target):
|
|||
abort(403)
|
||||
|
||||
|
||||
def _get_list_resource_policy_filter(rule, resource_type, user, project):
|
||||
try:
|
||||
# Check if the policy allows the user to list any resource
|
||||
enforce(rule, {
|
||||
"resource_type": resource_type,
|
||||
})
|
||||
except webob.exc.HTTPForbidden:
|
||||
policy_filter = []
|
||||
try:
|
||||
# Check if the policy allows the user to list resources linked
|
||||
# to their project
|
||||
enforce(rule, {
|
||||
"resource_type": resource_type,
|
||||
"project_id": project,
|
||||
})
|
||||
except webob.exc.HTTPForbidden:
|
||||
pass
|
||||
else:
|
||||
policy_filter.append({"=": {"project_id": project}})
|
||||
try:
|
||||
# Check if the policy allows the user to list resources linked
|
||||
# to their created_by_project
|
||||
enforce(rule, {
|
||||
"resource_type": resource_type,
|
||||
"created_by_project_id": project,
|
||||
})
|
||||
except webob.exc.HTTPForbidden:
|
||||
pass
|
||||
else:
|
||||
policy_filter.append({"=": {"created_by_project_id": project}})
|
||||
|
||||
if not policy_filter:
|
||||
# We need to have at least one policy filter in place
|
||||
abort(403, "Insufficient privileges")
|
||||
|
||||
return {"or": policy_filter}
|
||||
|
||||
|
||||
def set_resp_location_hdr(location):
|
||||
location = '%s%s' % (pecan.request.script_name, location)
|
||||
# NOTE(sileht): according the pep-3333 the headers must be
|
||||
|
@ -1035,9 +997,8 @@ class ResourcesController(rest.RestController):
|
|||
history = get_history(kwargs)
|
||||
pagination_opts = get_pagination_options(
|
||||
kwargs, RESOURCE_DEFAULT_PAGINATION)
|
||||
user, project = get_user_and_project()
|
||||
policy_filter = _get_list_resource_policy_filter(
|
||||
"list resource", self._resource_type, user, project)
|
||||
policy_filter = pecan.request.auth_helper.get_resource_policy_filter(
|
||||
"list resource", self._resource_type)
|
||||
|
||||
try:
|
||||
# FIXME(sileht): next API version should returns
|
||||
|
@ -1065,9 +1026,8 @@ class ResourcesController(rest.RestController):
|
|||
abort(400, "caution: the query can not be empty, or it will \
|
||||
delete entire database")
|
||||
|
||||
user, project = get_user_and_project()
|
||||
policy_filter = _get_list_resource_policy_filter(
|
||||
"delete resources", self._resource_type, user, project)
|
||||
policy_filter = pecan.request.auth_helper.get_resource_policy_filter(
|
||||
"delete resources", self._resource_type)
|
||||
|
||||
if policy_filter:
|
||||
attr_filter = {"and": [policy_filter, attr_filter]}
|
||||
|
@ -1238,9 +1198,8 @@ class SearchResourceTypeController(rest.RestController):
|
|||
pagination_opts = get_pagination_options(
|
||||
kwargs, RESOURCE_DEFAULT_PAGINATION)
|
||||
|
||||
user, project = get_user_and_project()
|
||||
policy_filter = _get_list_resource_policy_filter(
|
||||
"search resource", self._resource_type, user, project)
|
||||
policy_filter = pecan.request.auth_helper.get_resource_policy_filter(
|
||||
"search resource", self._resource_type)
|
||||
if policy_filter:
|
||||
if attr_filter:
|
||||
attr_filter = {"and": [
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[composite:gnocchi]
|
||||
[composite:gnocchi+noauth]
|
||||
use = egg:Paste#urlmap
|
||||
/ = gnocchiversions_pipeline
|
||||
/v1 = gnocchiv1+noauth
|
||||
|
|
|
@ -23,6 +23,7 @@ from oslo_policy import policy
|
|||
from paste import deploy
|
||||
import pecan
|
||||
from pecan import jsonify
|
||||
from stevedore import driver
|
||||
import webob.exc
|
||||
|
||||
from gnocchi import exceptions
|
||||
|
@ -46,12 +47,16 @@ class GnocchiHook(pecan.hooks.PecanHook):
|
|||
self.indexer = indexer
|
||||
self.conf = conf
|
||||
self.policy_enforcer = policy.Enforcer(conf)
|
||||
self.auth_helper = driver.DriverManager("gnocchi.rest.auth_helper",
|
||||
conf.api.auth_mode,
|
||||
invoke_on_load=True).driver
|
||||
|
||||
def on_route(self, state):
|
||||
state.request.storage = self.storage
|
||||
state.request.indexer = self.indexer
|
||||
state.request.conf = self.conf
|
||||
state.request.policy_enforcer = self.policy_enforcer
|
||||
state.request.auth_helper = self.auth_helper
|
||||
|
||||
|
||||
class NotImplementedMiddleware(object):
|
||||
|
@ -102,8 +107,7 @@ def load_app(conf, indexer=None, storage=None,
|
|||
APPCONFIGS[configkey] = config
|
||||
|
||||
LOG.info("WSGI config used: %s", cfg_path)
|
||||
appname = "gnocchi" + ("+" + conf.api.auth_mode
|
||||
if conf.api.auth_mode else "")
|
||||
appname = "gnocchi+" + conf.api.auth_mode
|
||||
app = deploy.loadapp("config:" + cfg_path, name=appname,
|
||||
global_conf={'configkey': configkey})
|
||||
return cors.CORS(app, conf=conf)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
# Copyright © 2016 Red Hat, Inc.
|
||||
# Copyright © 2014-2015 eNovance
|
||||
#
|
||||
# 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 webob
|
||||
|
||||
from gnocchi import rest
|
||||
|
||||
|
||||
class KeystoneAuthHelper(object):
|
||||
@staticmethod
|
||||
def get_resource_policy_filter(rule, resource_type):
|
||||
try:
|
||||
# Check if the policy allows the user to list any resource
|
||||
rest.enforce(rule, {
|
||||
"resource_type": resource_type,
|
||||
})
|
||||
except webob.exc.HTTPForbidden:
|
||||
user, project = rest.get_user_and_project()
|
||||
policy_filter = []
|
||||
try:
|
||||
# Check if the policy allows the user to list resources linked
|
||||
# to their project
|
||||
rest.enforce(rule, {
|
||||
"resource_type": resource_type,
|
||||
"project_id": project,
|
||||
})
|
||||
except webob.exc.HTTPForbidden:
|
||||
pass
|
||||
else:
|
||||
policy_filter.append({"=": {"project_id": project}})
|
||||
try:
|
||||
# Check if the policy allows the user to list resources linked
|
||||
# to their created_by_project
|
||||
rest.enforce(rule, {
|
||||
"resource_type": resource_type,
|
||||
"created_by_project_id": project,
|
||||
})
|
||||
except webob.exc.HTTPForbidden:
|
||||
pass
|
||||
else:
|
||||
policy_filter.append({"=": {"created_by_project_id": project}})
|
||||
|
||||
if not policy_filter:
|
||||
# We need to have at least one policy filter in place
|
||||
rest.abort(403, "Insufficient privileges")
|
||||
|
||||
return {"or": policy_filter}
|
||||
|
||||
|
||||
NoAuthHelper = KeystoneAuthHelper
|
|
@ -112,7 +112,7 @@ class ConfigFixture(fixture.GabbiFixture):
|
|||
# Set pagination to a testable value
|
||||
conf.set_override('max_limit', 7, 'api')
|
||||
# Those tests do not use any auth
|
||||
conf.set_override("auth_mode", None, 'api')
|
||||
conf.set_override("auth_mode", "noauth", 'api')
|
||||
|
||||
self.index = index
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ class RestTest(tests_base.TestCase, testscenarios.TestWithScenarios):
|
|||
if self.auth:
|
||||
self.conf.set_override("auth_mode", "keystone", group="api")
|
||||
else:
|
||||
self.conf.set_override("auth_mode", None, group="api")
|
||||
self.conf.set_override("auth_mode", "noauth", group="api")
|
||||
|
||||
self.app = TestingApp(app.load_app(conf=self.conf,
|
||||
indexer=self.index,
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
features:
|
||||
- >-
|
||||
The REST API authentication mechanism is now pluggable. You can write your
|
||||
own plugin to specify how segregation and policy should be enforced.
|
|
@ -122,6 +122,10 @@ gnocchi.indexer =
|
|||
gnocchi.aggregates =
|
||||
moving-average = gnocchi.aggregates.moving_stats:MovingAverage
|
||||
|
||||
gnocchi.rest.auth_helper =
|
||||
noauth = gnocchi.rest.auth_helper:NoAuthHelper
|
||||
keystone = gnocchi.rest.auth_helper:KeystoneAuthHelper
|
||||
|
||||
console_scripts =
|
||||
gnocchi-upgrade = gnocchi.cli:upgrade
|
||||
gnocchi-statsd = gnocchi.cli:statsd
|
||||
|
|
Loading…
Reference in New Issue