Add ACL support on the API

Right now every authenticated user can perform administration actions on
CloudKitty's API. This patch uses oslo.context and oslo.policy to enable
a policy engine.

Enforcement of the policy will be added in another patchset.

Change-Id: Ia88a37b259f12aee00f65876686d12411297b8fb
This commit is contained in:
Gauvain Pocentek 2015-04-21 17:28:32 +02:00 committed by Stéphane Albert
parent 262b231ce7
commit 1960a4c114
7 changed files with 113 additions and 0 deletions

View File

@ -72,6 +72,7 @@ def setup_app(pecan_config=None, extra_hooks=None):
app_hooks = [
hooks.RPCHook(client),
hooks.StorageHook(storage_backend),
hooks.ContextHook(),
]
app = pecan.make_app(

View File

@ -15,8 +15,11 @@
#
# @author: Stéphane Albert
#
from oslo_context import context
from pecan import hooks
from cloudkitty.common import policy
class RPCHook(hooks.PecanHook):
def __init__(self, rcp_client):
@ -32,3 +35,20 @@ class StorageHook(hooks.PecanHook):
def before(self, state):
state.request.storage_backend = self._storage_backend
class ContextHook(hooks.PecanHook):
def before(self, state):
headers = state.request.headers
roles = headers.get('X-Roles', '').split(',')
is_admin = policy.check_is_admin(roles)
creds = {
'user': headers.get('X-User') or headers.get('X-User-Id'),
'tenant': headers.get('X-Tenant') or headers.get('X-Tenant-Id'),
'auth_token': headers.get('X-Auth-Token'),
'is_admin': is_admin,
}
state.request.context = context.RequestContext(**creds)

View File

@ -0,0 +1,84 @@
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
#
# 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.
# Borrowed from cinder (cinder/policy.py)
from oslo.config import cfg
from oslo_policy import policy
import six
CONF = cfg.CONF
_ENFORCER = None
# TODO(gpocentek): provide a proper parent class to handle such exceptions
class PolicyNotAuthorized(Exception):
message = "Policy doesn't allow %(action)s to be performed."
code = 403
def __init__(self, **kwargs):
self.msg = self.message % kwargs
super(PolicyNotAuthorized, self).__init__(self.msg)
def __unicode__(self):
return six.text_type(self.msg)
def init():
global _ENFORCER
if not _ENFORCER:
_ENFORCER = policy.Enforcer(CONF)
def enforce(context, action, target):
"""Verifies that the action is valid on the target in this context.
:param context: cloudkitty context
:param action: string representing the action to be checked
this should be colon separated for clarity.
i.e. ``compute:create_instance``,
``compute:attach_volume``,
``volume:attach_volume``
:param object: dictionary representing the object of the action
for object creation this should be a dictionary representing the
location of the object e.g. ``{'project_id': context.project_id}``
:raises PolicyNotAuthorized: if verification fails.
"""
init()
return _ENFORCER.enforce(action, target, context.to_dict(),
do_raise=True,
exc=PolicyNotAuthorized,
action=action)
def check_is_admin(roles):
"""Whether or not roles contains 'admin' role according to policy setting.
"""
init()
# include project_id on target to avoid KeyError if context_is_admin
# policy definition is missing, and default admin_or_owner rule
# attempts to apply. Since our credentials dict does not include a
# project_id, this target can never match as a generic rule.
target = {'project_id': ''}
credentials = {'roles': roles}
return _ENFORCER.enforce('context_is_admin', target, credentials)

View File

@ -71,6 +71,7 @@ mkdir -p %{buildroot}/etc/cloudkitty/
#popd
install -p -D -m 640 etc/cloudkitty/cloudkitty.conf.sample %{buildroot}/%{_sysconfdir}/cloudkitty/cloudkitty.conf
install -p -D -m 640 etc/cloudkitty/policy.json %{buildroot}/%{_sysconfdir}/cloudkitty/policy.json
%description
OpenStack Rating-as-a-Service
@ -114,6 +115,7 @@ Components common to all CloudKitty services
%dir %attr(0755,cloudkitty,root) %{_sysconfdir}/cloudkitty
%config(noreplace) %{_sysconfdir}/logrotate.d/cloudkitty
%config(noreplace) %attr(-, root, cloudkitty) %{_sysconfdir}/cloudkitty/cloudkitty.conf
%config(noreplace) %attr(-, root, cloudkitty) %{_sysconfdir}/cloudkitty/policy.json
%pre common
getent group cloudkitty >/dev/null || groupadd -r cloudkitty

View File

@ -137,6 +137,7 @@ function configure_cloudkitty {
sudo mkdir -m 755 -p $CLOUDKITTY_API_LOG_DIR
sudo chown $STACK_USER $CLOUDKITTY_API_LOG_DIR
cp $CLOUDKITTY_DIR$CLOUDKITTY_CONF_DIR/policy.json $CLOUDKITTY_CONF_DIR
cp $CLOUDKITTY_DIR$CLOUDKITTY_CONF.sample $CLOUDKITTY_CONF
iniset_rpc_backend cloudkitty $CLOUDKITTY_CONF DEFAULT

View File

@ -0,0 +1,3 @@
{
"context_is_admin": "role:admin"
}

View File

@ -13,6 +13,8 @@ oslo.messaging
oslo.db
oslo.i18n
oslo.utils
oslo.context
oslo.policy
sqlalchemy
six>=1.7.0
stevedore