Documentation of the new auth and acls middleware modules and bugfixes
This commit is contained in:
@@ -42,6 +42,15 @@ Auth
|
|||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. _acls:
|
||||||
|
|
||||||
|
ACLs
|
||||||
|
====
|
||||||
|
|
||||||
|
.. automodule:: swift.common.middleware.acl
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
.. _wsgi:
|
.. _wsgi:
|
||||||
|
|
||||||
WSGI
|
WSGI
|
||||||
|
@@ -14,37 +14,101 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
def clean_acl(name, value):
|
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 = []
|
values = []
|
||||||
for raw_value in value.lower().split(','):
|
for raw_value in value.lower().split(','):
|
||||||
raw_value = raw_value.strip()
|
raw_value = raw_value.strip()
|
||||||
if raw_value:
|
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))
|
first, second = (v.strip() for v in raw_value.split(':', 1))
|
||||||
if not first:
|
if first != '.ref':
|
||||||
raise ValueError('No value before colon in %s' %
|
values.append(raw_value)
|
||||||
repr(raw_value))
|
elif 'write' in name:
|
||||||
if first == '.ref' and 'write' in name:
|
|
||||||
raise ValueError('Referrers not allowed in write ACLs: %s'
|
raise ValueError('Referrers not allowed in write ACLs: %s'
|
||||||
% repr(raw_value))
|
% repr(raw_value))
|
||||||
if second:
|
elif not second:
|
||||||
if first == '.ref' and second[0] == '-':
|
raise ValueError('No value after referrer designation in '
|
||||||
|
'%s' % repr(raw_value))
|
||||||
|
else:
|
||||||
|
if second[0] == '-':
|
||||||
second = second[1:].strip()
|
second = second[1:].strip()
|
||||||
if not second:
|
if not second:
|
||||||
raise ValueError('No value after referrer deny '
|
raise ValueError('No value after referrer deny '
|
||||||
'designation in %s' % repr(raw_value))
|
'designation in %s' % repr(raw_value))
|
||||||
second = '-' + second
|
second = '-' + second
|
||||||
values.append('%s:%s' % (first, 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)
|
return ','.join(values)
|
||||||
|
|
||||||
|
|
||||||
def parse_acl(acl_string):
|
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 = []
|
referrers = []
|
||||||
groups = []
|
groups = []
|
||||||
if acl_string:
|
if acl_string:
|
||||||
@@ -56,15 +120,29 @@ def parse_acl(acl_string):
|
|||||||
return referrers, groups
|
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
|
allow = False
|
||||||
if referrers:
|
if referrer_acl:
|
||||||
parts = req.referer.split('//', 1)
|
if not referrer:
|
||||||
if len(parts) == 2:
|
|
||||||
rhost = parts[1].split('/', 1)[0].split(':', 1)[0].lower()
|
|
||||||
else:
|
|
||||||
rhost = 'unknown'
|
rhost = 'unknown'
|
||||||
for mhost in referrers:
|
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 referrer_acl:
|
||||||
if mhost[0] == '-':
|
if mhost[0] == '-':
|
||||||
mhost = mhost[1:]
|
mhost = mhost[1:]
|
||||||
if mhost == rhost or \
|
if mhost == rhost or \
|
||||||
|
@@ -24,6 +24,7 @@ from swift.common.utils import cache_from_env, split_path
|
|||||||
|
|
||||||
|
|
||||||
class DevAuth(object):
|
class DevAuth(object):
|
||||||
|
"""Auth Middleware that uses the dev auth server."""
|
||||||
|
|
||||||
def __init__(self, app, conf):
|
def __init__(self, app, conf):
|
||||||
self.app = app
|
self.app = app
|
||||||
@@ -35,6 +36,11 @@ class DevAuth(object):
|
|||||||
self.timeout = int(conf.get('node_timeout', 10))
|
self.timeout = int(conf.get('node_timeout', 10))
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
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
|
user = None
|
||||||
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
||||||
if token:
|
if token:
|
||||||
@@ -64,13 +70,17 @@ class DevAuth(object):
|
|||||||
return self.app(env, start_response)
|
return self.app(env, start_response)
|
||||||
|
|
||||||
def authorize(self, req):
|
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)
|
version, account, container, obj = split_path(req.path, 1, 4, True)
|
||||||
if not account:
|
if not account:
|
||||||
return self.denied_response(req)
|
return self.denied_response(req)
|
||||||
if req.remote_user and account in req.remote_user.split(','):
|
if req.remote_user and account in req.remote_user.split(','):
|
||||||
return None
|
return None
|
||||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||||
if referrer_allowed(req, referrers):
|
if referrer_allowed(req.referer, referrers):
|
||||||
return None
|
return None
|
||||||
if not req.remote_user:
|
if not req.remote_user:
|
||||||
return self.denied_response(req)
|
return self.denied_response(req)
|
||||||
@@ -80,6 +90,10 @@ class DevAuth(object):
|
|||||||
return self.denied_response(req)
|
return self.denied_response(req)
|
||||||
|
|
||||||
def denied_response(self, 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:
|
if req.remote_user:
|
||||||
return HTTPForbidden(request=req)
|
return HTTPForbidden(request=req)
|
||||||
else:
|
else:
|
||||||
@@ -87,6 +101,7 @@ class DevAuth(object):
|
|||||||
|
|
||||||
|
|
||||||
def filter_factory(global_conf, **local_conf):
|
def filter_factory(global_conf, **local_conf):
|
||||||
|
"""Returns a WSGI filter app for use with paste.deploy."""
|
||||||
conf = global_conf.copy()
|
conf = global_conf.copy()
|
||||||
conf.update(local_conf)
|
conf.update(local_conf)
|
||||||
def auth_filter(app):
|
def auth_filter(app):
|
||||||
|
Reference in New Issue
Block a user