summaryrefslogtreecommitdiffstats
path: root/apachekerbauth
diff options
context:
space:
mode:
authorPrashanth Pai <ppai@redhat.com>2013-09-23 11:47:21 +0530
committerPrashanth Pai <ppai@redhat.com>2013-09-23 11:57:31 +0530
commitaf2cbe8a5bec2b7971de137416d6e62ac1b96498 (patch)
tree42de6bfc2b380f27512956a7c0392ede9e0889e1 /apachekerbauth
parent728157e52fe3212d50edfc17fcd72e1aaf9a6e76 (diff)
Minor swiftkerbauth changes
* Replaced python-webob with swift.common.swob * Use swift memcached instead of python memcached * Added optional debugging headers to swift-auth script * Swiftkerbauth and Apachekerbauth are now a single RPM * Updates to httpd conf file to specify Kerberos principal * Added setupy.py, makerpm.sh, .gitignore and MANIFEST.in * RPM is now generated by bdist_rpm using setup.py and not from spec files TODO -> Documentation changes in doc/ * Steps to setup kerberos environment * Swiftkerbauth usage and examples -> Testing swiftkerbauth * Investigate borrowing tests from tempauth.py and its dependencies * Write a python client script to test swiftkerbauth Signed-off-by: Prashanth Pai <ppai@redhat.com>
Diffstat (limited to 'apachekerbauth')
-rw-r--r--apachekerbauth/apachekerbauth.spec50
-rw-r--r--apachekerbauth/apachekerbauth/var/www/cgi-bin/memcached.py318
-rwxr-xr-xapachekerbauth/build.sh7
-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-xapachekerbauth/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")