From 991989bc04178442b2a6b766a67f7a26e60c08f0 Mon Sep 17 00:00:00 2001 From: Prashanth Pai Date: Wed, 6 Nov 2013 17:30:28 +0530 Subject: Modularize swift-auth CGI script, add unit tests - Moved most of swift-auth CGI script to kerbauth_utils.py - Added unit tests for kerbauth_utils.py - Made MEMCACHE_SERVERS, DEBUG_HEADERS, TOKEN_LIFE as configurable parameters Change-Id: I2e9e9823e8aa99dc2cf41327c55428350c8768dc Signed-off-by: Prashanth Pai Reviewed-on: http://review.gluster.org/6248 Tested-by: Chetan Risbud Reviewed-by: Chetan Risbud Reviewed-by: Luis Pabon Tested-by: Luis Pabon --- swiftkerbauth/__init__.py | 21 +++++++- swiftkerbauth/kerbauth.py | 8 +-- swiftkerbauth/kerbauth_utils.py | 106 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 swiftkerbauth/kerbauth_utils.py (limited to 'swiftkerbauth') diff --git a/swiftkerbauth/__init__.py b/swiftkerbauth/__init__.py index eaa1d88..abdbeaa 100644 --- a/swiftkerbauth/__init__.py +++ b/swiftkerbauth/__init__.py @@ -14,4 +14,23 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "1.0.0" + +from swift.common.utils import readconf, config_true_value + +config_file = {} +try: + config_file = readconf("/etc/swift/proxy-server.conf", + section_name="filter:cache") +except SystemExit: + pass +MEMCACHE_SERVERS = config_file.get('memcache_servers', None) + +config_file = {} +try: + config_file = readconf("/etc/swift/proxy-server.conf", + section_name="filter:kerbauth") +except SystemExit: + pass +TOKEN_LIFE = int(config_file.get('token_life', 86400)) +RESELLER_PREFIX = config_file.get('reseller_prefix', "AUTH_") +DEBUG_HEADERS = config_true_value(config_file.get('debug_headers', 'yes')) diff --git a/swiftkerbauth/kerbauth.py b/swiftkerbauth/kerbauth.py index 612299d..a1ba091 100644 --- a/swiftkerbauth/kerbauth.py +++ b/swiftkerbauth/kerbauth.py @@ -17,12 +17,8 @@ from traceback import format_exc from eventlet import Timeout from swift.common.swob import Request -from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound - -try: - from swift.common.swob import HTTPSeeOther -except ImportError: - from swift.common.swob import HTTPFound as HTTPSeeOther +from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \ + HTTPSeeOther from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed from swift.common.utils import cache_from_env, get_logger, \ diff --git a/swiftkerbauth/kerbauth_utils.py b/swiftkerbauth/kerbauth_utils.py new file mode 100644 index 0000000..507580e --- /dev/null +++ b/swiftkerbauth/kerbauth_utils.py @@ -0,0 +1,106 @@ +# Copyright (c) 2013 Red Hat, Inc. +# +# 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 re +import random +import grp +import subprocess +from time import time +from swiftkerbauth import TOKEN_LIFE, RESELLER_PREFIX + + +def get_remote_user(env): + """Retrieve REMOTE_USER set by Apache from environment.""" + remote_user = env.get('REMOTE_USER', "") + matches = re.match('([^@]+)@.*', remote_user) + if not matches: + raise RuntimeError("Malformed REMOTE_USER \"%s\"" % remote_user) + return matches.group(1) + + +def get_auth_data(mc, username): + """ + Returns the token, expiry time and groups for the user if it already exists + on memcache. Returns None otherwise. + + :param mc: MemcacheRing object + :param username: swift user + """ + token, expires, groups = None, None, None + memcache_user_key = '%s/user/%s' % (RESELLER_PREFIX, username) + candidate_token = mc.get(memcache_user_key) + if candidate_token: + memcache_token_key = '%s/token/%s' % (RESELLER_PREFIX, candidate_token) + cached_auth_data = mc.get(memcache_token_key) + if cached_auth_data: + expires, groups = cached_auth_data + if expires > time(): + token = candidate_token + else: + expires, groups = None, None + return (token, expires, groups) + + +def set_auth_data(mc, username, token, expires, groups): + """ + Stores the following key value pairs on Memcache: + (token, expires+groups) + (user, token) + """ + auth_data = (expires, groups) + memcache_token_key = "%s/token/%s" % (RESELLER_PREFIX, token) + mc.set(memcache_token_key, auth_data, timeout=TOKEN_LIFE) + + # Record the token with the user info for future use. + memcache_user_key = '%s/user/%s' % (RESELLER_PREFIX, username) + mc.set(memcache_user_key, token, timeout=TOKEN_LIFE) + + +def generate_token(): + """Generates a random token.""" + # We don't use uuid.uuid4() here because importing the uuid module + # causes (harmless) SELinux denials in the audit log on RHEL 6. If this + # is a security concern, a custom SELinux policy module could be + # written to not log those denials. + r = random.SystemRandom() + token = '%stk%s' % \ + (RESELLER_PREFIX, + ''.join(r.choice('abcdef0123456789') for x in range(32))) + return token + + +def get_groups(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 + # we wouldn't be able to split the list of group names into its + # elements. + p = subprocess.Popen(['id', '-G', username], stdout=subprocess.PIPE) + if p.wait() != 0: + raise RuntimeError("Failure running id -G for %s" % username) + (p_stdout, p_stderr) = p.communicate() + + # Convert the group numbers into group names. + groups = [] + for gid in p_stdout.strip().split(" "): + groups.append(grp.getgrgid(int(gid))[0]) + + # The first element of the list is considered a unique identifier + # for the user. We add the username to accomplish this. + if username in groups: + groups.remove(username) + groups = [username] + groups + groups = ','.join(groups) + return groups -- cgit