#!/usr/bin/python # 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. # Requires the following command to be run: # setsebool -P httpd_can_network_connect 1 # setsebool -P httpd_can_network_memcache 1 import cgi from swift.common.memcached import MemcacheRing import os import grp import random import re import subprocess from time import time, ctime # After how many seconds the cached information about an authentication # token is discarded. TOKEN_LIFE = 86400 # This is used as a prefix for tokens and memcache keys. We use the default # value from the Swift tempauth filter. In the future, this should be turned # into a configuration parameter. RESELLER_PREFIX = 'AUTH_' MEMCACHE_SERVERS = ['127.0.0.1:11211'] DEBUG_HEADERS = True def main(): remote_user = os.environ['REMOTE_USER'] matches = re.match('([^@]+)@.*', remote_user) if not matches: raise RuntimeError("Malformed REMOTE_USER \"%s\"" % remote_user) username = matches.group(1) mc = MemcacheRing(MEMCACHE_SERVERS) # Check if we already got a token for this user. memcache_user_key = '%s/user/%s' % (RESELLER_PREFIX, username) token = None 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 if not 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))) # 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" % remote_user) (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) expires = time() + TOKEN_LIFE 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) print "X-Auth-Token: %s" % token # For debugging. if DEBUG_HEADERS: print "X-Debug-Remote-User: %s" % username print "X-Debug-Groups: %s" % groups print "X-Debug-Token-Life: %ss" % TOKEN_LIFE print "X-Debug-Token-Expires: %s" % ctime(expires) print "" try: print("Content-Type: text/html") main() except: cgi.print_exception()