Documentation of the new auth and acls middleware modules and bugfixes

This commit is contained in:
gholt 2010-09-03 22:33:41 -07:00
parent bb01c22440
commit 65eb19f103
3 changed files with 124 additions and 22 deletions

View File

@ -42,6 +42,15 @@ Auth
:members:
:show-inheritance:
.. _acls:
ACLs
====
.. automodule:: swift.common.middleware.acl
:members:
:show-inheritance:
.. _wsgi:
WSGI

View File

@ -14,37 +14,101 @@
# limitations under the License.
def clean_acl(name, value):
"""
Returns a cleaned ACL header value, validating that it meets the formatting
requirements for standard Swift ACL strings.
The ACL format is::
[item[,item...]]
Each item can be a group name to give access to or a referrer designation
to grant or deny based on the HTTP Referer header.
The referrer designation format is::
.ref:[-]value
The value can be "any" to specify any referrer host is allowed access, a
specific host name like "www.example.com", or if it has a leading period
"." it is a domain name specification, like ".example.com". The leading
minus sign "-" indicates referrer hosts that should be denied access.
Referrer access is applied in the order they are specified. For example,
.ref:.example.com,.ref:-thief.example.com would allow all hosts ending with
.example.com except for the specific host thief.example.com.
Example valid ACLs::
.ref:any
.ref:any,.ref:-.thief.com
.ref:any,.ref:-.thief.com,bobs_account,sues_account:sue
bobs_account,sues_account:sue
Example invalid ACLs::
.ref:
.ref:-
Also, .ref designations aren't allowed in headers whose names include the
word 'write'.
ACLs that are "messy" will be cleaned up. Examples:
====================== ======================
Original Cleaned
---------------------- ----------------------
bob, sue bob,sue
bob , sue bob,sue
bob,,,sue bob,sue
.ref : any .ref:any
====================== ======================
:param name: The name of the header being cleaned, such as X-Container-Read
or X-Container-Write.
:param value: The value of the header being cleaned.
:returns: The value, cleaned of extraneous formatting.
:raises ValueError: If the value does not meet the ACL formatting
requirements; the error message will indicate why.
"""
values = []
for raw_value in value.lower().split(','):
raw_value = raw_value.strip()
if raw_value:
if ':' in raw_value:
if ':' not in raw_value:
values.append(raw_value)
else:
first, second = (v.strip() for v in raw_value.split(':', 1))
if not first:
raise ValueError('No value before colon in %s' %
repr(raw_value))
if first == '.ref' and 'write' in name:
if first != '.ref':
values.append(raw_value)
elif 'write' in name:
raise ValueError('Referrers not allowed in write ACLs: %s'
% repr(raw_value))
if second:
if first == '.ref' and second[0] == '-':
elif not second:
raise ValueError('No value after referrer designation in '
'%s' % repr(raw_value))
else:
if second[0] == '-':
second = second[1:].strip()
if not second:
raise ValueError('No value after referrer deny '
'designation in %s' % repr(raw_value))
second = '-' + second
values.append('%s:%s' % (first, second))
elif first == '.ref':
raise ValueError('No value after referrer designation in '
'%s' % repr(raw_value))
else:
values.append(first)
else:
values.append(raw_value)
return ','.join(values)
def parse_acl(acl_string):
"""
Parses a standard Swift ACL string into a referrers list and groups list.
See :func:`clean_acl` for documentation of the standard Swift ACL format.
:param acl_string: The standard Swift ACL string to parse.
:returns: A tuple of (referrers, groups) where referrers is a list of
referrer designations (without the leading .ref:) and groups is a
list of groups to allow access.
"""
referrers = []
groups = []
if acl_string:
@ -56,15 +120,29 @@ def parse_acl(acl_string):
return referrers, groups
def referrer_allowed(req, referrers):
def referrer_allowed(referrer, referrer_acl):
"""
Returns True if the referrer should be allowed based on the referrer_acl
list (as returned by :func:`parse_acl`).
See :func:`clean_acl` for documentation of the standard Swift ACL format.
:param referrer: The value of the HTTP Referer header.
:param referrer_acl: The list of referrer designations as returned by
:func:`parse_acl`.
:returns: True if the referrer should be allowed; False if not.
"""
allow = False
if referrers:
parts = req.referer.split('//', 1)
if referrer_acl:
if not referrer:
rhost = 'unknown'
else:
parts = referrer.split('//', 1)
if len(parts) == 2:
rhost = parts[1].split('/', 1)[0].split(':', 1)[0].lower()
else:
rhost = 'unknown'
for mhost in referrers:
for mhost in referrer_acl:
if mhost[0] == '-':
mhost = mhost[1:]
if mhost == rhost or \

View File

@ -24,6 +24,7 @@ from swift.common.utils import cache_from_env, split_path
class DevAuth(object):
"""Auth Middleware that uses the dev auth server."""
def __init__(self, app, conf):
self.app = app
@ -35,6 +36,11 @@ class DevAuth(object):
self.timeout = int(conf.get('node_timeout', 10))
def __call__(self, env, start_response):
"""
Accepts a standard WSGI application call, authenticating the request
and installing callback hooks for authorization and ACL header
validation.
"""
user = None
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
if token:
@ -64,13 +70,17 @@ class DevAuth(object):
return self.app(env, start_response)
def authorize(self, req):
"""
Returns None if the request is authorized to continue or a standard
WSGI response callable if not.
"""
version, account, container, obj = split_path(req.path, 1, 4, True)
if not account:
return self.denied_response(req)
if req.remote_user and account in req.remote_user.split(','):
return None
referrers, groups = parse_acl(getattr(req, 'acl', None))
if referrer_allowed(req, referrers):
if referrer_allowed(req.referer, referrers):
return None
if not req.remote_user:
return self.denied_response(req)
@ -80,6 +90,10 @@ class DevAuth(object):
return self.denied_response(req)
def denied_response(self, req):
"""
Returns a standard WSGI response callable with the status of 403 or 401
depending on whether the REMOTE_USER is set or not.
"""
if req.remote_user:
return HTTPForbidden(request=req)
else:
@ -87,6 +101,7 @@ class DevAuth(object):
def filter_factory(global_conf, **local_conf):
"""Returns a WSGI filter app for use with paste.deploy."""
conf = global_conf.copy()
conf.update(local_conf)
def auth_filter(app):