summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xapachekerbauth/var/www/cgi-bin/swift-auth96
-rw-r--r--doc/architecture.md7
-rw-r--r--setup.py7
-rw-r--r--swiftkerbauth/__init__.py21
-rw-r--r--swiftkerbauth/kerbauth.py8
-rw-r--r--swiftkerbauth/kerbauth_utils.py106
-rw-r--r--test/unit/__init__.py44
-rw-r--r--test/unit/test_kerbauth.py30
-rw-r--r--test/unit/test_kerbauth_utils.py77
9 files changed, 278 insertions, 118 deletions
diff --git a/apachekerbauth/var/www/cgi-bin/swift-auth b/apachekerbauth/var/www/cgi-bin/swift-auth
index 1d124c5..45df45c 100755
--- a/apachekerbauth/var/www/cgi-bin/swift-auth
+++ b/apachekerbauth/var/www/cgi-bin/swift-auth
@@ -18,95 +18,41 @@
# setsebool -P httpd_can_network_connect 1
# setsebool -P httpd_can_network_memcache 1
+import os
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
+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
def main():
- remote_user = os.environ['REMOTE_USER']
- matches = re.match('([^@]+)@.*', remote_user)
- if not matches:
- raise RuntimeError("Malformed REMOTE_USER \"%s\"" % remote_user)
+ try:
+ username = get_remote_user(os.environ)
+ except RuntimeError:
+ print "Status: 401 Unauthorized\n"
+ print "Malformed REMOTE_USER"
+ return
- username = matches.group(1)
+ if not MEMCACHE_SERVERS:
+ print "Status: 500 Internal Server Error\n"
+ print "Memcache not configured in /etc/swift/proxy-server.conf"
+ return
- mc = MemcacheRing(MEMCACHE_SERVERS)
+ mc_servers = [s.strip() for s in MEMCACHE_SERVERS.split(',') if s.strip()]
+ mc = MemcacheRing(mc_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
+ token, expires, groups = get_auth_data(mc, username)
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)
-
+ token = generate_token()
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)
+ groups = get_groups(username)
+ set_auth_data(mc, username, token, expires, groups)
print "X-Auth-Token: %s" % token
+ print "X-Storage-Token: %s" % token
# For debugging.
if DEBUG_HEADERS:
diff --git a/doc/architecture.md b/doc/architecture.md
index cfe64d2..fc6d764 100644
--- a/doc/architecture.md
+++ b/doc/architecture.md
@@ -38,10 +38,9 @@ either grants or denies the resource access.
## kerbauth.py
-The script /usr/lib/python2.6/site-packages/swiftkerbauth/kerbauth.py began as
-a copy of the tempauth.py script from
-/usr/lib/python2.6/site-packages/swift/common/middleware. It contains
-the following modifications, among others:
+The script kerbauth.py began as a copy of the tempauth.py script from
+from tempauth middleware. It contains the following modifications, among
+others:
In the __init__ method, we read the ext_authentication_url parameter
from /etc/swift/proxy-server.conf. This is the URL that clients are
diff --git a/setup.py b/setup.py
index 65aab59..d435a39 100644
--- a/setup.py
+++ b/setup.py
@@ -16,9 +16,10 @@
# limitations under the License.
from setuptools import setup
-from swiftkerbauth import __version__
import os
+__version__ = '1.0.0'
+
with open('README.md') as file:
long_description = file.read()
@@ -44,10 +45,10 @@ setup(
author='Red Hat, Inc.',
author_email='gluster-users@gluster.org',
long_description=long_description,
- url='https://forge.gluster.org/swiftkerbauth',
+ url='https://github.com/gluster/swiftkrbauth/',
packages=['swiftkerbauth'],
keywords='openstack swift kerberos',
- install_requires=['swift>=1.9.1'],
+ install_requires=['swift>=1.10.0'],
test_suite='nose.collector',
classifiers=[
'Development Status :: 3 - Alpha',
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
diff --git a/test/unit/__init__.py b/test/unit/__init__.py
index e69de29..3e26378 100644
--- a/test/unit/__init__.py
+++ b/test/unit/__init__.py
@@ -0,0 +1,44 @@
+# 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.
+from contextlib import contextmanager
+
+
+class FakeMemcache(object):
+ """A Fake class to emulate memcache."""
+
+ def __init__(self):
+ self.store = {}
+
+ def get(self, key):
+ return self.store.get(key)
+
+ def set(self, key, value, timeout=0):
+ self.store[key] = value
+ return True
+
+ def incr(self, key, time=0):
+ self.store[key] = self.store.setdefault(key, 0) + 1
+ return self.store[key]
+
+ @contextmanager
+ def soft_lock(self, key, timeout=0, retries=5):
+ yield True
+
+ def delete(self, key):
+ try:
+ del self.store[key]
+ except Exception:
+ pass
+ return True
diff --git a/test/unit/test_kerbauth.py b/test/unit/test_kerbauth.py
index 446abb8..1771314 100644
--- a/test/unit/test_kerbauth.py
+++ b/test/unit/test_kerbauth.py
@@ -14,10 +14,10 @@
# limitations under the License.
import unittest
-from contextlib import contextmanager
from time import time
from swiftkerbauth import kerbauth as auth
+from test.unit import FakeMemcache
from swift.common.swob import Request, Response
EXT_AUTHENTICATION_URL = "127.0.0.1"
@@ -46,34 +46,6 @@ def unpatch_filter_factory():
reload(auth)
-class FakeMemcache(object):
-
- def __init__(self):
- self.store = {}
-
- def get(self, key):
- return self.store.get(key)
-
- def set(self, key, value, time=0):
- self.store[key] = value
- return True
-
- def incr(self, key, time=0):
- self.store[key] = self.store.setdefault(key, 0) + 1
- return self.store[key]
-
- @contextmanager
- def soft_lock(self, key, timeout=0, retries=5):
- yield True
-
- def delete(self, key):
- try:
- del self.store[key]
- except Exception:
- pass
- return True
-
-
class FakeApp(object):
def __init__(self, status_headers_body_iter=None, acl=None, sync_key=None):
diff --git a/test/unit/test_kerbauth_utils.py b/test/unit/test_kerbauth_utils.py
new file mode 100644
index 0000000..abf8ad0
--- /dev/null
+++ b/test/unit/test_kerbauth_utils.py
@@ -0,0 +1,77 @@
+# 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 unittest
+import re
+from time import time
+from test.unit import FakeMemcache
+from swiftkerbauth import kerbauth_utils as ku
+
+
+class TestKerbUtils(unittest.TestCase):
+
+ def test_get_remote_user(self):
+ env = {'REMOTE_USER': "auth_admin@EXAMPLE.COM"}
+ result = ku.get_remote_user(env)
+ self.assertEqual(result, "auth_admin")
+
+ def test_get_remote_user_err(self):
+ env = {'REMOTE_USER': "auth_admin"}
+ try:
+ ku.get_remote_user(env)
+ except RuntimeError as err:
+ self.assertTrue(err.args[0].startswith("Malformed REMOTE_USER"))
+ else:
+ self.fail("Expected RuntimeError")
+
+ def test_get_auth_data(self):
+ mc = FakeMemcache()
+ expiry = time() + 100
+ ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin")
+ (token, expires, groups) = ku.get_auth_data(mc, "root")
+ self.assertEqual(("AUTH_tk", expiry, "root,admin"),
+ (token, expires, groups))
+
+ def test_get_auth_data_err(self):
+ mc = FakeMemcache()
+ (token, expires, groups) = ku.get_auth_data(mc, "root")
+ self.assertEqual((token, expires, groups), (None, None, None))
+
+ expiry = time() - 1
+ ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin")
+ (token, expires, groups) = ku.get_auth_data(mc, "root")
+ self.assertEqual((token, expires, groups), (None, None, None))
+
+ def test_set_auth_data(self):
+ mc = FakeMemcache()
+ expiry = time() + 100
+ ku.set_auth_data(mc, "root", "AUTH_tk", expiry, "root,admin")
+
+ def test_generate_token(self):
+ token = ku.generate_token()
+ matches = re.match('AUTH_tk[a-f0-9]{32}', token)
+ self.assertIsNotNone(matches)
+
+ def test_get_groups(self):
+ groups = ku.get_groups("root")
+ self.assertIn("root", groups)
+
+ def test_get_groups_err(self):
+ try:
+ ku.get_groups("Zroot")
+ except RuntimeError as err:
+ self.assertTrue(err.args[0].startswith("Failure running id -G"))
+ else:
+ self.fail("Expected RuntimeError")