Files
swiftpolicy/swiftpolicy/swiftpolicy.py

274 lines
10 KiB
Python

# Copyright 2012 OpenStack Foundation
#
# 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 swift.common import utils as swift_utils
from swift.common.middleware import acl as swift_acl
from swift.common.swob import HTTPNotFound, HTTPForbidden, HTTPUnauthorized
from swift.common.swob import Request
from swift.common.utils import register_swift_info
from enforcer import get_enforcer
class SwiftPolicy(object):
"""Swift middleware to handle Keystone authorization based
openstack policy.json format
In Swift's proxy-server.conf add this middleware to your pipeline::
[pipeline:main]
pipeline = catch_errors cache authtoken swiftpolicy proxy-server
Make sure you have the authtoken middleware before the
swiftpolicy middleware.
The authtoken middleware will take care of validating the user and
swiftpolicy will authorize access.
The authtoken middleware is shipped directly with keystone it
does not have any other dependences than itself so you can either
install it by copying the file directly in your python path or by
installing keystone.
If support is required for unvalidated users (as with anonymous
access) or for formpost/staticweb/tempurl middleware, authtoken will
need to be configured with ``delay_auth_decision`` set to true. See
the Keystone documentation for more detail on how to configure the
authtoken middleware.
In proxy-server.conf you will need to have the setting account
auto creation to true::
[app:proxy-server]
account_autocreate = true
And add a swift authorization filter section, such as::
[filter:swiftpolicy]
use = egg:swiftpolicy#swiftpolicy
operator_roles = admin, swiftoperator
policy = /path/to/policy.json
This maps tenants to account in Swift.
The user whose able to give ACL / create Containers permissions
will be the one that are inside the ``operator_roles``
setting which by default includes the admin and the swiftoperator
roles.
If you need to have a different reseller_prefix to be able to
mix different auth servers you can configure the option
``reseller_prefix`` in your swiftpolicy entry like this::
reseller_prefix = NEWAUTH
:param app: The next WSGI app in the pipeline
:param conf: The dict of configuration values
"""
def __init__(self, app, conf):
self.app = app
self.conf = conf
self.logger = swift_utils.get_logger(conf, log_route='swiftpolicy')
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH_').strip()
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
self.reseller_prefix += '_'
self.operator_roles = conf.get('operator_roles',
'admin, swiftoperator').lower()
self.reseller_admin_role = conf.get('reseller_admin_role',
'ResellerAdmin').lower()
config_is_admin = conf.get('is_admin', "false").lower()
self.is_admin = swift_utils.config_true_value(config_is_admin)
config_overrides = conf.get('allow_overrides', 't').lower()
self.allow_overrides = swift_utils.config_true_value(config_overrides)
self.policy_file = conf.get('policy', None)
def __call__(self, environ, start_response):
identity = self._keystone_identity(environ)
# Check if one of the middleware like tempurl or formpost have
# set the swift.authorize_override environ and want to control the
# authentication
if (self.allow_overrides and
environ.get('swift.authorize_override', False)):
msg = 'Authorizing from an overriding middleware (i.e: tempurl)'
self.logger.debug(msg)
return self.app(environ, start_response)
if identity:
self.logger.debug('Using identity: %r', identity)
environ['keystone.identity'] = identity
environ['REMOTE_USER'] = identity.get('tenant')
environ['swift.authorize'] = self.authorize
# Check reseller_request again poicy
if self.check_action('reseller_request', environ):
environ['reseller_request'] = True
else:
self.logger.debug('Authorizing as anonymous')
environ['swift.authorize'] = self.authorize
environ['swift.clean_acl'] = swift_acl.clean_acl
return self.app(environ, start_response)
def _keystone_identity(self, environ):
"""Extract the identity from the Keystone auth component."""
# In next release, we would add user id in env['keystone.identity'] by
# using _integral_keystone_identity to replace current
# _keystone_identity. The purpose of keeping it in this release it for
# back compatibility.
if environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed':
return
roles = []
if 'HTTP_X_ROLES' in environ:
roles = environ['HTTP_X_ROLES'].split(',')
identity = {'user': environ.get('HTTP_X_USER_NAME'),
'tenant': (environ.get('HTTP_X_TENANT_ID'),
environ.get('HTTP_X_TENANT_NAME')),
'roles': roles}
return identity
def _integral_keystone_identity(self, environ):
"""Extract the identity from the Keystone auth component."""
if environ.get('HTTP_X_IDENTITY_STATUS') != 'Confirmed':
return
roles = []
if 'HTTP_X_ROLES' in environ:
roles = environ['HTTP_X_ROLES'].split(',')
identity = {'user': (environ.get('HTTP_X_USER_ID'),
environ.get('HTTP_X_USER_NAME')),
'tenant': (environ.get('HTTP_X_TENANT_ID'),
environ.get('HTTP_X_TENANT_NAME')),
'roles': roles}
return identity
def _get_account_for_tenant(self, tenant_id):
return '%s%s' % (self.reseller_prefix, tenant_id)
def get_creds(self, environ):
req = Request(environ)
try:
parts = req.split_path(1, 4, True)
_, account, _, _ = parts
except ValueError:
account = None
env_identity = self._integral_keystone_identity(environ)
if not env_identity:
# user identity is not confirmed. (anonymous?)
creds = {
'identity': None,
'is_authoritative': (account and
account.startswith(self.reseller_prefix))
}
return creds
tenant_id, tenant_name = env_identity['tenant']
user_id, user_name = env_identity['user']
roles = [r.strip() for r in env_identity.get('roles', [])]
account = self._get_account_for_tenant(tenant_id)
is_admin = (tenant_name == user_name)
creds = {
"identity": env_identity,
"roles": roles,
"account": account,
"tenant_id": tenant_id,
"tenant_name": tenant_name,
"user_id": user_id,
"user_name": user_name,
"is_admin": is_admin
}
return creds
def get_target(self, environ):
req = Request(environ)
try:
parts = req.split_path(1, 4, True)
version, account, container, obj = parts
except ValueError:
version = account = container = obj = None
referrers, acls = swift_acl.parse_acl(getattr(req, 'acl', None))
target = {
"req": req,
"method": req.method.lower(),
"version": version,
"account": account,
"container": container,
"object": obj,
"acls": acls,
"referrers": referrers
}
return target
@staticmethod
def get_action(method, parts):
version, account, container, obj = parts
action = method.lower() + "_"
if obj:
action += "object"
elif container:
action += "container"
elif account:
action += "account"
return action
def check_action(self, action, environ):
creds = self.get_creds(environ)
target = self.get_target(environ)
enforcer = get_enforcer(self.operator_roles,
self.reseller_admin_role,
self.is_admin,
self.logger,
self.policy_file)
self.logger.debug("enforce action '%s'", action)
return enforcer.enforce(action, target, creds)
def authorize(self, req):
try:
parts = req.split_path(1, 4, True)
except ValueError:
return HTTPNotFound(request=req)
env = req.environ
action = self.get_action(req.method, parts)
if self.check_action(action, env):
if self.check_action("swift_owner", env):
req.environ['swift_owner'] = True
return
return self.denied_response(req)
def denied_response(self, req):
"""Deny WSGI Response.
Returns a standard WSGI response callable with the status of 403 or 401
depending on whether the REMOTE_USER is set or not.
"""
if req.remote_user:
return HTTPForbidden(request=req)
else:
return HTTPUnauthorized(request=req)
def filter_factory(global_conf, **local_conf):
"""Returns a WSGI filter app for use with paste.deploy."""
conf = global_conf.copy()
conf.update(local_conf)
register_swift_info('swiftpolicy')
def auth_filter(app):
return SwiftPolicy(app, conf)
return auth_filter