Rebase swiftkerbauth imported code with upstream
Few changes have been merged to upstream swiftkerbauth repo. This commit brings it down to gluster-swift repo. Bringing below changes to gluster-swift repo in one go. http://review.gluster.org/#/c/6296/ http://review.gluster.org/#/c/6370/ http://review.gluster.org/#/c/6595/ http://review.gluster.org/#/c/6713/ http://review.gluster.org/#/c/6732/ Change-Id: I10dc12d75ec63fca313339fbc71e4f18071af552 Signed-off-by: Chetan Risbud <crisbud@redhat.com> Reviewed-on: http://review.gluster.org/6764 Reviewed-by: Prashanth Pai <ppai@redhat.com>
This commit is contained in:
parent
1f432663ba
commit
03128e172e
@ -98,6 +98,18 @@ On client:
|
|||||||
<a name="users-groups" />
|
<a name="users-groups" />
|
||||||
###Adding users and groups
|
###Adding users and groups
|
||||||
|
|
||||||
|
The following convention is to be followed in creating group names:
|
||||||
|
|
||||||
|
<reseller-prefix>\_<volume-name>
|
||||||
|
|
||||||
|
<reseller-prefix>\_<account-name>
|
||||||
|
|
||||||
|
As of now, account=volume=group
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
AUTH\_test
|
||||||
|
|
||||||
Adding groups and users to the Windows domain is easy task.
|
Adding groups and users to the Windows domain is easy task.
|
||||||
|
|
||||||
- Start -> Administrative Tools -> Active Directory Users & Computers
|
- Start -> Administrative Tools -> Active Directory Users & Computers
|
||||||
|
@ -107,6 +107,18 @@ Check if reverse resolution works :
|
|||||||
<a name="users-groups" />
|
<a name="users-groups" />
|
||||||
## Adding users and groups
|
## Adding users and groups
|
||||||
|
|
||||||
|
The following convention is to be followed in creating group names:
|
||||||
|
|
||||||
|
<reseller-prefix>\_<volume-name>
|
||||||
|
|
||||||
|
<reseller-prefix>\_<account-name>
|
||||||
|
|
||||||
|
As of now, account=volume=group
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
AUTH\_test
|
||||||
|
|
||||||
Create *auth_reseller_admin* user group
|
Create *auth_reseller_admin* user group
|
||||||
> ipa group-add auth_reseller_admin --desc="Full access to all Swift accounts"
|
> ipa group-add auth_reseller_admin --desc="Full access to all Swift accounts"
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
* [Creating HTTP Service Principal on IPA server] (#http-principal)
|
* [Creating HTTP Service Principal on IPA server] (#http-principal)
|
||||||
* [Installing and configuring swiftkerbauth on IPA client] (#install-swiftkerbauth)
|
* [Installing and configuring swiftkerbauth on IPA client] (#install-swiftkerbauth)
|
||||||
* [Using swiftkerbauth] (#use-swiftkerbauth)
|
* [Using swiftkerbauth] (#use-swiftkerbauth)
|
||||||
|
* [Configurable Parameters] (#config-swiftkerbauth)
|
||||||
|
|
||||||
<a name="httpd-kerb-install" />
|
<a name="httpd-kerb-install" />
|
||||||
## Installing Kerberos module for Apache on IPA client
|
## Installing Kerberos module for Apache on IPA client
|
||||||
@ -47,7 +48,8 @@ Copy keytab file to client:
|
|||||||
|
|
||||||
Add a HTTP Kerberos service principal:
|
Add a HTTP Kerberos service principal:
|
||||||
> c:\>ktpass.exe -princ HTTP/fcclient.winad.com@WINAD.COM -mapuser
|
> c:\>ktpass.exe -princ HTTP/fcclient.winad.com@WINAD.COM -mapuser
|
||||||
> auth_admin@WINAD.COM -pass Redhat*123 -out c:\HTTP.keytab
|
> auth_admin@WINAD.COM -pass Redhat*123 -out c:\HTTP.keytab -crypto DES-CBC-CRC
|
||||||
|
> -kvno 0
|
||||||
|
|
||||||
Use winscp to copy HTTP.ketab file to /etc/httpd/conf/http.keytab
|
Use winscp to copy HTTP.ketab file to /etc/httpd/conf/http.keytab
|
||||||
|
|
||||||
@ -101,6 +103,7 @@ Edit */etc/swift/proxy-server.conf* and add a new filter section as follows:
|
|||||||
[filter:kerbauth]
|
[filter:kerbauth]
|
||||||
use = egg:swiftkerbauth#kerbauth
|
use = egg:swiftkerbauth#kerbauth
|
||||||
ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth
|
ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth
|
||||||
|
auth_mode=passive
|
||||||
|
|
||||||
Add kerbauth to pipeline
|
Add kerbauth to pipeline
|
||||||
|
|
||||||
@ -433,3 +436,56 @@ The --negotiate option is for curl to perform Kerberos authentication and
|
|||||||
--location-trusted is for curl to follow the redirect.
|
--location-trusted is for curl to follow the redirect.
|
||||||
|
|
||||||
[auth_kerb_module Configuration]: http://modauthkerb.sourceforge.net/configure.html
|
[auth_kerb_module Configuration]: http://modauthkerb.sourceforge.net/configure.html
|
||||||
|
|
||||||
|
|
||||||
|
#### Get an authentication token when auth_mode=passive:
|
||||||
|
> curl -v -H 'X-Auth-User: test:auth_admin' -H 'X-Auth-Key: Redhat*123' http://127.0.0.1:8080/auth/v1.0
|
||||||
|
|
||||||
|
**NOTE**: X-Storage-Url response header can be returned only in passive mode.
|
||||||
|
|
||||||
|
<a name="config-swiftkerbauth" />
|
||||||
|
##Configurable Parameters
|
||||||
|
|
||||||
|
The kerbauth filter section in **/etc/swift/proxy-server.conf** looks something
|
||||||
|
like this:
|
||||||
|
|
||||||
|
[filter:kerbauth]
|
||||||
|
use = egg:swiftkerbauth#kerbauth
|
||||||
|
ext_authentication_url = http://client.rhelbox.com/cgi-bin/swift-auth
|
||||||
|
auth_method = active
|
||||||
|
token_life = 86400
|
||||||
|
debug_headers = yes
|
||||||
|
realm_name = RHELBOX.COM
|
||||||
|
|
||||||
|
Of all the options listed above, specifying **ext\_authentication\_url** is
|
||||||
|
mandatory. The rest of the options are optional and have default values.
|
||||||
|
|
||||||
|
#### ext\_authentication\_url
|
||||||
|
A URL specifying location of the swift-auth CGI script. Avoid using IP address.
|
||||||
|
Default value: None
|
||||||
|
|
||||||
|
#### token_life
|
||||||
|
After how many seconds the cached information about an authentication token is
|
||||||
|
discarded.
|
||||||
|
Default value: 86400
|
||||||
|
|
||||||
|
#### debug_headers
|
||||||
|
When turned on, the response headers sent to the user will contain additional
|
||||||
|
debug information apart from the auth token.
|
||||||
|
Default value: yes
|
||||||
|
|
||||||
|
#### auth_method
|
||||||
|
Set this to **"active"** when you want to allow access **only to clients
|
||||||
|
residing inside the domain**. In this mode, authentication is performed by
|
||||||
|
mod\_auth\_kerb using the Kerberos ticket bundled with the client request.
|
||||||
|
No username and password have to be specified to get a token.
|
||||||
|
Set this to **"passive"** when you want to allow access to clients residing
|
||||||
|
outside the domain. In this mode, authentication is performed by gleaning
|
||||||
|
username and password from request headers (X-Auth-User and X-Auth-Key) and
|
||||||
|
running kinit command against it.
|
||||||
|
Default value: passive
|
||||||
|
|
||||||
|
#### realm_name
|
||||||
|
This is applicable only when the auth_method=passive. This option specifies
|
||||||
|
realm name if RHS server belongs to more than one realm and realm name is not
|
||||||
|
part of the username specified in X-Auth-User header.
|
||||||
|
@ -24,7 +24,7 @@ from swift.common.memcached import MemcacheRing
|
|||||||
from time import time, ctime
|
from time import time, ctime
|
||||||
from swiftkerbauth import MEMCACHE_SERVERS, TOKEN_LIFE, DEBUG_HEADERS
|
from swiftkerbauth import MEMCACHE_SERVERS, TOKEN_LIFE, DEBUG_HEADERS
|
||||||
from swiftkerbauth.kerbauth_utils import get_remote_user, get_auth_data, \
|
from swiftkerbauth.kerbauth_utils import get_remote_user, get_auth_data, \
|
||||||
generate_token, set_auth_data, get_groups
|
generate_token, set_auth_data, get_groups_from_username
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -48,7 +48,7 @@ def main():
|
|||||||
if not token:
|
if not token:
|
||||||
token = generate_token()
|
token = generate_token()
|
||||||
expires = time() + TOKEN_LIFE
|
expires = time() + TOKEN_LIFE
|
||||||
groups = get_groups(username)
|
groups = get_groups_from_username(username)
|
||||||
set_auth_data(mc, username, token, expires, groups)
|
set_auth_data(mc, username, token, expires, groups)
|
||||||
|
|
||||||
print "X-Auth-Token: %s" % token
|
print "X-Auth-Token: %s" % token
|
||||||
|
@ -12,17 +12,22 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from time import time
|
import errno
|
||||||
|
from time import time, ctime
|
||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
from eventlet import Timeout
|
from eventlet import Timeout
|
||||||
|
from urllib import unquote
|
||||||
|
|
||||||
from swift.common.swob import Request
|
from swift.common.swob import Request, Response
|
||||||
from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
|
from swift.common.swob import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
|
||||||
HTTPSeeOther
|
HTTPSeeOther, HTTPUnauthorized, HTTPServerError
|
||||||
|
|
||||||
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
||||||
from swift.common.utils import cache_from_env, get_logger, \
|
from swift.common.utils import cache_from_env, get_logger, \
|
||||||
split_path, config_true_value
|
split_path, config_true_value
|
||||||
|
from gluster.swift.common.middleware.swiftkerbauth.kerbauth_utils import \
|
||||||
|
get_auth_data, generate_token, \
|
||||||
|
set_auth_data, run_kinit, get_groups_from_username
|
||||||
|
|
||||||
|
|
||||||
class KerbAuth(object):
|
class KerbAuth(object):
|
||||||
@ -71,6 +76,10 @@ class KerbAuth(object):
|
|||||||
if self.auth_prefix[-1] != '/':
|
if self.auth_prefix[-1] != '/':
|
||||||
self.auth_prefix += '/'
|
self.auth_prefix += '/'
|
||||||
self.token_life = int(conf.get('token_life', 86400))
|
self.token_life = int(conf.get('token_life', 86400))
|
||||||
|
self.auth_method = conf.get('auth_method', 'passive')
|
||||||
|
self.debug_headers = config_true_value(
|
||||||
|
conf.get('debug_headers', 'yes'))
|
||||||
|
self.realm_name = conf.get('realm_name', None)
|
||||||
self.allow_overrides = config_true_value(
|
self.allow_overrides = config_true_value(
|
||||||
conf.get('allow_overrides', 't'))
|
conf.get('allow_overrides', 't'))
|
||||||
self.storage_url_scheme = conf.get('storage_url_scheme', 'default')
|
self.storage_url_scheme = conf.get('storage_url_scheme', 'default')
|
||||||
@ -109,8 +118,13 @@ class KerbAuth(object):
|
|||||||
env['reseller_request'] = True
|
env['reseller_request'] = True
|
||||||
else:
|
else:
|
||||||
# Invalid token (may be expired)
|
# Invalid token (may be expired)
|
||||||
return HTTPSeeOther(
|
if self.auth_method == "active":
|
||||||
location=self.ext_authentication_url)(env, start_response)
|
return HTTPSeeOther(
|
||||||
|
location=self.ext_authentication_url)(env,
|
||||||
|
start_response)
|
||||||
|
elif self.auth_method == "passive":
|
||||||
|
self.logger.increment('unauthorized')
|
||||||
|
return HTTPUnauthorized()(env, start_response)
|
||||||
else:
|
else:
|
||||||
# With a non-empty reseller_prefix, I would like to be called
|
# With a non-empty reseller_prefix, I would like to be called
|
||||||
# back for anonymous access to accounts I know I'm the
|
# back for anonymous access to accounts I know I'm the
|
||||||
@ -234,7 +248,11 @@ class KerbAuth(object):
|
|||||||
self.logger.increment('forbidden')
|
self.logger.increment('forbidden')
|
||||||
return HTTPForbidden(request=req)
|
return HTTPForbidden(request=req)
|
||||||
else:
|
else:
|
||||||
return HTTPSeeOther(location=self.ext_authentication_url)
|
if self.auth_method == "active":
|
||||||
|
return HTTPSeeOther(location=self.ext_authentication_url)
|
||||||
|
elif self.auth_method == "passive":
|
||||||
|
self.logger.increment('unauthorized')
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
def handle(self, env, start_response):
|
def handle(self, env, start_response):
|
||||||
"""
|
"""
|
||||||
@ -290,16 +308,37 @@ class KerbAuth(object):
|
|||||||
"""
|
"""
|
||||||
Handles the various `request for token and service end point(s)` calls.
|
Handles the various `request for token and service end point(s)` calls.
|
||||||
There are various formats to support the various auth servers in the
|
There are various formats to support the various auth servers in the
|
||||||
past. Examples::
|
past.
|
||||||
|
|
||||||
|
"Active Mode" usage:
|
||||||
|
All formats require GSS (Kerberos) authentication.
|
||||||
|
|
||||||
GET <auth-prefix>/v1/<act>/auth
|
GET <auth-prefix>/v1/<act>/auth
|
||||||
GET <auth-prefix>/auth
|
GET <auth-prefix>/auth
|
||||||
GET <auth-prefix>/v1.0
|
GET <auth-prefix>/v1.0
|
||||||
|
|
||||||
All formats require GSS (Kerberos) authentication.
|
On successful authentication, the response will have X-Auth-Token
|
||||||
|
and X-Storage-Token set to the token to use with Swift.
|
||||||
|
|
||||||
On successful authentication, the response will have X-Auth-Token
|
"Passive Mode" usage::
|
||||||
set to the token to use with Swift.
|
|
||||||
|
GET <auth-prefix>/v1/<act>/auth
|
||||||
|
X-Auth-User: <act>:<usr> or X-Storage-User: <usr>
|
||||||
|
X-Auth-Key: <key> or X-Storage-Pass: <key>
|
||||||
|
GET <auth-prefix>/auth
|
||||||
|
X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr>
|
||||||
|
X-Auth-Key: <key> or X-Storage-Pass: <key>
|
||||||
|
GET <auth-prefix>/v1.0
|
||||||
|
X-Auth-User: <act>:<usr> or X-Storage-User: <act>:<usr>
|
||||||
|
X-Auth-Key: <key> or X-Storage-Pass: <key>
|
||||||
|
|
||||||
|
Values should be url encoded, "act%3Ausr" instead of "act:usr" for
|
||||||
|
example; however, for backwards compatibility the colon may be
|
||||||
|
included unencoded.
|
||||||
|
|
||||||
|
On successful authentication, the response will have X-Auth-Token
|
||||||
|
and X-Storage-Token set to the token to use with Swift and
|
||||||
|
X-Storage-URL set to the URL to the default Swift cluster to use.
|
||||||
|
|
||||||
:param req: The swob.Request to process.
|
:param req: The swob.Request to process.
|
||||||
:returns: swob.Response, 2xx on success with data set as explained
|
:returns: swob.Response, 2xx on success with data set as explained
|
||||||
@ -315,7 +354,103 @@ class KerbAuth(object):
|
|||||||
or pathsegs[0] in ('auth', 'v1.0')):
|
or pathsegs[0] in ('auth', 'v1.0')):
|
||||||
return HTTPBadRequest(request=req)
|
return HTTPBadRequest(request=req)
|
||||||
|
|
||||||
return HTTPSeeOther(location=self.ext_authentication_url)
|
# Client is inside the domain
|
||||||
|
if self.auth_method == "active":
|
||||||
|
return HTTPSeeOther(location=self.ext_authentication_url)
|
||||||
|
|
||||||
|
# Client is outside the domain
|
||||||
|
elif self.auth_method == "passive":
|
||||||
|
account, user, key = None, None, None
|
||||||
|
# Extract user, account and key from request
|
||||||
|
if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
|
||||||
|
account = pathsegs[1]
|
||||||
|
user = req.headers.get('x-storage-user')
|
||||||
|
if not user:
|
||||||
|
user = unquote(req.headers.get('x-auth-user', ''))
|
||||||
|
if user:
|
||||||
|
if ':' not in user:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
else:
|
||||||
|
account2, user = user.split(':', 1)
|
||||||
|
if account != account2:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
key = req.headers.get('x-storage-pass')
|
||||||
|
if not key:
|
||||||
|
key = unquote(req.headers.get('x-auth-key', ''))
|
||||||
|
elif pathsegs[0] in ('auth', 'v1.0'):
|
||||||
|
user = unquote(req.headers.get('x-auth-user', ''))
|
||||||
|
if not user:
|
||||||
|
user = req.headers.get('x-storage-user')
|
||||||
|
if user:
|
||||||
|
if ':' not in user:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
else:
|
||||||
|
account, user = user.split(':', 1)
|
||||||
|
key = unquote(req.headers.get('x-auth-key', ''))
|
||||||
|
if not key:
|
||||||
|
key = req.headers.get('x-storage-pass')
|
||||||
|
|
||||||
|
if not (account or user or key):
|
||||||
|
# If all are not given, client may be part of the domain
|
||||||
|
return HTTPSeeOther(location=self.ext_authentication_url)
|
||||||
|
elif None in (key, user, account):
|
||||||
|
# If only one or two of them is given, but not all
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
# Run kinit on the user
|
||||||
|
if self.realm_name and "@" not in user:
|
||||||
|
user = user + "@" + self.realm_name
|
||||||
|
try:
|
||||||
|
ret = run_kinit(user, key)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == errno.ENOENT:
|
||||||
|
return HTTPServerError("kinit command not found\n")
|
||||||
|
if ret != 0:
|
||||||
|
self.logger.warning("Failed: kinit %s", user)
|
||||||
|
if ret == -1:
|
||||||
|
self.logger.warning("Failed: kinit: Password has probably "
|
||||||
|
"expired.")
|
||||||
|
return HTTPServerError("Kinit is taking too long.\n")
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
self.logger.debug("kinit succeeded")
|
||||||
|
|
||||||
|
if "@" in user:
|
||||||
|
user = user.split("@")[0]
|
||||||
|
|
||||||
|
# Check if user really belongs to the account
|
||||||
|
groups_list = get_groups_from_username(user).strip().split(",")
|
||||||
|
user_group = ("%s%s" % (self.reseller_prefix, account)).lower()
|
||||||
|
reseller_admin_group = \
|
||||||
|
("%sreseller_admin" % self.reseller_prefix).lower()
|
||||||
|
if user_group not in groups_list:
|
||||||
|
# Check if user is reseller_admin. If not, return Unauthorized.
|
||||||
|
# On AD/IdM server, auth_reseller_admin is a separate group
|
||||||
|
if reseller_admin_group not in groups_list:
|
||||||
|
return HTTPUnauthorized(request=req)
|
||||||
|
|
||||||
|
mc = cache_from_env(req.environ)
|
||||||
|
if not mc:
|
||||||
|
raise Exception('Memcache required')
|
||||||
|
token, expires, groups = get_auth_data(mc, user)
|
||||||
|
if not token:
|
||||||
|
token = generate_token()
|
||||||
|
expires = time() + self.token_life
|
||||||
|
groups = get_groups_from_username(user)
|
||||||
|
set_auth_data(mc, user, token, expires, groups)
|
||||||
|
|
||||||
|
headers = {'X-Auth-Token': token,
|
||||||
|
'X-Storage-Token': token}
|
||||||
|
|
||||||
|
if self.debug_headers:
|
||||||
|
headers.update({'X-Debug-Remote-User': user,
|
||||||
|
'X-Debug-Groups:': groups,
|
||||||
|
'X-Debug-Token-Life': self.token_life,
|
||||||
|
'X-Debug-Token-Expires': ctime(expires)})
|
||||||
|
|
||||||
|
resp = Response(request=req, headers=headers)
|
||||||
|
resp.headers['X-Storage-Url'] = \
|
||||||
|
'%s/v1/%s%s' % (resp.host_url, self.reseller_prefix, account)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
@ -16,7 +16,8 @@
|
|||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
import grp
|
import grp
|
||||||
import subprocess
|
import signal
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
from time import time
|
from time import time
|
||||||
from gluster.swift.common.middleware.swiftkerbauth \
|
from gluster.swift.common.middleware.swiftkerbauth \
|
||||||
import TOKEN_LIFE, RESELLER_PREFIX
|
import TOKEN_LIFE, RESELLER_PREFIX
|
||||||
@ -82,13 +83,13 @@ def generate_token():
|
|||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
def get_groups(username):
|
def get_groups_from_username(username):
|
||||||
"""Return a set of groups to which the user belongs to."""
|
"""Return a set of groups to which the user belongs to."""
|
||||||
# Retrieve the numerical group IDs. We cannot list the group names
|
# Retrieve the numerical group IDs. We cannot list the group names
|
||||||
# because group names from Active Directory may contain spaces, and
|
# because group names from Active Directory may contain spaces, and
|
||||||
# we wouldn't be able to split the list of group names into its
|
# we wouldn't be able to split the list of group names into its
|
||||||
# elements.
|
# elements.
|
||||||
p = subprocess.Popen(['id', '-G', username], stdout=subprocess.PIPE)
|
p = Popen(['id', '-G', username], stdout=PIPE)
|
||||||
if p.wait() != 0:
|
if p.wait() != 0:
|
||||||
raise RuntimeError("Failure running id -G for %s" % username)
|
raise RuntimeError("Failure running id -G for %s" % username)
|
||||||
(p_stdout, p_stderr) = p.communicate()
|
(p_stdout, p_stderr) = p.communicate()
|
||||||
@ -105,3 +106,32 @@ def get_groups(username):
|
|||||||
groups = [username] + groups
|
groups = [username] + groups
|
||||||
groups = ','.join(groups)
|
groups = ','.join(groups)
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
|
||||||
|
def run_kinit(username, password):
|
||||||
|
"""Runs kinit command as a child process and returns the status code."""
|
||||||
|
kinit = Popen(['kinit', username],
|
||||||
|
stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||||
|
kinit.stdin.write('%s\n' % password)
|
||||||
|
|
||||||
|
# The following code handles a corner case where the Kerberos password
|
||||||
|
# has expired and a prompt is displayed to enter new password. Ideally,
|
||||||
|
# we would want to read from stdout but these are blocked reads. This is
|
||||||
|
# a hack to kill the process if it's taking too long!
|
||||||
|
|
||||||
|
class Alarm(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
raise Alarm
|
||||||
|
# Set the signal handler and a 1-second alarm
|
||||||
|
signal.signal(signal.SIGALRM, signal_handler)
|
||||||
|
signal.alarm(1)
|
||||||
|
try:
|
||||||
|
kinit.wait() # Wait for the child to exit
|
||||||
|
signal.alarm(0) # Reset the alarm
|
||||||
|
return kinit.returncode # Exit status of child on graceful exit
|
||||||
|
except Alarm:
|
||||||
|
# Taking too long, kill and return error
|
||||||
|
kinit.kill()
|
||||||
|
return -1
|
||||||
|
@ -18,9 +18,9 @@ import errno
|
|||||||
import unittest
|
import unittest
|
||||||
from time import time
|
from time import time
|
||||||
from mock import patch, Mock
|
from mock import patch, Mock
|
||||||
from gluster.swift.common.middleware.swiftkerbauth import kerbauth as auth
|
|
||||||
from test.unit import FakeMemcache
|
from test.unit import FakeMemcache
|
||||||
from swift.common.swob import Request, Response
|
from swift.common.swob import Request, Response
|
||||||
|
from gluster.swift.common.middleware.swiftkerbauth import kerbauth as auth
|
||||||
|
|
||||||
EXT_AUTHENTICATION_URL = "127.0.0.1"
|
EXT_AUTHENTICATION_URL = "127.0.0.1"
|
||||||
REDIRECT_STATUS = 303 # HTTPSeeOther
|
REDIRECT_STATUS = 303 # HTTPSeeOther
|
||||||
@ -80,7 +80,8 @@ class TestKerbAuth(unittest.TestCase):
|
|||||||
patch_filter_factory()
|
patch_filter_factory()
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.test_auth = auth.filter_factory({})(FakeApp())
|
self.test_auth = \
|
||||||
|
auth.filter_factory({'auth_method': 'active'})(FakeApp())
|
||||||
self.test_auth_passive = \
|
self.test_auth_passive = \
|
||||||
auth.filter_factory({'auth_method': 'passive'})(FakeApp())
|
auth.filter_factory({'auth_method': 'passive'})(FakeApp())
|
||||||
|
|
||||||
@ -105,6 +106,10 @@ class TestKerbAuth(unittest.TestCase):
|
|||||||
app = FakeApp()
|
app = FakeApp()
|
||||||
ath = auth.filter_factory({})(app)
|
ath = auth.filter_factory({})(app)
|
||||||
self.assertEquals(ath.reseller_prefix, 'AUTH_')
|
self.assertEquals(ath.reseller_prefix, 'AUTH_')
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': 'TEST'})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefix, 'TEST_')
|
||||||
|
ath = auth.filter_factory({'reseller_prefix': 'TEST_'})(app)
|
||||||
|
self.assertEquals(ath.reseller_prefix, 'TEST_')
|
||||||
|
|
||||||
def test_auth_prefix_init(self):
|
def test_auth_prefix_init(self):
|
||||||
app = FakeApp()
|
app = FakeApp()
|
||||||
@ -130,6 +135,19 @@ class TestKerbAuth(unittest.TestCase):
|
|||||||
self.assertEquals(req.environ['swift.authorize'],
|
self.assertEquals(req.environ['swift.authorize'],
|
||||||
self.test_auth.denied_response)
|
self.test_auth.denied_response)
|
||||||
|
|
||||||
|
def test_passive_top_level_deny(self):
|
||||||
|
req = self._make_request('/')
|
||||||
|
resp = req.get_response(self.test_auth_passive)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
self.assertEquals(req.environ['swift.authorize'],
|
||||||
|
self.test_auth_passive.denied_response)
|
||||||
|
|
||||||
|
def test_passive_deny_invalid_token(self):
|
||||||
|
req = self._make_request('/v1/AUTH_account',
|
||||||
|
headers={'X-Auth-Token': 'AUTH_t'})
|
||||||
|
resp = req.get_response(self.test_auth_passive)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
|
||||||
def test_override_asked_for_and_allowed(self):
|
def test_override_asked_for_and_allowed(self):
|
||||||
self.test_auth = \
|
self.test_auth = \
|
||||||
auth.filter_factory({'allow_overrides': 'true'})(FakeApp())
|
auth.filter_factory({'allow_overrides': 'true'})(FakeApp())
|
||||||
@ -249,6 +267,126 @@ class TestKerbAuth(unittest.TestCase):
|
|||||||
resp = self.test_auth.handle_get_token(req)
|
resp = self.test_auth.handle_get_token(req)
|
||||||
self.assertEquals(resp.status_int, 404)
|
self.assertEquals(resp.status_int, 404)
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_no_user_or_key(self):
|
||||||
|
#No user and key
|
||||||
|
req = self._make_request('/auth/v1.0')
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, REDIRECT_STATUS)
|
||||||
|
#User given but no key
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:user'})
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_account_in_req_path(self):
|
||||||
|
req = self._make_request('/v1/test/auth',
|
||||||
|
headers={'X-Auth-User': 'test:user',
|
||||||
|
'X-Auth-Key': 'password'})
|
||||||
|
_mock_run_kinit = Mock(return_value=0)
|
||||||
|
_mock_get_groups = Mock(return_value="user,auth_test")
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
|
||||||
|
_mock_get_groups):
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
_mock_run_kinit.assert_called_once_with('user', 'password')
|
||||||
|
self.assertEquals(_mock_get_groups.call_count, 2)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertTrue(resp.headers['X-Auth-Token'] is not None)
|
||||||
|
self.assertTrue(resp.headers['X-Storage-Token'] is not None)
|
||||||
|
self.assertTrue(resp.headers['X-Storage-Url'] is not None)
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_user_invalid_or_no__account(self):
|
||||||
|
#X-Auth-User not in acc:user format
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'user'})
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
req = self._make_request('/v1/test/auth',
|
||||||
|
headers={'X-Auth-User': 'user'})
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
# Account name mismatch
|
||||||
|
req = self._make_request('/v1/test/auth',
|
||||||
|
headers={'X-Auth-User': 'wrongacc:user'})
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_no_kinit(self):
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:user',
|
||||||
|
'X-Auth-Key': 'password'})
|
||||||
|
_mock_run_kinit = Mock(side_effect=OSError(errno.ENOENT,
|
||||||
|
os.strerror(errno.ENOENT)))
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 500)
|
||||||
|
self.assertTrue("kinit command not found" in resp.body)
|
||||||
|
_mock_run_kinit.assert_called_once_with('user', 'password')
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_kinit_fail(self):
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:user',
|
||||||
|
'X-Auth-Key': 'password'})
|
||||||
|
_mock_run_kinit = Mock(return_value=1)
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
_mock_run_kinit.assert_called_once_with('user', 'password')
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_kinit_success_token_not_present(self):
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:user',
|
||||||
|
'X-Auth-Key': 'password'})
|
||||||
|
_mock_run_kinit = Mock(return_value=0)
|
||||||
|
_mock_get_groups = Mock(return_value="user,auth_test")
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
|
||||||
|
_mock_get_groups):
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
_mock_run_kinit.assert_called_once_with('user', 'password')
|
||||||
|
self.assertEquals(_mock_get_groups.call_count, 2)
|
||||||
|
self.assertEquals(resp.status_int, 200)
|
||||||
|
self.assertTrue(resp.headers['X-Auth-Token'] is not None)
|
||||||
|
self.assertTrue(resp.headers['X-Storage-Token'] is not None)
|
||||||
|
self.assertTrue(resp.headers['X-Storage-Url'] is not None)
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_kinit_realm_and_memcache(self):
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:user',
|
||||||
|
'X-Auth-Key': 'password'})
|
||||||
|
req.environ['swift.cache'] = None
|
||||||
|
_auth_passive = \
|
||||||
|
auth.filter_factory({'auth_method': 'passive',
|
||||||
|
'realm_name': 'EXAMPLE.COM'})(FakeApp())
|
||||||
|
_mock_run_kinit = Mock(return_value=0)
|
||||||
|
_mock_get_groups = Mock(return_value="user,auth_test")
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
|
||||||
|
_mock_get_groups):
|
||||||
|
try:
|
||||||
|
_auth_passive.handle_get_token(req)
|
||||||
|
except Exception as e:
|
||||||
|
self.assertTrue(e.args[0].startswith("Memcache "
|
||||||
|
"required"))
|
||||||
|
else:
|
||||||
|
self.fail("Expected Exception - Memcache required")
|
||||||
|
_mock_run_kinit.assert_called_once_with('user@EXAMPLE.COM', 'password')
|
||||||
|
_mock_get_groups.assert_called_once_with('user')
|
||||||
|
|
||||||
|
def test_passive_handle_get_token_user_in_any__account(self):
|
||||||
|
req = self._make_request('/auth/v1.0',
|
||||||
|
headers={'X-Auth-User': 'test:user',
|
||||||
|
'X-Auth-Key': 'password'})
|
||||||
|
_mock_run_kinit = Mock(return_value=0)
|
||||||
|
_mock_get_groups = Mock(return_value="user,auth_blah")
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.run_kinit', _mock_run_kinit):
|
||||||
|
with patch('gluster.swift.common.middleware.swiftkerbauth.kerbauth.get_groups_from_username',
|
||||||
|
_mock_get_groups):
|
||||||
|
resp = self.test_auth_passive.handle_get_token(req)
|
||||||
|
self.assertEquals(resp.status_int, 401)
|
||||||
|
_mock_run_kinit.assert_called_once_with('user', 'password')
|
||||||
|
_mock_get_groups.assert_called_once_with('user')
|
||||||
|
|
||||||
def test_handle(self):
|
def test_handle(self):
|
||||||
req = self._make_request('/auth/v1.0')
|
req = self._make_request('/auth/v1.0')
|
||||||
resp = req.get_response(self.test_auth)
|
resp = req.get_response(self.test_auth)
|
||||||
|
@ -17,7 +17,6 @@ import unittest
|
|||||||
import re
|
import re
|
||||||
from time import time
|
from time import time
|
||||||
from test.unit import FakeMemcache
|
from test.unit import FakeMemcache
|
||||||
from gluster.swift.common.middleware.swiftkerbauth import kerbauth as auth
|
|
||||||
from gluster.swift.common.middleware.swiftkerbauth import kerbauth_utils as ku
|
from gluster.swift.common.middleware.swiftkerbauth import kerbauth_utils as ku
|
||||||
|
|
||||||
|
|
||||||
@ -63,15 +62,15 @@ class TestKerbUtils(unittest.TestCase):
|
|||||||
def test_generate_token(self):
|
def test_generate_token(self):
|
||||||
token = ku.generate_token()
|
token = ku.generate_token()
|
||||||
matches = re.match('AUTH_tk[a-f0-9]{32}', token)
|
matches = re.match('AUTH_tk[a-f0-9]{32}', token)
|
||||||
self.assertNotEqual(matches, None)
|
self.assertTrue(matches is not None)
|
||||||
|
|
||||||
def test_get_groups(self):
|
def test_get_groups_from_username(self):
|
||||||
groups = ku.get_groups("root")
|
groups = ku.get_groups_from_username("root")
|
||||||
self.assertTrue("root" in groups)
|
self.assertTrue("root" in groups)
|
||||||
|
|
||||||
def test_get_groups_err(self):
|
def test_get_groups_from_username_err(self):
|
||||||
try:
|
try:
|
||||||
ku.get_groups("Zroot")
|
ku.get_groups_from_username("Zroot")
|
||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
self.assertTrue(err.args[0].startswith("Failure running id -G"))
|
self.assertTrue(err.args[0].startswith("Failure running id -G"))
|
||||||
else:
|
else:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user