From e577cd60871e1810b45236d3642d60e460dc4858 Mon Sep 17 00:00:00 2001 From: Chmouel Boudjnah Date: Sat, 8 Dec 2012 13:38:45 +0100 Subject: [PATCH] Remove swift auth. - This has been moved since last release to swift main repository. Change-Id: I11fc4001fbc4a1d78823d41450cdfcc97677c420 --- doc/source/configuringservices.rst | 147 +------------- keystone/middleware/swift_auth.py | 295 ---------------------------- tests/test_swift_auth_middleware.py | 249 ----------------------- 3 files changed, 2 insertions(+), 689 deletions(-) delete mode 100644 keystone/middleware/swift_auth.py delete mode 100644 tests/test_swift_auth_middleware.py diff --git a/doc/source/configuringservices.rst b/doc/source/configuringservices.rst index 4dbba55ea4..2a8c14724b 100644 --- a/doc/source/configuringservices.rst +++ b/doc/source/configuringservices.rst @@ -176,151 +176,8 @@ Configuring Swift to use Keystone --------------------------------- Similar to Nova, swift can be configured to use Keystone for authentication -rather than its built in 'tempauth'. - -1. Add a service endpoint for Swift to Keystone - -2. Configure the paste file for swift-proxy (`/etc/swift/swift-proxy.conf`) - -3. Reconfigure Swift's proxy server to use Keystone instead of TempAuth. - Here's an example `/etc/swift/proxy-server.conf`:: - - [DEFAULT] - bind_port = 8888 - user = - - [pipeline:main] - pipeline = catch_errors healthcheck cache authtoken keystone proxy-server - - [app:proxy-server] - use = egg:swift#proxy - account_autocreate = true - - [filter:keystone] - paste.filter_factory = keystone.middleware.swift_auth:filter_factory - operator_roles = admin, swiftoperator - - [filter:authtoken] - paste.filter_factory = keystone.middleware.auth_token:filter_factory - # Delaying the auth decision is required to support token-less - # usage for anonymous referrers ('.r:*') or for tempurl/formpost - # middleware. - delay_auth_decision = 1 - auth_port = 35357 - auth_host = 127.0.0.1 - auth_token = ADMIN - admin_token = ADMIN - - [filter:cache] - use = egg:swift#memcache - set log_name = cache - - [filter:catch_errors] - use = egg:swift#catch_errors - - [filter:healthcheck] - use = egg:swift#healthcheck - -.. Note:: - Your user needs to have the role swiftoperator or admin by default - to be able to operate on an swift account or as specified by the - variable `operator_roles`. - -4. Restart swift - -5. Verify that keystone is providing authentication to Swift - - $ swift -V 2 -A http://localhost:5000/v2.0 -U admin:admin -K ADMIN stat - -.. NOTE:: - Instead of connecting to Swift here, as you would with other services, we - are connecting directly to Keystone. - -Configuring Swift with S3 emulation to use Keystone ---------------------------------------------------- - -Keystone supports validating S3 tokens using the same tokens as the -generated EC2 tokens. When you have generated a pair of EC2 access -token and secret you can access your swift cluster directly with the -S3 API. - -1. Ensure you have defined the S3 service in your `keystone.conf`. First, define the filter as follows:: - - [filter:s3_extension] - paste.filter_factory = keystone.contrib.s3:S3Extension.factory - -Then, ensure that the filter is being called by the admin_api pipeline, as follows:: - - [pipeline:admin_api] - pipeline = token_auth [....] ec2_extension s3_extension [...] - -2. Configure the paste file for swift-proxy - (`/etc/swift/swift-proxy.conf` to use S3token and Swift3 - middleware. - - Here's an example that by default communicates with keystone via https :: - - [DEFAULT] - bind_port = 8080 - user = - - [pipeline:main] - pipeline = catch_errors healthcheck cache swift3 s3token authtoken keystone proxy-server - - [app:proxy-server] - use = egg:swift#proxy - account_autocreate = true - - [filter:catch_errors] - use = egg:swift#catch_errors - - [filter:healthcheck] - use = egg:swift#healthcheck - - [filter:cache] - use = egg:swift#memcache - - [filter:swift3] - use = egg:swift#swift3 - - [filter:keystone] - paste.filter_factory = keystone.middleware.swift_auth:filter_factory - operator_roles = admin, swiftoperator - - [filter:s3token] - paste.filter_factory = keystone.middleware.s3_token:filter_factory - # uncomment the following line if you don't want to use SSL - # auth_protocol = http - auth_port = 35357 - auth_host = 127.0.0.1 - - [filter:authtoken] - paste.filter_factory = keystone.middleware.auth_token:filter_factory - # uncomment the following line if you don't want to use SSL - # auth_protocol = http - auth_port = 35357 - auth_host = 127.0.0.1 - auth_token = ADMIN - admin_token = ADMIN - -3. You can then access directly your Swift via the S3 API, here's an - example with the `boto` library:: - - import boto - import boto.s3.connection - - connection = boto.connect_s3( - aws_access_key_id='', - aws_secret_access_key='', - port=8080, - host='localhost', - is_secure=False, - calling_format=boto.s3.connection.OrdinaryCallingFormat()) - - -.. Note:: - With the S3 middleware you are connecting to the `Swift` proxy and - not to `keystone`. +rather than its built in 'tempauth'. Refer to the `overview_auth` documentation +in Swift. Auth-Token Middleware with Username and Password ------------------------------------------------ diff --git a/keystone/middleware/swift_auth.py b/keystone/middleware/swift_auth.py deleted file mode 100644 index ddcec4af0e..0000000000 --- a/keystone/middleware/swift_auth.py +++ /dev/null @@ -1,295 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 OpenStack LLC -# -# 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. - -# Copyright (c) 2012 OpenStack, LLC. -# -# 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 swift.common.middleware import acl as swift_acl -from swift.common import utils as swift_utils - - -class SwiftAuth(object): - """Swift middleware to Keystone authorization system. - - In Swift's proxy-server.conf add this middleware to your pipeline:: - - [pipeline:main] - pipeline = catch_errors cache authtoken keystone proxy-server - - Make sure you have the authtoken middleware before the swiftauth - middleware. authtoken will take care of validating the user and - swiftauth will authorize access. If support is required for - unvalidated users (as with anonymous access) or for - tempurl/formpost middleware, authtoken will need to be configured with - delay_auth_decision set to 1. See the documentation for more - detail on how to configure the authtoken middleware. - - Set account auto creation to true:: - - [app:proxy-server] - account_autocreate = true - - And add a swift authorization filter section, such as:: - - [filter:keystone] - paste.filter_factory = keystone.middleware.auth_token:filter_factory - operator_roles = admin, swiftoperator - - 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. - - The option is_admin if set to true will allow the - username that has the same name as the account name to be the owner. - - Example: If we have the account called hellocorp with a user - hellocorp that user will be admin on that account and can give ACL - to all other users for hellocorp. - - 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 swiftauth entry like this : - - reseller_prefix = NEWAUTH_ - - Make sure you have a underscore at the end of your new - reseller_prefix option. - - :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='keystoneauth') - self.reseller_prefix = conf.get('reseller_prefix', 'AUTH_').strip() - self.operator_roles = conf.get('operator_roles', - 'admin, swiftoperator') - self.reseller_admin_role = conf.get('reseller_admin_role', - 'ResellerAdmin') - config_is_admin = conf.get('is_admin', "false").lower() - self.is_admin = config_is_admin in swift_utils.TRUE_VALUES - cfg_synchosts = conf.get('allowed_sync_hosts', '127.0.0.1') - self.allowed_sync_hosts = [h.strip() for h in cfg_synchosts.split(',') - if h.strip()] - config_overrides = conf.get('allow_overrides', 't').lower() - self.allow_overrides = config_overrides in swift_utils.TRUE_VALUES - - 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 - else: - self.logger.debug('Authorizing as anonymous') - environ['swift.authorize'] = self.authorize_anonymous - - 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.""" - 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 _get_account_for_tenant(self, tenant_id): - return '%s%s' % (self.reseller_prefix, tenant_id) - - def _reseller_check(self, account, tenant_id): - """Check reseller prefix.""" - return account == self._get_account_for_tenant(tenant_id) - - def authorize(self, req): - env = req.environ - env_identity = env.get('keystone.identity', {}) - tenant_id, tenant_name = env_identity.get('tenant') - - try: - part = swift_utils.split_path(req.path, 1, 4, True) - version, account, container, obj = part - except ValueError: - return webob.exc.HTTPNotFound(request=req) - - user_roles = env_identity.get('roles', []) - - # Give unconditional access to a user with the reseller_admin - # role. - if self.reseller_admin_role in user_roles: - msg = 'User %s has reseller admin authorizing' - self.logger.debug(msg % tenant_id) - req.environ['swift_owner'] = True - return - - # Check if a user tries to access an account that does not match their - # token - if not self._reseller_check(account, tenant_id): - log_msg = 'tenant mismatch: %s != %s' % (account, tenant_id) - self.logger.debug(log_msg) - return self.denied_response(req) - - # Check the roles the user is belonging to. If the user is - # part of the role defined in the config variable - # operator_roles (like admin) then it will be - # promoted as an admin of the account/tenant. - for role in self.operator_roles.split(','): - role = role.strip() - if role in user_roles: - log_msg = 'allow user with role %s as account admin' % (role) - self.logger.debug(log_msg) - req.environ['swift_owner'] = True - return - - # If user is of the same name of the tenant then make owner of it. - user = env_identity.get('user', '') - if self.is_admin and user == tenant_name: - req.environ['swift_owner'] = True - return - - referrers, roles = swift_acl.parse_acl(getattr(req, 'acl', None)) - - authorized = self._authorize_unconfirmed_identity(req, obj, referrers, - roles) - if authorized: - return - elif authorized is not None: - return self.denied_response(req) - - # Allow ACL at individual user level (tenant:user format) - # For backward compatibility, check for ACL in tenant_id:user format - if ('%s:%s' % (tenant_name, user) in roles - or '%s:%s' % (tenant_id, user) in roles): - log_msg = 'user %s:%s or %s:%s allowed in ACL authorizing' - self.logger.debug(log_msg % (tenant_name, user, tenant_id, user)) - return - - # Check if we have the role in the userroles and allow it - for user_role in user_roles: - if user_role in roles: - log_msg = 'user %s:%s allowed in ACL: %s authorizing' - self.logger.debug(log_msg % (tenant_name, user, user_role)) - return - - return self.denied_response(req) - - def authorize_anonymous(self, req): - """ - Authorize an anonymous request. - - :returns: None if authorization is granted, an error page otherwise. - """ - try: - part = swift_utils.split_path(req.path, 1, 4, True) - version, account, container, obj = part - except ValueError: - return webob.exc.HTTPNotFound(request=req) - - is_authoritative_authz = (account and - account.startswith(self.reseller_prefix)) - if not is_authoritative_authz: - return self.denied_response(req) - - referrers, roles = swift_acl.parse_acl(getattr(req, 'acl', None)) - authorized = self._authorize_unconfirmed_identity(req, obj, referrers, - roles) - if not authorized: - return self.denied_response(req) - - def _authorize_unconfirmed_identity(self, req, obj, referrers, roles): - """" - Perform authorization for access that does not require a - confirmed identity. - - :returns: A boolean if authorization is granted or denied. None if - a determination could not be made. - """ - # Allow container sync. - if (req.environ.get('swift_sync_key') - and req.environ['swift_sync_key'] == - req.headers.get('x-container-sync-key', None) - and 'x-timestamp' in req.headers - and (req.remote_addr in self.allowed_sync_hosts - or swift_utils.get_remote_client(req) - in self.allowed_sync_hosts)): - log_msg = 'allowing proxy %s for container-sync' % req.remote_addr - self.logger.debug(log_msg) - return True - - # Check if referrer is allowed. - if swift_acl.referrer_allowed(req.referer, referrers): - if obj or '.rlistings' in roles: - log_msg = 'authorizing %s via referer ACL' % req.referrer - self.logger.debug(log_msg) - return True - return False - - 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 webob.exc.HTTPForbidden(request=req) - else: - return webob.exc.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) - - def auth_filter(app): - return SwiftAuth(app, conf) - return auth_filter diff --git a/tests/test_swift_auth_middleware.py b/tests/test_swift_auth_middleware.py deleted file mode 100644 index 7a82302079..0000000000 --- a/tests/test_swift_auth_middleware.py +++ /dev/null @@ -1,249 +0,0 @@ -# Copyright (c) 2012 OpenStack, LLC. -# -# 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 logging - -import stubout -import unittest2 as unittest -import webob - -from swift.common import utils as swift_utils - -from keystone.middleware import swift_auth - - -def setUpModule(self): - self.stubs = stubout.StubOutForTesting() - - # Stub out swift_utils.get_logger. get_logger tries to configure - # syslogging to '/dev/log', which will fail on OS X. - def fake_get_logger(config, log_route=None): - return logging.getLogger(log_route) - self.stubs.Set(swift_utils, 'get_logger', fake_get_logger) - - -def tearDownModule(self): - self.stubs.UnsetAll() - - -class FakeApp(object): - def __init__(self, status_headers_body_iter=None): - self.calls = 0 - self.status_headers_body_iter = status_headers_body_iter - if not self.status_headers_body_iter: - self.status_headers_body_iter = iter([('404 Not Found', {}, '')]) - - def __call__(self, env, start_response): - self.calls += 1 - self.request = webob.Request.blank('', environ=env) - if 'swift.authorize' in env: - resp = env['swift.authorize'](self.request) - if resp: - return resp(env, start_response) - status, headers, body = self.status_headers_body_iter.next() - return webob.Response(status=status, headers=headers, - body=body)(env, start_response) - - -class SwiftAuth(unittest.TestCase): - def setUp(self): - self.test_auth = swift_auth.filter_factory({})(FakeApp()) - - def _make_request(self, path=None, headers=None, **kwargs): - if not path: - path = '/v1/%s/c/o' % self.test_auth._get_account_for_tenant('foo') - return webob.Request.blank(path, headers=headers, **kwargs) - - def _get_identity_headers(self, status='Confirmed', tenant_id='1', - tenant_name='acct', user='usr', role=''): - return dict(X_IDENTITY_STATUS=status, - X_TENANT_ID=tenant_id, - X_TENANT_NAME=tenant_name, - X_ROLES=role, - X_USER_NAME=user) - - def _get_successful_middleware(self): - response_iter = iter([('200 OK', {}, '')]) - return swift_auth.filter_factory({})(FakeApp(response_iter)) - - def test_confirmed_identity_is_authorized(self): - role = self.test_auth.reseller_admin_role - headers = self._get_identity_headers(role=role) - req = self._make_request('/v1/AUTH_acct/c', headers) - resp = req.get_response(self._get_successful_middleware()) - self.assertEqual(resp.status_int, 200) - - def test_confirmed_identity_is_not_authorized(self): - headers = self._get_identity_headers() - req = self._make_request('/v1/AUTH_acct/c', headers) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 403) - - def test_anonymous_is_authorized_for_permitted_referrer(self): - req = self._make_request(headers={'X_IDENTITY_STATUS': 'Invalid'}) - req.acl = '.r:*' - resp = req.get_response(self._get_successful_middleware()) - self.assertEqual(resp.status_int, 200) - - def test_anonymous_is_not_authorized_for_unknown_reseller_prefix(self): - req = self._make_request(path='/v1/BLAH_foo/c/o', - headers={'X_IDENTITY_STATUS': 'Invalid'}) - resp = req.get_response(self.test_auth) - self.assertEqual(resp.status_int, 401) - - def test_blank_reseller_prefix(self): - conf = {'reseller_prefix': ''} - test_auth = swift_auth.filter_factory(conf)(FakeApp()) - account = tenant_id = 'foo' - self.assertTrue(test_auth._reseller_check(account, tenant_id)) - - def test_override_asked_for_but_not_allowed(self): - conf = {'allow_overrides': 'false'} - self.test_auth = swift_auth.filter_factory(conf)(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 401) - - def test_override_asked_for_and_allowed(self): - conf = {'allow_overrides': 'true'} - self.test_auth = swift_auth.filter_factory(conf)(FakeApp()) - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - - def test_override_default_allowed(self): - req = self._make_request('/v1/AUTH_account', - environ={'swift.authorize_override': True}) - resp = req.get_response(self.test_auth) - self.assertEquals(resp.status_int, 404) - - -class TestAuthorize(unittest.TestCase): - def setUp(self): - self.test_auth = swift_auth.filter_factory({})(FakeApp()) - - def _make_request(self, path, **kwargs): - return webob.Request.blank(path, **kwargs) - - def _get_account(self, identity=None): - if not identity: - identity = self._get_identity() - return self.test_auth._get_account_for_tenant(identity['tenant'][0]) - - def _get_identity(self, tenant_id='tenant_id', - tenant_name='tenant_name', user='user', roles=None): - if not roles: - roles = [] - return dict(tenant=(tenant_id, tenant_name), user=user, roles=roles) - - def _check_authenticate(self, account=None, identity=None, headers=None, - exception=None, acl=None, env=None, path=None): - if not identity: - identity = self._get_identity() - if not account: - account = self._get_account(identity) - if not path: - path = '/v1/%s/c' % account - default_env = {'keystone.identity': identity, - 'REMOTE_USER': identity['tenant']} - if env: - default_env.update(env) - req = self._make_request(path, headers=headers, environ=default_env) - req.acl = acl - result = self.test_auth.authorize(req) - if exception: - self.assertIsInstance(result, exception) - else: - self.assertIsNone(result) - return req - - def test_authorize_fails_for_unauthorized_user(self): - self._check_authenticate(exception=webob.exc.HTTPForbidden) - - def test_authorize_fails_for_invalid_reseller_prefix(self): - self._check_authenticate(account='BLAN_a', - exception=webob.exc.HTTPForbidden) - - def test_authorize_succeeds_for_reseller_admin(self): - roles = [self.test_auth.reseller_admin_role] - identity = self._get_identity(roles=roles) - req = self._check_authenticate(identity=identity) - self.assertTrue(req.environ.get('swift_owner')) - - def test_authorize_succeeds_as_owner_for_operator_role(self): - roles = self.test_auth.operator_roles.split(',')[0] - identity = self._get_identity(roles=roles) - req = self._check_authenticate(identity=identity) - self.assertTrue(req.environ.get('swift_owner')) - - def _check_authorize_for_tenant_owner_match(self, exception=None): - identity = self._get_identity() - identity['user'] = identity['tenant'][1] - req = self._check_authenticate(identity=identity, exception=exception) - expected = bool(exception is None) - self.assertEqual(bool(req.environ.get('swift_owner')), expected) - - def test_authorize_succeeds_as_owner_for_tenant_owner_match(self): - self.test_auth.is_admin = True - self._check_authorize_for_tenant_owner_match() - - def test_authorize_fails_as_owner_for_tenant_owner_match(self): - self.test_auth.is_admin = False - self._check_authorize_for_tenant_owner_match( - exception=webob.exc.HTTPForbidden) - - def test_authorize_succeeds_for_container_sync(self): - env = {'swift_sync_key': 'foo', 'REMOTE_ADDR': '127.0.0.1'} - headers = {'x-container-sync-key': 'foo', 'x-timestamp': None} - self._check_authenticate(env=env, headers=headers) - - def test_authorize_fails_for_invalid_referrer(self): - env = {'HTTP_REFERER': 'http://invalid.com/index.html'} - self._check_authenticate(acl='.r:example.com', env=env, - exception=webob.exc.HTTPForbidden) - - def test_authorize_fails_for_referrer_without_rlistings(self): - env = {'HTTP_REFERER': 'http://example.com/index.html'} - self._check_authenticate(acl='.r:example.com', env=env, - exception=webob.exc.HTTPForbidden) - - def test_authorize_succeeds_for_referrer_with_rlistings(self): - env = {'HTTP_REFERER': 'http://example.com/index.html'} - self._check_authenticate(acl='.r:example.com,.rlistings', env=env) - - def test_authorize_succeeds_for_referrer_with_obj(self): - path = '/v1/%s/c/o' % self._get_account() - env = {'HTTP_REFERER': 'http://example.com/index.html'} - self._check_authenticate(acl='.r:example.com', env=env, path=path) - - def test_authorize_succeeds_for_user_role_in_roles(self): - acl = 'allowme' - identity = self._get_identity(roles=[acl]) - self._check_authenticate(identity=identity, acl=acl) - - def test_authorize_succeeds_for_tenant_name_user_in_roles(self): - identity = self._get_identity() - acl = '%s:%s' % (identity['tenant'][1], identity['user']) - self._check_authenticate(identity=identity, acl=acl) - - def test_authorize_succeeds_for_tenant_id_user_in_roles(self): - identity = self._get_identity() - acl = '%s:%s' % (identity['tenant'][0], identity['user']) - self._check_authenticate(identity=identity, acl=acl) - -if __name__ == '__main__': - unittest.main()