From f952c756ad024e100953a43b1f297f82b5c8f3e2 Mon Sep 17 00:00:00 2001 From: Prashanth Pai Date: Thu, 26 Dec 2013 14:24:19 +0530 Subject: Return X-Storage-Url in passive mode When auth_mode is set to 'passive', client can authenticate itself using account, user and key. This enables swiftkerbauth to return X-Storage-Url response header to client. X-Storage-Url contains account name provided in the request. This required a change in X-Storage-User header format from X-Storage-User: user to X-Storage-User: account:user This makes swiftkerbauth(passive mode) handle_get_token APIs to be more consistent with that of swauth and tempauth. Change-Id: Ic1d1520bb8afbc80cca443d92d659436f2f7cd0e Signed-off-by: Prashanth Pai Reviewed-on: http://review.gluster.org/6595 Reviewed-by: Chetan Risbud Tested-by: Chetan Risbud --- apachekerbauth/var/www/cgi-bin/swift-auth | 4 +- doc/AD_server.md | 12 ++++ doc/ipa_server.md | 12 ++++ doc/swiftkerbauth_guide.md | 6 +- swiftkerbauth/kerbauth.py | 98 ++++++++++++++++++++++++------- swiftkerbauth/kerbauth_utils.py | 2 +- test/unit/test_kerbauth.py | 83 +++++++++++++++++++++----- test/unit/test_kerbauth_utils.py | 8 +-- 8 files changed, 179 insertions(+), 46 deletions(-) diff --git a/apachekerbauth/var/www/cgi-bin/swift-auth b/apachekerbauth/var/www/cgi-bin/swift-auth index 45df45c..11fe0e2 100755 --- a/apachekerbauth/var/www/cgi-bin/swift-auth +++ b/apachekerbauth/var/www/cgi-bin/swift-auth @@ -24,7 +24,7 @@ from swift.common.memcached import MemcacheRing from time import time, ctime from swiftkerbauth import MEMCACHE_SERVERS, TOKEN_LIFE, DEBUG_HEADERS from swiftkerbauth.kerbauth_utils import get_remote_user, get_auth_data, \ - generate_token, set_auth_data, get_groups + generate_token, set_auth_data, get_groups_from_username def main(): @@ -48,7 +48,7 @@ def main(): if not token: token = generate_token() expires = time() + TOKEN_LIFE - groups = get_groups(username) + groups = get_groups_from_username(username) set_auth_data(mc, username, token, expires, groups) print "X-Auth-Token: %s" % token diff --git a/doc/AD_server.md b/doc/AD_server.md index c34f0f1..66d90f2 100644 --- a/doc/AD_server.md +++ b/doc/AD_server.md @@ -98,6 +98,18 @@ On client: ###Adding users and groups +The following convention is to be followed in creating group names: + + \_ + + \_ + +As of now, account=volume=group + +For example: + + AUTH\_test + Adding groups and users to the Windows domain is easy task. - Start -> Administrative Tools -> Active Directory Users & Computers diff --git a/doc/ipa_server.md b/doc/ipa_server.md index ef12b53..55e654e 100644 --- a/doc/ipa_server.md +++ b/doc/ipa_server.md @@ -107,6 +107,18 @@ Check if reverse resolution works : ## Adding users and groups +The following convention is to be followed in creating group names: + + \_ + + \_ + +As of now, account=volume=group + +For example: + + AUTH\_test + Create *auth_reseller_admin* user group > ipa group-add auth_reseller_admin --desc="Full access to all Swift accounts" diff --git a/doc/swiftkerbauth_guide.md b/doc/swiftkerbauth_guide.md index e18c7ef..12845a6 100644 --- a/doc/swiftkerbauth_guide.md +++ b/doc/swiftkerbauth_guide.md @@ -103,6 +103,7 @@ Edit */etc/swift/proxy-server.conf* and add a new filter section as follows: [filter:kerbauth] use = egg:swiftkerbauth#kerbauth ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth + auth_mode=passive Add kerbauth to pipeline @@ -438,8 +439,9 @@ The --negotiate option is for curl to perform Kerberos authentication and #### Get an authentication token when auth_mode=passive: -> curl -v -H 'X-Auth-User: auth_admin' -H 'X-Auth-Key: Redhat*123' http://127.0.0.1:8080/auth/v1.0 +> curl -v -H 'X-Auth-User: test:auth_admin' -H 'X-Auth-Key: Redhat*123' http://127.0.0.1:8080/auth/v1.0 +**NOTE**: X-Storage-Url response header can be returned only in passive mode. ##Configurable Parameters @@ -481,7 +483,7 @@ Set this to **"passive"** when you want to allow access to clients residing outside the domain. In this mode, authentication is performed by gleaning username and password from request headers (X-Auth-User and X-Auth-Key) and running kinit command against it. -Default value: active +Default value: passive #### realm_name This is applicable only when the auth_method=passive. This option specifies diff --git a/swiftkerbauth/kerbauth.py b/swiftkerbauth/kerbauth.py index c51dc14..c8b51fb 100644 --- a/swiftkerbauth/kerbauth.py +++ b/swiftkerbauth/kerbauth.py @@ -16,6 +16,7 @@ import errno from time import time, ctime from traceback import format_exc from eventlet import Timeout +from urllib import unquote from swift.common.swob import Request, Response from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \ @@ -26,9 +27,7 @@ from swift.common.utils import cache_from_env, get_logger, \ split_path, config_true_value from swiftkerbauth.kerbauth_utils import get_auth_data, generate_token, \ - set_auth_data, run_kinit -from swiftkerbauth.kerbauth_utils import get_groups as \ - get_groups_from_username + set_auth_data, run_kinit, get_groups_from_username class KerbAuth(object): @@ -77,7 +76,7 @@ class KerbAuth(object): if self.auth_prefix[-1] != '/': self.auth_prefix += '/' self.token_life = int(conf.get('token_life', 86400)) - self.auth_method = conf.get('auth_method', 'active') + self.auth_method = conf.get('auth_method', 'passive') self.debug_headers = config_true_value( conf.get('debug_headers', 'yes')) self.realm_name = conf.get('realm_name', None) @@ -309,16 +308,37 @@ class KerbAuth(object): """ Handles the various `request for token and service end point(s)` calls. There are various formats to support the various auth servers in the - past. Examples:: + past. + + "Active Mode" usage: + All formats require GSS (Kerberos) authentication. GET /v1//auth GET /auth GET /v1.0 - All formats require GSS (Kerberos) authentication. + On successful authentication, the response will have X-Auth-Token + and X-Storage-Token set to the token to use with Swift. + + "Passive Mode" usage:: - On successful authentication, the response will have X-Auth-Token - set to the token to use with Swift. + GET /v1//auth + X-Auth-User: : or X-Storage-User: + X-Auth-Key: or X-Storage-Pass: + GET /auth + X-Auth-User: : or X-Storage-User: : + X-Auth-Key: or X-Storage-Pass: + GET /v1.0 + X-Auth-User: : or X-Storage-User: : + X-Auth-Key: or X-Storage-Pass: + + Values should be url encoded, "act%3Ausr" instead of "act:usr" for + example; however, for backwards compatibility the colon may be + included unencoded. + + On successful authentication, the response will have X-Auth-Token + and X-Storage-Token set to the token to use with Swift and + X-Storage-URL set to the URL to the default Swift cluster to use. :param req: The swob.Request to process. :returns: swob.Response, 2xx on success with data set as explained @@ -340,21 +360,41 @@ class KerbAuth(object): # Client is outside the domain elif self.auth_method == "passive": - user = None - key = None - # Extract username and password from request - user = req.headers.get('x-storage-user') - if not user: - user = req.headers.get('x-auth-user') - key = req.headers.get('x-storage-pass') - if not key: - key = req.headers.get('x-auth-key') - - if (not user) and (not key): - # If both are not given, client may be part of the domain + account, user, key = None, None, None + # Extract user, account and key from request + if pathsegs[0] == 'v1' and pathsegs[2] == 'auth': + account = pathsegs[1] + user = req.headers.get('x-storage-user') + if not user: + user = unquote(req.headers.get('x-auth-user', '')) + if user: + if ':' not in user: + return HTTPUnauthorized(request=req) + else: + account2, user = user.split(':', 1) + if account != account2: + return HTTPUnauthorized(request=req) + key = req.headers.get('x-storage-pass') + if not key: + key = unquote(req.headers.get('x-auth-key', '')) + elif pathsegs[0] in ('auth', 'v1.0'): + user = unquote(req.headers.get('x-auth-user', '')) + if not user: + user = req.headers.get('x-storage-user') + if user: + if ':' not in user: + return HTTPUnauthorized(request=req) + else: + account, user = user.split(':', 1) + key = unquote(req.headers.get('x-auth-key', '')) + if not key: + key = req.headers.get('x-storage-pass') + + if not (account or user or key): + # If all are not given, client may be part of the domain return HTTPSeeOther(location=self.ext_authentication_url) - elif None in (key, user): - # If either user OR key is given, but not both + elif None in (key, user, account): + # If only one or two of them is given, but not all return HTTPUnauthorized(request=req) # Run kinit on the user @@ -372,6 +412,18 @@ class KerbAuth(object): if "@" in user: user = user.split("@")[0] + + # Check if user really belongs to the account + groups_list = get_groups_from_username(user).strip().split(",") + user_group = ("%s%s" % (self.reseller_prefix, account)).lower() + reseller_admin_group = \ + ("%sreseller_admin" % self.reseller_prefix).lower() + if user_group not in groups_list: + # Check if user is reseller_admin. If not, return Unauthorized. + # On AD/IdM server, auth_reseller_admin is a separate group + if reseller_admin_group not in groups_list: + return HTTPUnauthorized(request=req) + mc = cache_from_env(req.environ) if not mc: raise Exception('Memcache required') @@ -392,6 +444,8 @@ class KerbAuth(object): 'X-Debug-Token-Expires': ctime(expires)}) resp = Response(request=req, headers=headers) + resp.headers['X-Storage-Url'] = \ + '%s/v1/%s%s' % (resp.host_url, self.reseller_prefix, account) return resp diff --git a/swiftkerbauth/kerbauth_utils.py b/swiftkerbauth/kerbauth_utils.py index 8490d83..683cdea 100644 --- a/swiftkerbauth/kerbauth_utils.py +++ b/swiftkerbauth/kerbauth_utils.py @@ -81,7 +81,7 @@ def generate_token(): return token -def get_groups(username): +def get_groups_from_username(username): """Return a set of groups to which the user belongs to.""" # Retrieve the numerical group IDs. We cannot list the group names # because group names from Active Directory may contain spaces, and diff --git a/test/unit/test_kerbauth.py b/test/unit/test_kerbauth.py index 471ff58..207558f 100644 --- a/test/unit/test_kerbauth.py +++ b/test/unit/test_kerbauth.py @@ -80,7 +80,8 @@ class TestAuth(unittest.TestCase): patch_filter_factory() def setUp(self): - self.test_auth = auth.filter_factory({})(FakeApp()) + self.test_auth = \ + auth.filter_factory({'auth_method': 'active'})(FakeApp()) self.test_auth_passive = \ auth.filter_factory({'auth_method': 'passive'})(FakeApp()) @@ -273,13 +274,46 @@ class TestAuth(unittest.TestCase): self.assertEquals(resp.status_int, REDIRECT_STATUS) #User given but no key req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'blah'}) + headers={'X-Auth-User': 'test:user'}) + resp = self.test_auth_passive.handle_get_token(req) + self.assertEquals(resp.status_int, 401) + + def test_passive_handle_get_token_account_in_req_path(self): + req = self._make_request('/v1/test/auth', + headers={'X-Auth-User': 'test:user', + 'X-Auth-Key': 'password'}) + _mock_run_kinit = Mock(return_value=0) + _mock_get_groups = Mock(return_value="user,auth_test") + with patch('swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): + with patch('swiftkerbauth.kerbauth.get_groups_from_username', + _mock_get_groups): + resp = self.test_auth_passive.handle_get_token(req) + _mock_run_kinit.assert_called_once_with('user', 'password') + self.assertEquals(_mock_get_groups.call_count, 2) + self.assertEquals(resp.status_int, 200) + self.assertIsNotNone(resp.headers['X-Auth-Token']) + self.assertIsNotNone(resp.headers['X-Storage-Token']) + self.assertIsNotNone(resp.headers['X-Storage-Url']) + + def test_passive_handle_get_token_user_invalid_or_no__account(self): + #X-Auth-User not in acc:user format + req = self._make_request('/auth/v1.0', + headers={'X-Auth-User': 'user'}) + resp = self.test_auth_passive.handle_get_token(req) + self.assertEquals(resp.status_int, 401) + req = self._make_request('/v1/test/auth', + headers={'X-Auth-User': 'user'}) + resp = self.test_auth_passive.handle_get_token(req) + self.assertEquals(resp.status_int, 401) + # Account name mismatch + req = self._make_request('/v1/test/auth', + headers={'X-Auth-User': 'wrongacc:user'}) resp = self.test_auth_passive.handle_get_token(req) self.assertEquals(resp.status_int, 401) def test_passive_handle_get_token_no_kinit(self): req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'user', + headers={'X-Auth-User': 'test:user', 'X-Auth-Key': 'password'}) _mock_run_kinit = Mock(side_effect=OSError(errno.ENOENT, os.strerror(errno.ENOENT))) @@ -291,7 +325,7 @@ class TestAuth(unittest.TestCase): def test_passive_handle_get_token_kinit_fail(self): req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'user', + headers={'X-Auth-User': 'test:user', 'X-Auth-Key': 'password'}) _mock_run_kinit = Mock(return_value=1) with patch('swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): @@ -301,38 +335,57 @@ class TestAuth(unittest.TestCase): def test_passive_handle_get_token_kinit_success_token_not_present(self): req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'user', + headers={'X-Auth-User': 'test:user', 'X-Auth-Key': 'password'}) _mock_run_kinit = Mock(return_value=0) - _mock_get_groups = Mock(return_value="user,admins") + _mock_get_groups = Mock(return_value="user,auth_test") with patch('swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): with patch('swiftkerbauth.kerbauth.get_groups_from_username', _mock_get_groups): resp = self.test_auth_passive.handle_get_token(req) - _mock_run_kinit.assert_called_once_with('user', 'password') _mock_run_kinit.assert_called_once_with('user', 'password') - _mock_get_groups.assert_called_once_with('user') + self.assertEquals(_mock_get_groups.call_count, 2) self.assertEquals(resp.status_int, 200) self.assertIsNotNone(resp.headers['X-Auth-Token']) self.assertIsNotNone(resp.headers['X-Storage-Token']) + self.assertIsNotNone(resp.headers['X-Storage-Url']) def test_passive_handle_get_token_kinit_realm_and_memcache(self): req = self._make_request('/auth/v1.0', - headers={'X-Auth-User': 'user', + headers={'X-Auth-User': 'test:user', 'X-Auth-Key': 'password'}) req.environ['swift.cache'] = None _auth_passive = \ auth.filter_factory({'auth_method': 'passive', 'realm_name': 'EXAMPLE.COM'})(FakeApp()) _mock_run_kinit = Mock(return_value=0) + _mock_get_groups = Mock(return_value="user,auth_test") with patch('swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): - try: - _auth_passive.handle_get_token(req) - except Exception as e: - self.assertTrue(e.args[0].startswith("Memcache required")) - else: - self.fail("Expected Exception - Memcache required") + with patch('swiftkerbauth.kerbauth.get_groups_from_username', + _mock_get_groups): + try: + _auth_passive.handle_get_token(req) + except Exception as e: + self.assertTrue(e.args[0].startswith("Memcache " + "required")) + else: + self.fail("Expected Exception - Memcache required") _mock_run_kinit.assert_called_once_with('user@EXAMPLE.COM', 'password') + _mock_get_groups.assert_called_once_with('user') + + def test_passive_handle_get_token_user_in_any__account(self): + req = self._make_request('/auth/v1.0', + headers={'X-Auth-User': 'test:user', + 'X-Auth-Key': 'password'}) + _mock_run_kinit = Mock(return_value=0) + _mock_get_groups = Mock(return_value="user,auth_blah") + with patch('swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit): + with patch('swiftkerbauth.kerbauth.get_groups_from_username', + _mock_get_groups): + resp = self.test_auth_passive.handle_get_token(req) + self.assertEquals(resp.status_int, 401) + _mock_run_kinit.assert_called_once_with('user', 'password') + _mock_get_groups.assert_called_once_with('user') def test_handle(self): req = self._make_request('/auth/v1.0') diff --git a/test/unit/test_kerbauth_utils.py b/test/unit/test_kerbauth_utils.py index abf8ad0..cb135cf 100644 --- a/test/unit/test_kerbauth_utils.py +++ b/test/unit/test_kerbauth_utils.py @@ -64,13 +64,13 @@ class TestKerbUtils(unittest.TestCase): matches = re.match('AUTH_tk[a-f0-9]{32}', token) self.assertIsNotNone(matches) - def test_get_groups(self): - groups = ku.get_groups("root") + def test_get_groups_from_username(self): + groups = ku.get_groups_from_username("root") self.assertIn("root", groups) - def test_get_groups_err(self): + def test_get_groups_from_username_err(self): try: - ku.get_groups("Zroot") + ku.get_groups_from_username("Zroot") except RuntimeError as err: self.assertTrue(err.args[0].startswith("Failure running id -G")) else: -- cgit