diff options
Diffstat (limited to 'swiftkerbauth')
-rw-r--r-- | swiftkerbauth/__init__.py | 17 | ||||
-rwxr-xr-x | swiftkerbauth/build.sh | 7 | ||||
-rw-r--r-- | swiftkerbauth/kerbauth.py (renamed from swiftkerbauth/swiftkerbauth/swiftkerbauth.py) | 165 | ||||
-rw-r--r-- | swiftkerbauth/swiftkerbauth.spec | 54 |
4 files changed, 91 insertions, 152 deletions
diff --git a/swiftkerbauth/__init__.py b/swiftkerbauth/__init__.py new file mode 100644 index 0000000..eaa1d88 --- /dev/null +++ b/swiftkerbauth/__init__.py @@ -0,0 +1,17 @@ +#!/usr/bin/env 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. + +__version__ = "1.0.0" diff --git a/swiftkerbauth/build.sh b/swiftkerbauth/build.sh deleted file mode 100755 index 0daff96..0000000 --- a/swiftkerbauth/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -tar -cz --exclude=.svn -f ~/rpmbuild/SOURCES/swiftkerbauth.tar.gz swiftkerbauth - -rpmbuild --target noarch --clean -bb swiftkerbauth.spec - -rm ~/rpmbuild/SOURCES/swiftkerbauth.tar.gz diff --git a/swiftkerbauth/swiftkerbauth/swiftkerbauth.py b/swiftkerbauth/kerbauth.py index ca9cfc7..a37f1f6 100644 --- a/swiftkerbauth/swiftkerbauth/swiftkerbauth.py +++ b/swiftkerbauth/kerbauth.py @@ -12,22 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -from time import gmtime, strftime, time +from time import time from traceback import format_exc -from urllib import quote, unquote - from eventlet import Timeout -from pkg_resources import require -require("WebOb>=1.0.8") - -from webob import Response, Request -from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \ +from swift.common.swob import Request +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, get_remote_client, \ - split_path, TRUE_VALUES +from swift.common.utils import cache_from_env, get_logger, \ + split_path, config_true_value + class KerbAuth(object): """ @@ -58,26 +54,30 @@ class KerbAuth(object): self.app = app self.conf = conf self.logger = get_logger(conf, log_route='kerbauth') - self.log_headers = conf.get('log_headers') == 'True' + self.log_headers = config_true_value(conf.get('log_headers', 'f')) self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip() if self.reseller_prefix and self.reseller_prefix[-1] != '_': self.reseller_prefix += '_' + self.logger.set_statsd_prefix('kerbauth.%s' % ( + self.reseller_prefix if self.reseller_prefix else 'NONE',)) self.auth_prefix = conf.get('auth_prefix', '/auth/') - if not self.auth_prefix: + if not self.auth_prefix or not self.auth_prefix.strip('/'): + self.logger.warning('Rewriting invalid auth prefix "%s" to ' + '"/auth/" (Non-empty auth prefix path ' + 'is required)' % self.auth_prefix) self.auth_prefix = '/auth/' if self.auth_prefix[0] != '/': self.auth_prefix = '/' + self.auth_prefix if self.auth_prefix[-1] != '/': self.auth_prefix += '/' self.token_life = int(conf.get('token_life', 86400)) - self.allowed_sync_hosts = [h.strip() - for h in conf.get('allowed_sync_hosts', '127.0.0.1').split(',') - if h.strip()] - self.allow_overrides = \ - conf.get('allow_overrides', 't').lower() in TRUE_VALUES + self.allow_overrides = config_true_value( + conf.get('allow_overrides', 't')) + self.storage_url_scheme = conf.get('storage_url_scheme', 'default') self.ext_authentication_url = conf.get('ext_authentication_url') if not self.ext_authentication_url: - raise RuntimeError("Missing filter parameter ext_authentication_url in /etc/swift/proxy-server.conf") + raise RuntimeError("Missing filter parameter ext_authentication_" + "url in /etc/swift/proxy-server.conf") def __call__(self, env, start_response): """ @@ -98,16 +98,19 @@ class KerbAuth(object): if token and token.startswith(self.reseller_prefix): groups = self.get_groups(env, token) if groups: - env['REMOTE_USER'] = groups user = groups and groups.split(',', 1)[0] or '' - # We know the proxy logs the token, so we augment it just a bit - # to also log the authenticated user. - env['HTTP_X_AUTH_TOKEN'] = '%s,%s' % (user, token) + trans_id = env.get('swift.trans_id') + self.logger.debug('User: %s uses token %s (trans_id %s)' % + (user, token, trans_id)) + env['REMOTE_USER'] = groups env['swift.authorize'] = self.authorize env['swift.clean_acl'] = clean_acl + if '.reseller_admin' in groups: + env['reseller_request'] = True else: # Invalid token (may be expired) - return HTTPSeeOther(location=self.ext_authentication_url)(env, start_response) + return HTTPSeeOther( + location=self.ext_authentication_url)(env, start_response) else: # With a non-empty reseller_prefix, I would like to be called # back for anonymous access to accounts I know I'm the @@ -116,7 +119,8 @@ class KerbAuth(object): version, rest = split_path(env.get('PATH_INFO', ''), 1, 2, True) except ValueError: - return HTTPNotFound()(env, start_response) + version, rest = None, None + self.logger.increment('errors') # Not my token, not my account, I can't authorize this request, # deny all is a good idea if not already set... if 'swift.authorize' not in env: @@ -158,12 +162,19 @@ class KerbAuth(object): Enterprise Linux Identity Management is used. """ try: - version, account, container, obj = split_path(req.path, 1, 4, True) + version, account, container, obj = req.split_path(1, 4, True) except ValueError: + self.logger.increment('errors') return HTTPNotFound(request=req) + if not account or not account.startswith(self.reseller_prefix): + self.logger.debug("Account name: %s doesn't start with " + "reseller_prefix: %s." + % (account, self.reseller_prefix)) return self.denied_response(req) + user_groups = (req.remote_user or '').split(',') + account_user = user_groups[1] if len(user_groups) > 1 else None # If the user is in the reseller_admin group for our prefix, he gets # full access to all accounts we manage. For the default reseller # prefix, the group name is auth_reseller_admin. @@ -173,6 +184,7 @@ class KerbAuth(object): account[len(self.reseller_prefix)] != '.': req.environ['swift_owner'] = True return None + # The "account" is part of the request URL, and already contains the # reseller prefix, like in "/v1/AUTH_vol1/pictures/pic1.png". if account.lower() in user_groups and \ @@ -180,24 +192,37 @@ class KerbAuth(object): # If the user is admin for the account and is not trying to do an # account DELETE or PUT... req.environ['swift_owner'] = True + self.logger.debug("User %s has admin authorizing." + % account_user) return None - if (req.environ.get('swift_sync_key') and - req.environ['swift_sync_key'] == - req.headers.get('x-container-sync-key', None) and - 'x-timestamp' in req.headers and - (req.remote_addr in self.allowed_sync_hosts or - get_remote_client(req) in self.allowed_sync_hosts)): + + if (req.environ.get('swift_sync_key') + and (req.environ['swift_sync_key'] == + req.headers.get('x-container-sync-key', None)) + and 'x-timestamp' in req.headers): + self.logger.debug("Allow request with container sync-key: %s." + % req.environ['swift_sync_key']) return None + + if req.method == 'OPTIONS': + #allow OPTIONS requests to proceed as normal + self.logger.debug("Allow OPTIONS request.") + return None + referrers, groups = parse_acl(getattr(req, 'acl', None)) + if referrer_allowed(req.referer, referrers): if obj or '.rlistings' in groups: + self.logger.debug("Allow authorizing %s via referer ACL." + % req.referer) return None - return self.denied_response(req) - if not req.remote_user: - return self.denied_response(req) + for user_group in user_groups: if user_group in groups: + self.logger.debug("User %s allowed in ACL: %s authorizing." + % (account_user, user_group)) return None + return self.denied_response(req) def denied_response(self, req): @@ -206,6 +231,7 @@ class KerbAuth(object): depending on whether the REMOTE_USER is set or not. """ if req.remote_user: + self.logger.increment('forbidden') return HTTPForbidden(request=req) else: return HTTPSeeOther(location=self.ext_authentication_url) @@ -214,7 +240,7 @@ class KerbAuth(object): """ WSGI entry point for auth requests (ones that match the self.auth_prefix). - Wraps env in webob.Request object and passes it down. + Wraps env in swob.Request object and passes it down. :param env: WSGI environment dictionary :param start_response: WSGI callable @@ -228,20 +254,10 @@ class KerbAuth(object): if 'x-storage-token' in req.headers and \ 'x-auth-token' not in req.headers: req.headers['x-auth-token'] = req.headers['x-storage-token'] - if 'eventlet.posthooks' in env: - env['eventlet.posthooks'].append( - (self.posthooklogger, (req,), {})) - return self.handle_request(req)(env, start_response) - else: - # Lack of posthook support means that we have to log on the - # start of the response, rather than after all the data has - # been sent. This prevents logging client disconnects - # differently than full transmissions. - response = self.handle_request(req)(env, start_response) - self.posthooklogger(env, req) - return response + return self.handle_request(req)(env, start_response) except (Exception, Timeout): print "EXCEPTION IN handle: %s: %s" % (format_exc(), env) + self.logger.increment('errors') start_response('500 Server Error', [('Content-Type', 'text/plain')]) return ['Internal server error.\n'] @@ -251,19 +267,20 @@ class KerbAuth(object): Entry point for auth requests (ones that match the self.auth_prefix). Should return a WSGI-style callable (such as webob.Response). - :param req: webob.Request object + :param req: swob.Request object """ req.start_time = time() handler = None try: - version, account, user, _junk = split_path(req.path_info, - minsegs=1, maxsegs=4, rest_with_last=True) + version, account, user, _junk = req.split_path(1, 4, True) except ValueError: + self.logger.increment('errors') return HTTPNotFound(request=req) if version in ('v1', 'v1.0', 'auth'): if req.method == 'GET': handler = self.handle_get_token if not handler: + self.logger.increment('errors') req.response = HTTPBadRequest(request=req) else: req.response = handler(req) @@ -284,56 +301,22 @@ class KerbAuth(object): On successful authentication, the response will have X-Auth-Token set to the token to use with Swift. - :param req: The webob.Request to process. - :returns: webob.Response, 2xx on success with data set as explained + :param req: The swob.Request to process. + :returns: swob.Response, 2xx on success with data set as explained above. """ # Validate the request info try: - pathsegs = split_path(req.path_info, minsegs=1, maxsegs=3, - rest_with_last=True) + pathsegs = split_path(req.path_info, 1, 3, True) except ValueError: + self.logger.increment('errors') return HTTPNotFound(request=req) - if not ((pathsegs[0] == 'v1' and pathsegs[2] == 'auth') or pathsegs[0] in ('auth', 'v1.0')): - return HTTPBadRequest(request=req) + if not ((pathsegs[0] == 'v1' and pathsegs[2] == 'auth') + or pathsegs[0] in ('auth', 'v1.0')): + return HTTPBadRequest(request=req) return HTTPSeeOther(location=self.ext_authentication_url) - def posthooklogger(self, env, req): - if not req.path.startswith(self.auth_prefix): - return - response = getattr(req, 'response', None) - if not response: - return - trans_time = '%.4f' % (time() - req.start_time) - the_request = quote(unquote(req.path)) - if req.query_string: - the_request = the_request + '?' + req.query_string - # remote user for zeus - client = req.headers.get('x-cluster-client-ip') - if not client and 'x-forwarded-for' in req.headers: - # remote user for other lbs - client = req.headers['x-forwarded-for'].split(',')[0].strip() - logged_headers = None - if self.log_headers: - logged_headers = '\n'.join('%s: %s' % (k, v) - for k, v in req.headers.items()) - status_int = response.status_int - if getattr(req, 'client_disconnect', False) or \ - getattr(response, 'client_disconnect', False): - status_int = 499 - self.logger.info(' '.join(quote(str(x)) for x in (client or '-', - req.remote_addr or '-', strftime('%d/%b/%Y/%H/%M/%S', gmtime()), - req.method, the_request, req.environ['SERVER_PROTOCOL'], - status_int, req.referer or '-', req.user_agent or '-', - req.headers.get('x-auth-token', - req.headers.get('x-auth-admin-user', '-')), - getattr(req, 'bytes_transferred', 0) or '-', - getattr(response, 'bytes_transferred', 0) or '-', - req.headers.get('etag', '-'), - req.environ.get('swift.trans_id', '-'), logged_headers or '-', - trans_time))) - def filter_factory(global_conf, **local_conf): """Returns a WSGI filter app for use with paste.deploy.""" diff --git a/swiftkerbauth/swiftkerbauth.spec b/swiftkerbauth/swiftkerbauth.spec deleted file mode 100644 index f01dc8d..0000000 --- a/swiftkerbauth/swiftkerbauth.spec +++ /dev/null @@ -1,54 +0,0 @@ -Name: swiftkerbauth -Version: 1.0 -Release: 1 -Summary: Kerberos authentication filter for Swift - -Group: System Environment/Base -License: GPL -Source: %{name}.tar.gz -BuildRoot: %{_tmppath}/%{name}-root - -Requires: gluster-swift >= 1.4.8 -Requires: python-webob1.0 >= 1.0.8 - -%description -Python script which implements an authentication filter for Swift, the -object-level access layer of Red Hat Storage. - -Relies on an external authentication server, which comes with the -apachekerbauth package. - -%prep -%setup -q -n %{name} - -%build - -%install -rm -rf $RPM_BUILD_ROOT - -mkdir -p \ - $RPM_BUILD_ROOT/usr/lib/python2.6/site-packages - -install swiftkerbauth.py \ - $RPM_BUILD_ROOT/usr/lib/python2.6/site-packages - -%clean -rm -rf $RPM_BUILD_ROOT - -%files -%defattr(-,root,root,-) -/usr/lib/python2.6/site-packages/swiftkerbauth.py - -%post -if ! grep -q "filter:kerbauth" /etc/swift/proxy-server.conf; then -cat >>/etc/swift/proxy-server.conf <<EOF - -[filter:kerbauth] -paste.filter_factory = swiftkerbauth:filter_factory -ext_authentication_url = http://AUTHENTICATION_SERVER/cgi-bin/swift-auth -EOF -fi - -%changelog -* Fri Apr 5 2013 Carsten Clasohm <clasohm@redhat.com> - 1.0-1 -- initial build |