diff options
Diffstat (limited to 'apachekerbauth')
-rw-r--r-- | apachekerbauth/apachekerbauth.spec | 50 | ||||
-rw-r--r-- | apachekerbauth/apachekerbauth/var/www/cgi-bin/memcached.py | 318 | ||||
-rwxr-xr-x | apachekerbauth/build.sh | 7 | ||||
-rw-r--r-- | apachekerbauth/etc/httpd/conf.d/swift-auth.conf (renamed from apachekerbauth/apachekerbauth/etc/httpd/conf.d/swift-auth.conf) | 7 | ||||
-rwxr-xr-x | apachekerbauth/var/www/cgi-bin/swift-auth (renamed from apachekerbauth/apachekerbauth/var/www/cgi-bin/swift-auth) | 80 |
5 files changed, 48 insertions, 414 deletions
diff --git a/apachekerbauth/apachekerbauth.spec b/apachekerbauth/apachekerbauth.spec deleted file mode 100644 index cc6210a..0000000 --- a/apachekerbauth/apachekerbauth.spec +++ /dev/null @@ -1,50 +0,0 @@ -Name: apachekerbauth -Version: 1.0 -Release: 3 -Summary: Kerberos authentication filter for Swift - -Group: System Environment/Base -License: GPL -Source: %{name}.tar.gz -BuildRoot: %{_tmppath}/%{name}-root - -Requires: httpd >= 2.2.15 -Requires: mod_auth_kerb >= 5.4 - -%description -Python CGI script which is used by the swiftkerbauth package to -authenticate client requests using Kerberos. - -%prep -%setup -q -n %{name} - -%build - -%install -rm -rf $RPM_BUILD_ROOT - -mkdir -p \ - $RPM_BUILD_ROOT/etc/httpd/conf.d \ - $RPM_BUILD_ROOT/var/www/cgi-bin - -install -m 644 etc/httpd/conf.d/* \ - $RPM_BUILD_ROOT/etc/httpd/conf.d - -install -m 644 var/www/cgi-bin/memcached.py \ - $RPM_BUILD_ROOT/var/www/cgi-bin - -install var/www/cgi-bin/swift-auth \ - $RPM_BUILD_ROOT/var/www/cgi-bin - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%defattr(-,root,root,-) -%config /etc/httpd/conf.d/swift-auth.conf -/var/www/cgi-bin/memcached.py -/var/www/cgi-bin/swift-auth - -%changelog -* Fri Apr 5 2013 Carsten Clasohm <clasohm@redhat.com> - 1.0-1 -- initial build diff --git a/apachekerbauth/apachekerbauth/var/www/cgi-bin/memcached.py b/apachekerbauth/apachekerbauth/var/www/cgi-bin/memcached.py deleted file mode 100644 index ecd9332..0000000 --- a/apachekerbauth/apachekerbauth/var/www/cgi-bin/memcached.py +++ /dev/null @@ -1,318 +0,0 @@ -# Copyright (c) 2010-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. - -""" -Lucid comes with memcached: v1.4.2. Protocol documentation for that -version is at: - -http://github.com/memcached/memcached/blob/1.4.2/doc/protocol.txt -""" - -import cPickle as pickle -import logging -import socket -import time -from bisect import bisect -from hashlib import md5 - -DEFAULT_MEMCACHED_PORT = 11211 - -CONN_TIMEOUT = 0.3 -IO_TIMEOUT = 2.0 -PICKLE_FLAG = 1 -NODE_WEIGHT = 50 -PICKLE_PROTOCOL = 2 -TRY_COUNT = 3 - -# if ERROR_LIMIT_COUNT errors occur in ERROR_LIMIT_TIME seconds, the server -# will be considered failed for ERROR_LIMIT_DURATION seconds. -ERROR_LIMIT_COUNT = 10 -ERROR_LIMIT_TIME = 60 -ERROR_LIMIT_DURATION = 60 - - -def md5hash(key): - return md5(key).hexdigest() - - -class MemcacheConnectionError(Exception): - pass - - -class MemcacheRing(object): - """ - Simple, consistent-hashed memcache client. - """ - - def __init__(self, servers, connect_timeout=CONN_TIMEOUT, - io_timeout=IO_TIMEOUT, tries=TRY_COUNT): - self._ring = {} - self._errors = dict(((serv, []) for serv in servers)) - self._error_limited = dict(((serv, 0) for serv in servers)) - for server in sorted(servers): - for i in xrange(NODE_WEIGHT): - self._ring[md5hash('%s-%s' % (server, i))] = server - self._tries = tries if tries <= len(servers) else len(servers) - self._sorted = sorted(self._ring.keys()) - self._client_cache = dict(((server, []) for server in servers)) - self._connect_timeout = connect_timeout - self._io_timeout = io_timeout - - def _exception_occurred(self, server, e, action='talking'): - if isinstance(e, socket.timeout): - logging.error(_("Timeout %(action)s to memcached: %(server)s"), - {'action': action, 'server': server}) - else: - logging.exception(_("Error %(action)s to memcached: %(server)s"), - {'action': action, 'server': server}) - now = time.time() - self._errors[server].append(time.time()) - if len(self._errors[server]) > ERROR_LIMIT_COUNT: - self._errors[server] = [err for err in self._errors[server] - if err > now - ERROR_LIMIT_TIME] - if len(self._errors[server]) > ERROR_LIMIT_COUNT: - self._error_limited[server] = now + ERROR_LIMIT_DURATION - logging.error(_('Error limiting server %s'), server) - - def _get_conns(self, key): - """ - Retrieves a server conn from the pool, or connects a new one. - Chooses the server based on a consistent hash of "key". - """ - pos = bisect(self._sorted, key) - served = [] - while len(served) < self._tries: - pos = (pos + 1) % len(self._sorted) - server = self._ring[self._sorted[pos]] - if server in served: - continue - served.append(server) - if self._error_limited[server] > time.time(): - continue - try: - fp, sock = self._client_cache[server].pop() - yield server, fp, sock - except IndexError: - try: - if ':' in server: - host, port = server.split(':') - else: - host = server - port = DEFAULT_MEMCACHED_PORT - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - sock.settimeout(self._connect_timeout) - sock.connect((host, int(port))) - sock.settimeout(self._io_timeout) - yield server, sock.makefile(), sock - except Exception, e: - self._exception_occurred(server, e, 'connecting') - - def _return_conn(self, server, fp, sock): - """ Returns a server connection to the pool """ - self._client_cache[server].append((fp, sock)) - - def set(self, key, value, serialize=True, timeout=0): - """ - Set a key/value pair in memcache - - :param key: key - :param value: value - :param serialize: if True, value is pickled before sending to memcache - :param timeout: ttl in memcache - """ - key = md5hash(key) - if timeout > 0: - timeout += time.time() - flags = 0 - if serialize: - value = pickle.dumps(value, PICKLE_PROTOCOL) - flags |= PICKLE_FLAG - for (server, fp, sock) in self._get_conns(key): - try: - sock.sendall('set %s %d %d %s noreply\r\n%s\r\n' % \ - (key, flags, timeout, len(value), value)) - self._return_conn(server, fp, sock) - return - except Exception, e: - self._exception_occurred(server, e) - - def get(self, key): - """ - Gets the object specified by key. It will also unpickle the object - before returning if it is pickled in memcache. - - :param key: key - :returns: value of the key in memcache - """ - key = md5hash(key) - value = None - for (server, fp, sock) in self._get_conns(key): - try: - sock.sendall('get %s\r\n' % key) - line = fp.readline().strip().split() - while line[0].upper() != 'END': - if line[0].upper() == 'VALUE' and line[1] == key: - size = int(line[3]) - value = fp.read(size) - if int(line[2]) & PICKLE_FLAG: - value = pickle.loads(value) - fp.readline() - line = fp.readline().strip().split() - self._return_conn(server, fp, sock) - return value - except Exception, e: - self._exception_occurred(server, e) - - def incr(self, key, delta=1, timeout=0): - """ - Increments a key which has a numeric value by delta. - If the key can't be found, it's added as delta or 0 if delta < 0. - If passed a negative number, will use memcached's decr. Returns - the int stored in memcached - Note: The data memcached stores as the result of incr/decr is - an unsigned int. decr's that result in a number below 0 are - stored as 0. - - :param key: key - :param delta: amount to add to the value of key (or set as the value - if the key is not found) will be cast to an int - :param timeout: ttl in memcache - :raises MemcacheConnectionError: - """ - key = md5hash(key) - command = 'incr' - if delta < 0: - command = 'decr' - delta = str(abs(int(delta))) - for (server, fp, sock) in self._get_conns(key): - try: - sock.sendall('%s %s %s\r\n' % (command, key, delta)) - line = fp.readline().strip().split() - if line[0].upper() == 'NOT_FOUND': - add_val = delta - if command == 'decr': - add_val = '0' - sock.sendall('add %s %d %d %s\r\n%s\r\n' % \ - (key, 0, timeout, len(add_val), add_val)) - line = fp.readline().strip().split() - if line[0].upper() == 'NOT_STORED': - sock.sendall('%s %s %s\r\n' % (command, key, delta)) - line = fp.readline().strip().split() - ret = int(line[0].strip()) - else: - ret = int(add_val) - else: - ret = int(line[0].strip()) - self._return_conn(server, fp, sock) - return ret - except Exception, e: - self._exception_occurred(server, e) - raise MemcacheConnectionError("No Memcached connections succeeded.") - - def decr(self, key, delta=1, timeout=0): - """ - Decrements a key which has a numeric value by delta. Calls incr with - -delta. - - :param key: key - :param delta: amount to subtract to the value of key (or set the - value to 0 if the key is not found) will be cast to - an int - :param timeout: ttl in memcache - :raises MemcacheConnectionError: - """ - self.incr(key, delta=-delta, timeout=timeout) - - def delete(self, key): - """ - Deletes a key/value pair from memcache. - - :param key: key to be deleted - """ - key = md5hash(key) - for (server, fp, sock) in self._get_conns(key): - try: - sock.sendall('delete %s noreply\r\n' % key) - self._return_conn(server, fp, sock) - return - except Exception, e: - self._exception_occurred(server, e) - - def set_multi(self, mapping, server_key, serialize=True, timeout=0): - """ - Sets multiple key/value pairs in memcache. - - :param mapping: dictonary of keys and values to be set in memcache - :param servery_key: key to use in determining which server in the ring - is used - :param serialize: if True, value is pickled before sending to memcache - :param timeout: ttl for memcache - """ - server_key = md5hash(server_key) - if timeout > 0: - timeout += time.time() - msg = '' - for key, value in mapping.iteritems(): - key = md5hash(key) - flags = 0 - if serialize: - value = pickle.dumps(value, PICKLE_PROTOCOL) - flags |= PICKLE_FLAG - msg += ('set %s %d %d %s noreply\r\n%s\r\n' % - (key, flags, timeout, len(value), value)) - for (server, fp, sock) in self._get_conns(server_key): - try: - sock.sendall(msg) - self._return_conn(server, fp, sock) - return - except Exception, e: - self._exception_occurred(server, e) - - def get_multi(self, keys, server_key): - """ - Gets multiple values from memcache for the given keys. - - :param keys: keys for values to be retrieved from memcache - :param servery_key: key to use in determining which server in the ring - is used - :returns: list of values - """ - server_key = md5hash(server_key) - keys = [md5hash(key) for key in keys] - for (server, fp, sock) in self._get_conns(server_key): - try: - sock.sendall('get %s\r\n' % ' '.join(keys)) - line = fp.readline().strip().split() - responses = {} - while line[0].upper() != 'END': - if line[0].upper() == 'VALUE': - size = int(line[3]) - value = fp.read(size) - if int(line[2]) & PICKLE_FLAG: - value = pickle.loads(value) - responses[line[1]] = value - fp.readline() - line = fp.readline().strip().split() - values = [] - for key in keys: - if key in responses: - values.append(responses[key]) - else: - values.append(None) - self._return_conn(server, fp, sock) - return values - except Exception, e: - self._exception_occurred(server, e) diff --git a/apachekerbauth/build.sh b/apachekerbauth/build.sh deleted file mode 100755 index 6db04ac..0000000 --- a/apachekerbauth/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -tar -cz --exclude=.svn -f ~/rpmbuild/SOURCES/apachekerbauth.tar.gz apachekerbauth - -rpmbuild --target noarch --clean -bb apachekerbauth.spec - -rm ~/rpmbuild/SOURCES/apachekerbauth.tar.gz diff --git a/apachekerbauth/apachekerbauth/etc/httpd/conf.d/swift-auth.conf b/apachekerbauth/etc/httpd/conf.d/swift-auth.conf index ba2b249..68472d8 100644 --- a/apachekerbauth/apachekerbauth/etc/httpd/conf.d/swift-auth.conf +++ b/apachekerbauth/etc/httpd/conf.d/swift-auth.conf @@ -3,7 +3,10 @@ AuthName "Swift Authentication" KrbMethodNegotiate On KrbMethodK5Passwd On + KrbSaveCredentials On + KrbServiceName HTTP/client.example.com KrbAuthRealms EXAMPLE.COM - Krb5KeyTab /etc/httpd/conf/apache.keytab - require valid-user + Krb5KeyTab /etc/httpd/conf/http.keytab + KrbVerifyKDC Off + Require valid-user </Location> diff --git a/apachekerbauth/apachekerbauth/var/www/cgi-bin/swift-auth b/apachekerbauth/var/www/cgi-bin/swift-auth index 30f98b5..6173408 100755 --- a/apachekerbauth/apachekerbauth/var/www/cgi-bin/swift-auth +++ b/apachekerbauth/var/www/cgi-bin/swift-auth @@ -14,20 +14,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Requires the python-memcached package to be installed. -# # Requires the following command to be run: # setsebool -P httpd_can_network_connect 1 # setsebool -P httpd_can_network_memcache 1 import cgi -import json -from memcached import MemcacheRing +from swift.common.memcached import MemcacheRing import os +import grp import random import re import subprocess -from time import time +from time import time, ctime # After how many seconds the cached information about an authentication # token is discarded. @@ -38,14 +36,15 @@ TOKEN_LIFE = 86400 # into a configuration parameter. RESELLER_PREFIX = 'AUTH_' -MEMCACHE_SERVERS = ['rhs1.example.com:11211'] +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) + raise RuntimeError("Malformed REMOTE_USER \"%s\"" % remote_user) username = matches.group(1) @@ -56,59 +55,66 @@ def main(): 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 + 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. + # 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))) + 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 = 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() + (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. + # 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 = [username] + groups - groups = ','.join(groups) + groups = ','.join(groups) - expires = time() + TOKEN_LIFE - auth_data = (expires, 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) + 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) + # 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\n" % token + print "X-Auth-Token: %s" % token # For debugging. - print "<pre>%i / %s</pre>" % mc.get(memcache_token_key) + 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") |