In light of the upcoming Keystone auth service, which should become the standard OpenStack auth service, Swauth will need to move to its own project. Because we don't want the Swift project to require Keystone (or Swauth or any other auth service for that matter) we need a "placeholder". In this proposal I'm adding a TempAuth to fill our auth testing needs within Swift and removing Swauth.
To quickly change from Swauth on a standard SAIO install, change swauth in your pipeline to tempauth and add the following section: [filter:tempauth] use = egg:swift#tempauth user_test_tester = testing .admin user_test2_tester2 = testing2 .admin user_test_tester3 = testing3 user_system_root = testpass .admin .reseller_admin
This commit is contained in:
commit
e32c146e27
@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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 gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='Usage: %prog [options] <account>')
|
||||
parser.add_option('-s', '--suffix', dest='suffix',
|
||||
default='', help='The suffix to use with the reseller prefix as the '
|
||||
'storage account name (default: <randomly-generated-uuid4>) Note: If '
|
||||
'the account already exists, this will have no effect on existing '
|
||||
'service URLs. Those will need to be updated with '
|
||||
'swauth-set-account-service')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/)')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
parser.parse_args(['-h'])
|
||||
account = args[0]
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
path = '%sv2/%s' % (parsed_path, account)
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
if options.suffix:
|
||||
headers['X-Account-Suffix'] = options.suffix
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
exit('Account creation failed: %s %s' % (resp.status, resp.reason))
|
@ -1,93 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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 gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(
|
||||
usage='Usage: %prog [options] <account> <user> <password>')
|
||||
parser.add_option('-a', '--admin', dest='admin', action='store_true',
|
||||
default=False, help='Give the user administrator access; otherwise '
|
||||
'the user will only have access to containers specifically allowed '
|
||||
'with ACLs.')
|
||||
parser.add_option('-r', '--reseller-admin', dest='reseller_admin',
|
||||
action='store_true', default=False, help='Give the user full reseller '
|
||||
'administrator access, giving them full access to all accounts within '
|
||||
'the reseller, including the ability to create new accounts. Creating '
|
||||
'a new reseller admin requires super_admin rights.')
|
||||
parser.add_option('-s', '--suffix', dest='suffix',
|
||||
default='', help='The suffix to use with the reseller prefix as the '
|
||||
'storage account name (default: <randomly-generated-uuid4>) Note: If '
|
||||
'the account already exists, this will have no effect on existing '
|
||||
'service URLs. Those will need to be updated with '
|
||||
'swauth-set-account-service')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 3:
|
||||
parser.parse_args(['-h'])
|
||||
account, user, password = args
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
# Ensure the account exists
|
||||
path = '%sv2/%s' % (parsed_path, account)
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
if options.suffix:
|
||||
headers['X-Account-Suffix'] = options.suffix
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
print 'Account creation failed: %s %s' % (resp.status, resp.reason)
|
||||
# Add the user
|
||||
path = '%sv2/%s/%s' % (parsed_path, account, user)
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key,
|
||||
'X-Auth-User-Key': password}
|
||||
if options.admin:
|
||||
headers['X-Auth-User-Admin'] = 'true'
|
||||
if options.reseller_admin:
|
||||
headers['X-Auth-User-Reseller-Admin'] = 'true'
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'PUT', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
exit('User creation failed: %s %s' % (resp.status, resp.reason))
|
@ -1,118 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import gettext
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from optparse import OptionParser
|
||||
from sys import argv, exit
|
||||
from time import sleep, time
|
||||
|
||||
from swift.common.client import Connection, ClientException
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='Usage: %prog [options]')
|
||||
parser.add_option('-t', '--token-life', dest='token_life',
|
||||
default='86400', help='The expected life of tokens; token objects '
|
||||
'modified more than this number of seconds ago will be checked for '
|
||||
'expiration (default: 86400).')
|
||||
parser.add_option('-s', '--sleep', dest='sleep',
|
||||
default='0.1', help='The number of seconds to sleep between token '
|
||||
'checks (default: 0.1)')
|
||||
parser.add_option('-v', '--verbose', dest='verbose', action='store_true',
|
||||
default=False, help='Outputs everything done instead of just the '
|
||||
'deletions.')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/)')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for .super_admin.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 0:
|
||||
parser.parse_args(['-h'])
|
||||
options.admin_url = options.admin_url.rstrip('/')
|
||||
if not options.admin_url.endswith('/v1.0'):
|
||||
options.admin_url += '/v1.0'
|
||||
options.admin_user = '.super_admin:.super_admin'
|
||||
options.token_life = timedelta(0, float(options.token_life))
|
||||
options.sleep = float(options.sleep)
|
||||
conn = Connection(options.admin_url, options.admin_user, options.admin_key)
|
||||
for x in xrange(16):
|
||||
container = '.token_%x' % x
|
||||
marker = None
|
||||
while True:
|
||||
if options.verbose:
|
||||
print 'GET %s?marker=%s' % (container, marker)
|
||||
try:
|
||||
objs = conn.get_container(container, marker=marker)[1]
|
||||
except ClientException, e:
|
||||
if e.http_status == 404:
|
||||
exit('Container %s not found. swauth-prep needs to be '
|
||||
'rerun' % (container))
|
||||
else:
|
||||
exit('Object listing on container %s failed with status '
|
||||
'code %d' % (container, e.http_status))
|
||||
if objs:
|
||||
marker = objs[-1]['name']
|
||||
else:
|
||||
if options.verbose:
|
||||
print 'No more objects in %s' % container
|
||||
break
|
||||
for obj in objs:
|
||||
last_modified = datetime(*map(int, re.split('[^\d]',
|
||||
obj['last_modified'])[:-1]))
|
||||
ago = datetime.utcnow() - last_modified
|
||||
if ago > options.token_life:
|
||||
if options.verbose:
|
||||
print '%s/%s last modified %ss ago; investigating' % \
|
||||
(container, obj['name'],
|
||||
ago.days * 86400 + ago.seconds)
|
||||
print 'GET %s/%s' % (container, obj['name'])
|
||||
detail = conn.get_object(container, obj['name'])[1]
|
||||
detail = json.loads(detail)
|
||||
if detail['expires'] < time():
|
||||
if options.verbose:
|
||||
print '%s/%s expired %ds ago; deleting' % \
|
||||
(container, obj['name'],
|
||||
time() - detail['expires'])
|
||||
print 'DELETE %s/%s' % (container, obj['name'])
|
||||
try:
|
||||
conn.delete_object(container, obj['name'])
|
||||
except ClientException, e:
|
||||
if e.http_status != 404:
|
||||
print 'DELETE of %s/%s failed with status ' \
|
||||
'code %d' % (container, obj['name'],
|
||||
e.http_status)
|
||||
elif options.verbose:
|
||||
print "%s/%s won't expire for %ds; skipping" % \
|
||||
(container, obj['name'],
|
||||
detail['expires'] - time())
|
||||
elif options.verbose:
|
||||
print '%s/%s last modified %ss ago; skipping' % \
|
||||
(container, obj['name'],
|
||||
ago.days * 86400 + ago.seconds)
|
||||
sleep(options.sleep)
|
||||
if options.verbose:
|
||||
print 'Done.'
|
@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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 gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='Usage: %prog [options] <account>')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 1:
|
||||
parser.parse_args(['-h'])
|
||||
account = args[0]
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
path = '%sv2/%s' % (parsed_path, account)
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
exit('Account deletion failed: %s %s' % (resp.status, resp.reason))
|
@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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 gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='Usage: %prog [options] <account> <user>')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 2:
|
||||
parser.parse_args(['-h'])
|
||||
account, user = args
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
path = '%sv2/%s/%s' % (parsed_path, account, user)
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'DELETE', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
exit('User deletion failed: %s %s' % (resp.status, resp.reason))
|
@ -1,86 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='''
|
||||
Usage: %prog [options] [account] [user]
|
||||
|
||||
If [account] and [user] are omitted, a list of accounts will be output.
|
||||
|
||||
If [account] is included but not [user], an account's information will be
|
||||
output, including a list of users within the account.
|
||||
|
||||
If [account] and [user] are included, the user's information will be output,
|
||||
including a list of groups the user belongs to.
|
||||
|
||||
If the [user] is '.groups', the active groups for the account will be listed.
|
||||
'''.strip())
|
||||
parser.add_option('-p', '--plain-text', dest='plain_text',
|
||||
action='store_true', default=False, help='Changes the output from '
|
||||
'JSON to plain text. This will cause an account to list only the '
|
||||
'users and a user to list only the groups.')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) > 2:
|
||||
parser.parse_args(['-h'])
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
path = '%sv2/%s' % (parsed_path, '/'.join(args))
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'GET', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
body = resp.read()
|
||||
if resp.status // 100 != 2:
|
||||
exit('List failed: %s %s' % (resp.status, resp.reason))
|
||||
if options.plain_text:
|
||||
info = json.loads(body)
|
||||
for group in info[['accounts', 'users', 'groups'][len(args)]]:
|
||||
print group['name']
|
||||
else:
|
||||
print body
|
@ -1,59 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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 gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='Usage: %prog [options]')
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if args:
|
||||
parser.parse_args(['-h'])
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
path = '%sv2/.prep' % parsed_path
|
||||
headers = {'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
exit('Auth subsystem prep failed: %s %s' % (resp.status, resp.reason))
|
@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2010 OpenStack, LLC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import gettext
|
||||
from optparse import OptionParser
|
||||
from os.path import basename
|
||||
from sys import argv, exit
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.utils import urlparse
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
parser = OptionParser(usage='''
|
||||
Usage: %prog [options] <account> <service> <name> <value>
|
||||
|
||||
Sets a service URL for an account. Can only be set by a reseller admin.
|
||||
|
||||
Example: %prog -K swauthkey test storage local http://127.0.0.1:8080/v1/AUTH_018c3946-23f8-4efb-a8fb-b67aae8e4162
|
||||
'''.strip())
|
||||
parser.add_option('-A', '--admin-url', dest='admin_url',
|
||||
default='http://127.0.0.1:8080/auth/', help='The URL to the auth '
|
||||
'subsystem (default: http://127.0.0.1:8080/auth/)')
|
||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
||||
default='.super_admin', help='The user with admin rights to add users '
|
||||
'(default: .super_admin).')
|
||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
||||
help='The key for the user with admin rights to add users.')
|
||||
args = argv[1:]
|
||||
if not args:
|
||||
args.append('-h')
|
||||
(options, args) = parser.parse_args(args)
|
||||
if len(args) != 4:
|
||||
parser.parse_args(['-h'])
|
||||
account, service, name, url = args
|
||||
parsed = urlparse(options.admin_url)
|
||||
if parsed.scheme not in ('http', 'https'):
|
||||
raise Exception('Cannot handle protocol scheme %s for url %s' %
|
||||
(parsed.scheme, repr(options.admin_url)))
|
||||
parsed_path = parsed.path
|
||||
if not parsed_path:
|
||||
parsed_path = '/'
|
||||
elif parsed_path[-1] != '/':
|
||||
parsed_path += '/'
|
||||
path = '%sv2/%s/.services' % (parsed_path, account)
|
||||
body = json.dumps({service: {name: url}})
|
||||
headers = {'Content-Length': str(len(body)),
|
||||
'X-Auth-Admin-User': options.admin_user,
|
||||
'X-Auth-Admin-Key': options.admin_key}
|
||||
conn = http_connect(parsed.hostname, parsed.port, 'POST', path, headers,
|
||||
ssl=(parsed.scheme == 'https'))
|
||||
conn.send(body)
|
||||
resp = conn.getresponse()
|
||||
if resp.status // 100 != 2:
|
||||
exit('Service set failed: %s %s' % (resp.status, resp.reason))
|
@ -222,22 +222,6 @@ place and then rerun the dispersion report::
|
||||
Sample represents 1.00% of the object partition space
|
||||
|
||||
|
||||
------------------------------------
|
||||
Additional Cleanup Script for Swauth
|
||||
------------------------------------
|
||||
|
||||
With Swauth, you'll want to install a cronjob to clean up any
|
||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
||||
occurs where a single user authenticates several times concurrently. Generally,
|
||||
these orphaned tokens don't pose much of an issue, but it's good to clean them
|
||||
up once a "token life" period (default: 1 day or 86400 seconds).
|
||||
|
||||
This should be as simple as adding `swauth-cleanup-tokens -A
|
||||
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
|
||||
entry on one of the proxies that is running Swauth; but run
|
||||
`swauth-cleanup-tokens` with no arguments for detailed help on the options
|
||||
available.
|
||||
|
||||
------------------------
|
||||
Debugging Tips and Tools
|
||||
------------------------
|
||||
|
@ -549,35 +549,17 @@ allow_account_management false Whether account PUTs and DELETEs
|
||||
are even callable
|
||||
============================ =============== =============================
|
||||
|
||||
[auth]
|
||||
|
||||
============ =================================== ========================
|
||||
Option Default Description
|
||||
------------ ----------------------------------- ------------------------
|
||||
use Entry point for paste.deploy
|
||||
to use for auth. To
|
||||
use the swift dev auth,
|
||||
set to:
|
||||
`egg:swift#auth`
|
||||
ip 127.0.0.1 IP address of auth
|
||||
server
|
||||
port 11000 Port of auth server
|
||||
ssl False If True, use SSL to
|
||||
connect to auth
|
||||
node_timeout 10 Request timeout
|
||||
============ =================================== ========================
|
||||
|
||||
[swauth]
|
||||
[tempauth]
|
||||
|
||||
===================== =============================== =======================
|
||||
Option Default Description
|
||||
--------------------- ------------------------------- -----------------------
|
||||
use Entry point for
|
||||
paste.deploy to use for
|
||||
auth. To use the swauth
|
||||
auth. To use tempauth
|
||||
set to:
|
||||
`egg:swift#swauth`
|
||||
set log_name auth-server Label used when logging
|
||||
`egg:swift#tempauth`
|
||||
set log_name tempauth Label used when logging
|
||||
set log_facility LOG_LOCAL0 Syslog log facility
|
||||
set log_level INFO Log level
|
||||
set log_headers True If True, log headers in
|
||||
@ -593,16 +575,39 @@ auth_prefix /auth/ The HTTP request path
|
||||
reserves anything
|
||||
beginning with the
|
||||
letter `v`.
|
||||
default_swift_cluster local#http://127.0.0.1:8080/v1 The default Swift
|
||||
cluster to place newly
|
||||
created accounts on.
|
||||
token_life 86400 The number of seconds a
|
||||
token is valid.
|
||||
node_timeout 10 Request timeout
|
||||
super_admin_key None The key for the
|
||||
.super_admin account.
|
||||
===================== =============================== =======================
|
||||
|
||||
Additionally, you need to list all the accounts/users you want here. The format
|
||||
is::
|
||||
|
||||
user_<account>_<user> = <key> [group] [group] [...] [storage_url]
|
||||
|
||||
There are special groups of::
|
||||
|
||||
.reseller_admin = can do anything to any account for this auth
|
||||
.admin = can do anything within the account
|
||||
|
||||
If neither of these groups are specified, the user can only access containers
|
||||
that have been explicitly allowed for them by a .admin or .reseller_admin.
|
||||
|
||||
The trailing optional storage_url allows you to specify an alternate url to
|
||||
hand back to the user upon authentication. If not specified, this defaults to::
|
||||
|
||||
http[s]://<ip>:<port>/v1/<reseller_prefix>_<account>
|
||||
|
||||
Where http or https depends on whether cert_file is specified in the [DEFAULT]
|
||||
section, <ip> and <port> are based on the [DEFAULT] section's bind_ip and
|
||||
bind_port (falling back to 127.0.0.1 and 8080), <reseller_prefix> is from this
|
||||
section, and <account> is from the user_<account>_<user> name.
|
||||
|
||||
Here are example entries, required for running the tests::
|
||||
|
||||
user_admin_admin = admin .admin .reseller_admin
|
||||
user_test_tester = testing .admin
|
||||
user_test2_tester2 = testing2 .admin
|
||||
user_test_tester3 = testing3
|
||||
|
||||
------------------------
|
||||
Memcached Considerations
|
||||
|
@ -6,7 +6,7 @@ Auth Server and Middleware
|
||||
Creating Your Own Auth Server and Middleware
|
||||
--------------------------------------------
|
||||
|
||||
The included swift/common/middleware/swauth.py is a good example of how to
|
||||
The included swift/common/middleware/tempauth.py is a good example of how to
|
||||
create an auth subsystem with proxy server auth middleware. The main points are
|
||||
that the auth middleware can reject requests up front, before they ever get to
|
||||
the Swift Proxy application, and afterwards when the proxy issues callbacks to
|
||||
@ -27,7 +27,7 @@ specific information, it just passes it along. Convention has
|
||||
environ['REMOTE_USER'] set to the authenticated user string but often more
|
||||
information is needed than just that.
|
||||
|
||||
The included Swauth will set the REMOTE_USER to a comma separated list of
|
||||
The included TempAuth will set the REMOTE_USER to a comma separated list of
|
||||
groups the user belongs to. The first group will be the "user's group", a group
|
||||
that only the user belongs to. The second group will be the "account's group",
|
||||
a group that includes all users for that auth account (different than the
|
||||
@ -37,7 +37,7 @@ will be omitted.
|
||||
|
||||
It is highly recommended that authentication server implementers prefix their
|
||||
tokens and Swift storage accounts they create with a configurable reseller
|
||||
prefix (`AUTH_` by default with the included Swauth). This prefix will avoid
|
||||
prefix (`AUTH_` by default with the included TempAuth). This prefix will avoid
|
||||
conflicts with other authentication servers that might be using the same
|
||||
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
|
||||
until one validates a token or all fail.
|
||||
@ -46,14 +46,14 @@ A restriction with group names is that no group name should begin with a period
|
||||
'.' as that is reserved for internal Swift use (such as the .r for referrer
|
||||
designations as you'll see later).
|
||||
|
||||
Example Authentication with Swauth:
|
||||
Example Authentication with TempAuth:
|
||||
|
||||
* Token AUTH_tkabcd is given to the Swauth middleware in a request's
|
||||
* Token AUTH_tkabcd is given to the TempAuth middleware in a request's
|
||||
X-Auth-Token header.
|
||||
* The Swauth middleware validates the token AUTH_tkabcd and discovers
|
||||
* The TempAuth middleware validates the token AUTH_tkabcd and discovers
|
||||
it matches the "tester" user within the "test" account for the storage
|
||||
account "AUTH_storage_xyz".
|
||||
* The Swauth server sets the REMOTE_USER to
|
||||
* The TempAuth middleware sets the REMOTE_USER to
|
||||
"test:tester,test,AUTH_storage_xyz"
|
||||
* Now this user will have full access (via authorization procedures later)
|
||||
to the AUTH_storage_xyz Swift storage account and access to containers in
|
||||
|
@ -265,16 +265,18 @@ Sample configuration files are provided with all defaults in line-by-line commen
|
||||
log_facility = LOG_LOCAL1
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck cache swauth proxy-server
|
||||
pipeline = healthcheck cache tempauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
# Highly recommended to change this.
|
||||
super_admin_key = swauthkey
|
||||
[filter:tempauth]
|
||||
use = egg:swift#tempauth
|
||||
user_admin_admin = admin .admin .reseller_admin
|
||||
user_test_tester = testing .admin
|
||||
user_test2_tester2 = testing2 .admin
|
||||
user_test_tester3 = testing3
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
@ -558,8 +560,10 @@ Setting up scripts for running Swift
|
||||
------------------------------------
|
||||
|
||||
#. Create `~/bin/resetswift.`
|
||||
If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
|
||||
If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
|
||||
|
||||
If you are using a loopback device substitute `/dev/sdb1` with `/srv/swift-disk`.
|
||||
|
||||
If you did not set up rsyslog for individual logging, remove the `find /var/log/swift...` line::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
@ -608,18 +612,6 @@ Setting up scripts for running Swift
|
||||
|
||||
swift-init main start
|
||||
|
||||
#. Create `~/bin/recreateaccounts`::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Replace swauthkey with whatever your super_admin key is (recorded in
|
||||
# /etc/swift/proxy-server.conf).
|
||||
swauth-prep -K swauthkey
|
||||
swauth-add-user -K swauthkey -a test tester testing
|
||||
swauth-add-user -K swauthkey -a test2 tester2 testing2
|
||||
swauth-add-user -K swauthkey test tester3 testing3
|
||||
swauth-add-user -K swauthkey -a -r reseller reseller reseller
|
||||
|
||||
#. Create `~/bin/startrest`::
|
||||
|
||||
#!/bin/bash
|
||||
|
@ -13,7 +13,7 @@ Prerequisites
|
||||
Basic architecture and terms
|
||||
----------------------------
|
||||
- *node* - a host machine running one or more Swift services
|
||||
- *Proxy node* - node that runs Proxy services; also runs Swauth
|
||||
- *Proxy node* - node that runs Proxy services; also runs TempAuth
|
||||
- *Storage node* - node that runs Account, Container, and Object services
|
||||
- *ring* - a set of mappings of Swift data to physical devices
|
||||
|
||||
@ -23,7 +23,7 @@ This document shows a cluster using the following types of nodes:
|
||||
|
||||
- Runs the swift-proxy-server processes which proxy requests to the
|
||||
appropriate Storage nodes. The proxy server will also contain
|
||||
the Swauth service as WSGI middleware.
|
||||
the TempAuth service as WSGI middleware.
|
||||
|
||||
- five Storage nodes
|
||||
|
||||
@ -130,17 +130,15 @@ Configure the Proxy node
|
||||
user = swift
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck cache swauth proxy-server
|
||||
pipeline = healthcheck cache tempauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = swauthkey
|
||||
[filter:tempauth]
|
||||
use = egg:swift#tempauth
|
||||
user_system_root = testpass .admin https://$PROXY_LOCAL_NET_IP:8080/v1/AUTH_system
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
@ -366,16 +364,6 @@ Create Swift admin account and test
|
||||
|
||||
You run these commands from the Proxy node.
|
||||
|
||||
#. Create a user with administrative privileges (account = system,
|
||||
username = root, password = testpass). Make sure to replace
|
||||
``swauthkey`` with whatever super_admin key you assigned in
|
||||
the proxy-server.conf file
|
||||
above. *Note: None of the values of
|
||||
account, username, or password are special - they can be anything.*::
|
||||
|
||||
swauth-prep -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey
|
||||
swauth-add-user -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey -a system root testpass
|
||||
|
||||
#. Get an X-Storage-Url and X-Auth-Token::
|
||||
|
||||
curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://$PROXY_LOCAL_NET_IP:8080/auth/v1.0
|
||||
@ -430,45 +418,16 @@ See :ref:`config-proxy` for the initial setup, and then follow these additional
|
||||
use = egg:swift#memcache
|
||||
memcache_servers = $PROXY_LOCAL_NET_IP:11211
|
||||
|
||||
#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
|
||||
#. Change the storage url for any users to point to the load balanced url, rather than the first proxy server you created in /etc/swift/proxy-server.conf::
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = swauthkey
|
||||
|
||||
#. The above will make new accounts with the new default_swift_cluster URL, however it won't change any existing accounts. You can change a service URL for existing accounts with::
|
||||
|
||||
First retreve what the URL was::
|
||||
|
||||
swauth-list -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account>
|
||||
|
||||
And then update it with::
|
||||
|
||||
swauth-set-account-service -A https://$PROXY_LOCAL_NET_IP:8080/auth/ -K swauthkey <account> storage local <new_url_for_the_account>
|
||||
|
||||
Make the <new_url_for_the_account> look just like it's original URL but with the host:port update you want.
|
||||
[filter:tempauth]
|
||||
use = egg:swift#tempauth
|
||||
user_system_root = testpass .admin http[s]://<LOAD_BALANCER_HOSTNAME>:<PORT>/v1/AUTH_system
|
||||
|
||||
#. Next, copy all the ring information to all the nodes, including your new proxy nodes, and ensure the ring info gets to all the storage nodes as well.
|
||||
|
||||
#. After you sync all the nodes, make sure the admin has the keys in /etc/swift and the ownership for the ring file is correct.
|
||||
|
||||
Additional Cleanup Script for Swauth
|
||||
------------------------------------
|
||||
|
||||
With Swauth, you'll want to install a cronjob to clean up any
|
||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
||||
occurs where a single user authenticates several times concurrently. Generally,
|
||||
these orphaned tokens don't pose much of an issue, but it's good to clean them
|
||||
up once a "token life" period (default: 1 day or 86400 seconds).
|
||||
|
||||
This should be as simple as adding `swauth-cleanup-tokens -A
|
||||
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
|
||||
entry on one of the proxies that is running Swauth; but run
|
||||
`swauth-cleanup-tokens` with no arguments for detailed help on the options
|
||||
available.
|
||||
|
||||
Troubleshooting Notes
|
||||
---------------------
|
||||
If you see problems, look in var/log/syslog (or messages on some distros).
|
||||
|
@ -33,12 +33,12 @@ Utils
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _common_swauth:
|
||||
.. _common_tempauth:
|
||||
|
||||
Swauth
|
||||
======
|
||||
TempAuth
|
||||
========
|
||||
|
||||
.. automodule:: swift.common.middleware.swauth
|
||||
.. automodule:: swift.common.middleware.tempauth
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
@ -2,9 +2,9 @@
|
||||
The Auth System
|
||||
===============
|
||||
|
||||
------
|
||||
Swauth
|
||||
------
|
||||
--------
|
||||
TempAuth
|
||||
--------
|
||||
|
||||
The auth system for Swift is loosely based on the auth system from the existing
|
||||
Rackspace architecture -- actually from a few existing auth systems -- and is
|
||||
@ -27,7 +27,7 @@ validation.
|
||||
Swift will make calls to the auth system, giving the auth token to be
|
||||
validated. For a valid token, the auth system responds with an overall
|
||||
expiration in seconds from now. Swift will cache the token up to the expiration
|
||||
time. The included Swauth also has the concept of admin and non-admin users
|
||||
time. The included TempAuth also has the concept of admin and non-admin users
|
||||
within an account. Admin users can do anything within the account. Non-admin
|
||||
users can only perform operations per container based on the container's
|
||||
X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
|
||||
@ -40,152 +40,9 @@ receive the auth token and a URL to the Swift system.
|
||||
Extending Auth
|
||||
--------------
|
||||
|
||||
Swauth is written as wsgi middleware, so implementing your own auth is as easy
|
||||
as writing new wsgi middleware, and plugging it in to the proxy server.
|
||||
TempAuth is written as wsgi middleware, so implementing your own auth is as
|
||||
easy as writing new wsgi middleware, and plugging it in to the proxy server.
|
||||
The KeyStone project and the Swauth project are examples of additional auth
|
||||
services.
|
||||
|
||||
Also, see :doc:`development_auth`.
|
||||
|
||||
|
||||
--------------
|
||||
Swauth Details
|
||||
--------------
|
||||
|
||||
The Swauth system is included at swift/common/middleware/swauth.py; a scalable
|
||||
authentication and authorization system that uses Swift itself as its backing
|
||||
store. This section will describe how it stores its data.
|
||||
|
||||
At the topmost level, the auth system has its own Swift account it stores its
|
||||
own account information within. This Swift account is known as
|
||||
self.auth_account in the code and its name is in the format
|
||||
self.reseller_prefix + ".auth". In this text, we'll refer to this account as
|
||||
<auth_account>.
|
||||
|
||||
The containers whose names do not begin with a period represent the accounts
|
||||
within the auth service. For example, the <auth_account>/test container would
|
||||
represent the "test" account.
|
||||
|
||||
The objects within each container represent the users for that auth service
|
||||
account. For example, the <auth_account>/test/bob object would represent the
|
||||
user "bob" within the auth service account of "test". Each of these user
|
||||
objects contain a JSON dictionary of the format::
|
||||
|
||||
{"auth": "<auth_type>:<auth_value>", "groups": <groups_array>}
|
||||
|
||||
The `<auth_type>` can only be `plaintext` at this time, and the `<auth_value>`
|
||||
is the plain text password itself.
|
||||
|
||||
The `<groups_array>` contains at least two groups. The first is a unique group
|
||||
identifying that user and it's name is of the format `<user>:<account>`. The
|
||||
second group is the `<account>` itself. Additional groups of `.admin` for
|
||||
account administrators and `.reseller_admin` for reseller administrators may
|
||||
exist. Here's an example user JSON dictionary::
|
||||
|
||||
{"auth": "plaintext:testing",
|
||||
"groups": ["name": "test:tester", "name": "test", "name": ".admin"]}
|
||||
|
||||
To map an auth service account to a Swift storage account, the Service Account
|
||||
Id string is stored in the `X-Container-Meta-Account-Id` header for the
|
||||
<auth_account>/<account> container. To map back the other way, an
|
||||
<auth_account>/.account_id/<account_id> object is created with the contents of
|
||||
the corresponding auth service's account name.
|
||||
|
||||
Also, to support a future where the auth service will support multiple Swift
|
||||
clusters or even multiple services for the same auth service account, an
|
||||
<auth_account>/<account>/.services object is created with its contents having a
|
||||
JSON dictionary of the format::
|
||||
|
||||
{"storage": {"default": "local", "local": <url>}}
|
||||
|
||||
The "default" is always "local" right now, and "local" is always the single
|
||||
Swift cluster URL; but in the future there can be more than one cluster with
|
||||
various names instead of just "local", and the "default" key's value will
|
||||
contain the primary cluster to use for that account. Also, there may be more
|
||||
services in addition to the current "storage" service right now.
|
||||
|
||||
Here's an example .services dictionary at the moment::
|
||||
|
||||
{"storage":
|
||||
{"default": "local",
|
||||
"local": "http://127.0.0.1:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
|
||||
|
||||
But, here's an example of what the dictionary may look like in the future::
|
||||
|
||||
{"storage":
|
||||
{"default": "dfw",
|
||||
"dfw": "http://dfw.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"ord": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"sat": "http://ord.storage.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"},
|
||||
"servers":
|
||||
{"default": "dfw",
|
||||
"dfw": "http://dfw.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"ord": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"sat": "http://ord.servers.com:8080/v1/AUTH_8980f74b1cda41e483cbe0a925f448a9"}}
|
||||
|
||||
Lastly, the tokens themselves are stored as objects in the
|
||||
`<auth_account>/.token_[0-f]` containers. The names of the objects are the
|
||||
token strings themselves, such as `AUTH_tked86bbd01864458aa2bd746879438d5a`.
|
||||
The exact `.token_[0-f]` container chosen is based on the final digit of the
|
||||
token name, such as `.token_a` for the token
|
||||
`AUTH_tked86bbd01864458aa2bd746879438d5a`. The contents of the token objects
|
||||
are JSON dictionaries of the format::
|
||||
|
||||
{"account": <account>,
|
||||
"user": <user>,
|
||||
"account_id": <account_id>,
|
||||
"groups": <groups_array>,
|
||||
"expires": <time.time() value>}
|
||||
|
||||
The `<account>` is the auth service account's name for that token. The `<user>`
|
||||
is the user within the account for that token. The `<account_id>` is the
|
||||
same as the `X-Container-Meta-Account-Id` for the auth service's account,
|
||||
as described above. The `<groups_array>` is the user's groups, as described
|
||||
above with the user object. The "expires" value indicates when the token is no
|
||||
longer valid, as compared to Python's time.time() value.
|
||||
|
||||
Here's an example token object's JSON dictionary::
|
||||
|
||||
{"account": "test",
|
||||
"user": "tester",
|
||||
"account_id": "AUTH_8980f74b1cda41e483cbe0a925f448a9",
|
||||
"groups": ["name": "test:tester", "name": "test", "name": ".admin"],
|
||||
"expires": 1291273147.1624689}
|
||||
|
||||
To easily map a user to an already issued token, the token name is stored in
|
||||
the user object's `X-Object-Meta-Auth-Token` header.
|
||||
|
||||
Here is an example full listing of an <auth_account>::
|
||||
|
||||
.account_id
|
||||
AUTH_2282f516-559f-4966-b239-b5c88829e927
|
||||
AUTH_f6f57a3c-33b5-4e85-95a5-a801e67505c8
|
||||
AUTH_fea96a36-c177-4ca4-8c7e-b8c715d9d37b
|
||||
.token_0
|
||||
.token_1
|
||||
.token_2
|
||||
.token_3
|
||||
.token_4
|
||||
.token_5
|
||||
.token_6
|
||||
AUTH_tk9d2941b13d524b268367116ef956dee6
|
||||
.token_7
|
||||
.token_8
|
||||
AUTH_tk93627c6324c64f78be746f1e6a4e3f98
|
||||
.token_9
|
||||
.token_a
|
||||
.token_b
|
||||
.token_c
|
||||
.token_d
|
||||
.token_e
|
||||
AUTH_tk0d37d286af2c43ffad06e99112b3ec4e
|
||||
.token_f
|
||||
AUTH_tk766bbde93771489982d8dc76979d11cf
|
||||
reseller
|
||||
.services
|
||||
reseller
|
||||
test
|
||||
.services
|
||||
tester
|
||||
tester3
|
||||
test2
|
||||
.services
|
||||
tester2
|
||||
|
@ -13,7 +13,7 @@
|
||||
# log_level = INFO
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
|
||||
pipeline = catch_errors healthcheck cache ratelimit tempauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
@ -41,10 +41,10 @@ use = egg:swift#proxy
|
||||
# 'false' no one, even authorized, can.
|
||||
# allow_account_management = false
|
||||
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
[filter:tempauth]
|
||||
use = egg:swift#tempauth
|
||||
# You can override the default log routing for this filter here:
|
||||
# set log_name = auth-server
|
||||
# set log_name = tempauth
|
||||
# set log_facility = LOG_LOCAL0
|
||||
# set log_level = INFO
|
||||
# set log_headers = False
|
||||
@ -54,21 +54,28 @@ use = egg:swift#swauth
|
||||
# multiple auth systems are in use for one Swift cluster.
|
||||
# reseller_prefix = AUTH
|
||||
# The auth prefix will cause requests beginning with this prefix to be routed
|
||||
# to the auth subsystem, for granting tokens, creating accounts, users, etc.
|
||||
# to the auth subsystem, for granting tokens, etc.
|
||||
# auth_prefix = /auth/
|
||||
# Cluster strings are of the format name#url where name is a short name for the
|
||||
# Swift cluster and url is the url to the proxy server(s) for the cluster.
|
||||
# default_swift_cluster = local#http://127.0.0.1:8080/v1
|
||||
# You may also use the format name#url#url where the first url is the one
|
||||
# given to users to access their account (public url) and the second is the one
|
||||
# used by swauth itself to create and delete accounts (private url). This is
|
||||
# useful when a load balancer url should be used by users, but swauth itself is
|
||||
# behind the load balancer. Example:
|
||||
# default_swift_cluster = local#https://public.com:8080/v1#http://private.com:8080/v1
|
||||
# token_life = 86400
|
||||
# node_timeout = 10
|
||||
# Highly recommended to change this.
|
||||
super_admin_key = swauthkey
|
||||
# Lastly, you need to list all the accounts/users you want here. The format is:
|
||||
# user_<account>_<user> = <key> [group] [group] [...] [storage_url]
|
||||
# There are special groups of:
|
||||
# .reseller_admin = can do anything to any account for this auth
|
||||
# .admin = can do anything within the account
|
||||
# If neither of these groups are specified, the user can only access containers
|
||||
# that have been explicitly allowed for them by a .admin or .reseller_admin.
|
||||
# The trailing optional storage_url allows you to specify an alternate url to
|
||||
# hand back to the user upon authentication. If not specified, this defaults to
|
||||
# http[s]://<ip>:<port>/v1/<reseller_prefix>_<account> where http or https
|
||||
# depends on whether cert_file is specified in the [DEFAULT] section, <ip> and
|
||||
# <port> are based on the [DEFAULT] section's bind_ip and bind_port (falling
|
||||
# back to 127.0.0.1 and 8080), <reseller_prefix> is from this section, and
|
||||
# <account> is from the user_<account>_<user> name.
|
||||
# Here are example entries, required for running the tests:
|
||||
user_admin_admin = admin .admin .reseller_admin
|
||||
user_test_tester = testing .admin
|
||||
user_test2_tester2 = testing2 .admin
|
||||
user_test_tester3 = testing3
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
|
6
setup.py
6
setup.py
@ -96,10 +96,6 @@ setup(
|
||||
'bin/swift-log-stats-collector',
|
||||
'bin/swift-account-stats-logger',
|
||||
'bin/swift-container-stats-logger',
|
||||
'bin/swauth-add-account', 'bin/swauth-add-user',
|
||||
'bin/swauth-cleanup-tokens', 'bin/swauth-delete-account',
|
||||
'bin/swauth-delete-user', 'bin/swauth-list', 'bin/swauth-prep',
|
||||
'bin/swauth-set-account-service',
|
||||
],
|
||||
entry_points={
|
||||
'paste.app_factory': [
|
||||
@ -109,7 +105,6 @@ setup(
|
||||
'account=swift.account.server:app_factory',
|
||||
],
|
||||
'paste.filter_factory': [
|
||||
'swauth=swift.common.middleware.swauth:filter_factory',
|
||||
'healthcheck=swift.common.middleware.healthcheck:filter_factory',
|
||||
'memcache=swift.common.middleware.memcache:filter_factory',
|
||||
'ratelimit=swift.common.middleware.ratelimit:filter_factory',
|
||||
@ -118,6 +113,7 @@ setup(
|
||||
'domain_remap=swift.common.middleware.domain_remap:filter_factory',
|
||||
'swift3=swift.common.middleware.swift3:filter_factory',
|
||||
'staticweb=swift.common.middleware.staticweb:filter_factory',
|
||||
'tempauth=swift.common.middleware.tempauth:filter_factory',
|
||||
],
|
||||
},
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ added. For example::
|
||||
...
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = healthcheck cache swauth staticweb proxy-server
|
||||
pipeline = healthcheck cache tempauth staticweb proxy-server
|
||||
|
||||
...
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
482
swift/common/middleware/tempauth.py
Normal file
482
swift/common/middleware/tempauth.py
Normal file
@ -0,0 +1,482 @@
|
||||
# Copyright (c) 2011 OpenStack, LLC.
|
||||
#
|
||||
# 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 time import gmtime, strftime, time
|
||||
from traceback import format_exc
|
||||
from urllib import quote, unquote
|
||||
from uuid import uuid4
|
||||
from hashlib import sha1
|
||||
import hmac
|
||||
import base64
|
||||
|
||||
from eventlet import TimeoutError
|
||||
from webob import Response, Request
|
||||
from webob.exc import HTTPBadRequest, HTTPForbidden, HTTPNotFound, \
|
||||
HTTPUnauthorized
|
||||
|
||||
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
||||
from swift.common.utils import cache_from_env, get_logger, split_path
|
||||
|
||||
|
||||
class TempAuth(object):
|
||||
"""
|
||||
Test authentication and authorization system.
|
||||
|
||||
Add to your pipeline in proxy-server.conf, such as::
|
||||
|
||||
[pipeline:main]
|
||||
pipeline = catch_errors cache tempauth proxy-server
|
||||
|
||||
And add a tempauth filter section, such as::
|
||||
|
||||
[filter:tempauth]
|
||||
use = egg:swift#tempauth
|
||||
user_admin_admin = admin .admin .reseller_admin
|
||||
user_test_tester = testing .admin
|
||||
user_test2_tester2 = testing2 .admin
|
||||
user_test_tester3 = testing3
|
||||
|
||||
See the proxy-server.conf-sample for more information.
|
||||
|
||||
:param app: The next WSGI app in the pipeline
|
||||
:param conf: The dict of configuration values
|
||||
"""
|
||||
|
||||
def __init__(self, app, conf):
|
||||
self.app = app
|
||||
self.conf = conf
|
||||
self.logger = get_logger(conf, log_route='tempauth')
|
||||
self.log_headers = conf.get('log_headers') == 'True'
|
||||
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
|
||||
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
|
||||
self.reseller_prefix += '_'
|
||||
self.auth_prefix = conf.get('auth_prefix', '/auth/')
|
||||
if not 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.users = {}
|
||||
for conf_key in conf:
|
||||
if conf_key.startswith('user_'):
|
||||
values = conf[conf_key].split()
|
||||
if not values:
|
||||
raise ValueError('%s has no key set' % conf_key)
|
||||
key = values.pop(0)
|
||||
if values and '://' in values[-1]:
|
||||
url = values.pop()
|
||||
else:
|
||||
url = 'https://' if 'cert_file' in conf else 'http://'
|
||||
ip = conf.get('bind_ip', '127.0.0.1')
|
||||
if ip == '0.0.0.0':
|
||||
ip = '127.0.0.1'
|
||||
url += ip
|
||||
url += ':' + conf.get('bind_port', 80) + '/v1/' + \
|
||||
self.reseller_prefix + conf_key.split('_')[1]
|
||||
groups = values
|
||||
self.users[conf_key.split('_', 1)[1].replace('_', ':')] = {
|
||||
'key': key, 'url': url, 'groups': values}
|
||||
self.created_accounts = False
|
||||
|
||||
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. For an authenticated request, REMOTE_USER will be set to a
|
||||
comma separated list of the user's groups.
|
||||
|
||||
With a non-empty reseller prefix, acts as the definitive auth service
|
||||
for just tokens and accounts that begin with that prefix, but will deny
|
||||
requests outside this prefix if no other auth middleware overrides it.
|
||||
|
||||
With an empty reseller prefix, acts as the definitive auth service only
|
||||
for tokens that validate to a non-empty set of groups. For all other
|
||||
requests, acts as the fallback auth service when no other auth
|
||||
middleware overrides it.
|
||||
|
||||
Alternatively, if the request matches the self.auth_prefix, the request
|
||||
will be routed through the internal auth request handler (self.handle).
|
||||
This is to handle granting tokens, etc.
|
||||
"""
|
||||
# Ensure the accounts we handle have been created
|
||||
if not self.created_accounts and self.users:
|
||||
newenv = {'REQUEST_METHOD': 'HEAD', 'HTTP_USER_AGENT': 'TempAuth'}
|
||||
for name in ('swift.cache', 'HTTP_X_TRANS_ID'):
|
||||
if name in env:
|
||||
newenv[name] = env[name]
|
||||
for key, value in self.users.iteritems():
|
||||
account_id = value['url'].rsplit('/', 1)[-1]
|
||||
newenv['REQUEST_METHOD'] = 'HEAD'
|
||||
resp = Request.blank('/v1/' + account_id,
|
||||
environ=newenv).get_response(self.app)
|
||||
if resp.status_int // 100 != 2:
|
||||
newenv['REQUEST_METHOD'] = 'PUT'
|
||||
resp = Request.blank('/v1/' + account_id,
|
||||
environ=newenv).get_response(self.app)
|
||||
if resp.status_int // 100 != 2:
|
||||
raise Exception('Could not create account %s for user '
|
||||
'%s' % (account_id, key))
|
||||
self.created_accounts = True
|
||||
|
||||
if env.get('PATH_INFO', '').startswith(self.auth_prefix):
|
||||
return self.handle(env, start_response)
|
||||
s3 = env.get('HTTP_AUTHORIZATION')
|
||||
token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
|
||||
if s3 or (token and token.startswith(self.reseller_prefix)):
|
||||
# Note: Empty reseller_prefix will match all tokens.
|
||||
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, 's3' if s3 else token)
|
||||
env['swift.authorize'] = self.authorize
|
||||
env['swift.clean_acl'] = clean_acl
|
||||
else:
|
||||
# Unauthorized token
|
||||
if self.reseller_prefix:
|
||||
# Because I know I'm the definitive auth for this token, I
|
||||
# can deny it outright.
|
||||
return HTTPUnauthorized()(env, start_response)
|
||||
# Because I'm not certain if I'm the definitive auth for empty
|
||||
# reseller_prefixed tokens, I won't overwrite swift.authorize.
|
||||
elif 'swift.authorize' not in env:
|
||||
env['swift.authorize'] = self.denied_response
|
||||
else:
|
||||
if self.reseller_prefix:
|
||||
# With a non-empty reseller_prefix, I would like to be called
|
||||
# back for anonymous access to accounts I know I'm the
|
||||
# definitive auth for.
|
||||
try:
|
||||
version, rest = split_path(env.get('PATH_INFO', ''),
|
||||
1, 2, True)
|
||||
except ValueError:
|
||||
return HTTPNotFound()(env, start_response)
|
||||
if rest and rest.startswith(self.reseller_prefix):
|
||||
# Handle anonymous access to accounts I'm the definitive
|
||||
# auth for.
|
||||
env['swift.authorize'] = self.authorize
|
||||
env['swift.clean_acl'] = clean_acl
|
||||
# Not my token, not my account, I can't authorize this request,
|
||||
# deny all is a good idea if not already set...
|
||||
elif 'swift.authorize' not in env:
|
||||
env['swift.authorize'] = self.denied_response
|
||||
# Because I'm not certain if I'm the definitive auth for empty
|
||||
# reseller_prefixed accounts, I won't overwrite swift.authorize.
|
||||
elif 'swift.authorize' not in env:
|
||||
env['swift.authorize'] = self.authorize
|
||||
env['swift.clean_acl'] = clean_acl
|
||||
return self.app(env, start_response)
|
||||
|
||||
def get_groups(self, env, token):
|
||||
"""
|
||||
Get groups for the given token.
|
||||
|
||||
:param env: The current WSGI environment dictionary.
|
||||
:param token: Token to validate and return a group string for.
|
||||
|
||||
:returns: None if the token is invalid or a string containing a comma
|
||||
separated list of groups the authenticated user is a member
|
||||
of. The first group in the list is also considered a unique
|
||||
identifier for that user.
|
||||
"""
|
||||
groups = None
|
||||
memcache_client = cache_from_env(env)
|
||||
if not memcache_client:
|
||||
raise Exception('Memcache required')
|
||||
memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
|
||||
cached_auth_data = memcache_client.get(memcache_token_key)
|
||||
if cached_auth_data:
|
||||
expires, groups = cached_auth_data
|
||||
if expires < time():
|
||||
groups = None
|
||||
|
||||
if env.get('HTTP_AUTHORIZATION'):
|
||||
account_user, sign = \
|
||||
env['HTTP_AUTHORIZATION'].split(' ')[1].rsplit(':', 1)
|
||||
if account_user not in self.users:
|
||||
return None
|
||||
account, user = account_user.split(':', 1)
|
||||
account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
|
||||
path = env['PATH_INFO']
|
||||
env['PATH_INFO'] = path.replace(account_user, account_id, 1)
|
||||
msg = base64.urlsafe_b64decode(unquote(token))
|
||||
key = self.users[account_user]['key']
|
||||
s = base64.encodestring(hmac.new(key, msg, sha1).digest()).strip()
|
||||
if s != sign:
|
||||
return None
|
||||
groups = [account, account_user]
|
||||
groups.extend(self.users[account_user]['groups'])
|
||||
if '.admin' in groups:
|
||||
groups.remove('.admin')
|
||||
groups.append(account_id)
|
||||
groups = ','.join(groups)
|
||||
|
||||
return groups
|
||||
|
||||
def authorize(self, req):
|
||||
"""
|
||||
Returns None if the request is authorized to continue or a standard
|
||||
WSGI response callable if not.
|
||||
"""
|
||||
try:
|
||||
version, account, container, obj = split_path(req.path, 1, 4, True)
|
||||
except ValueError:
|
||||
return HTTPNotFound(request=req)
|
||||
if not account or not account.startswith(self.reseller_prefix):
|
||||
return self.denied_response(req)
|
||||
user_groups = (req.remote_user or '').split(',')
|
||||
if '.reseller_admin' in user_groups and \
|
||||
account != self.reseller_prefix and \
|
||||
account[len(self.reseller_prefix)] != '.':
|
||||
return None
|
||||
if account in user_groups and \
|
||||
(req.method not in ('DELETE', 'PUT') or container):
|
||||
# If the user is admin for the account and is not trying to do an
|
||||
# account DELETE or PUT...
|
||||
return None
|
||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||
if referrer_allowed(req.referer, referrers):
|
||||
if obj or '.rlistings' in groups:
|
||||
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:
|
||||
return None
|
||||
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:
|
||||
return HTTPUnauthorized(request=req)
|
||||
|
||||
def handle(self, env, start_response):
|
||||
"""
|
||||
WSGI entry point for auth requests (ones that match the
|
||||
self.auth_prefix).
|
||||
Wraps env in webob.Request object and passes it down.
|
||||
|
||||
:param env: WSGI environment dictionary
|
||||
:param start_response: WSGI callable
|
||||
"""
|
||||
try:
|
||||
req = Request(env)
|
||||
if self.auth_prefix:
|
||||
req.path_info_pop()
|
||||
req.bytes_transferred = '-'
|
||||
req.client_disconnect = False
|
||||
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
|
||||
except (Exception, TimeoutError):
|
||||
print "EXCEPTION IN handle: %s: %s" % (format_exc(), env)
|
||||
start_response('500 Server Error',
|
||||
[('Content-Type', 'text/plain')])
|
||||
return ['Internal server error.\n']
|
||||
|
||||
def handle_request(self, req):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
req.start_time = time()
|
||||
handler = None
|
||||
try:
|
||||
version, account, user, _junk = split_path(req.path_info,
|
||||
minsegs=1, maxsegs=4, rest_with_last=True)
|
||||
except ValueError:
|
||||
return HTTPNotFound(request=req)
|
||||
if version in ('v1', 'v1.0', 'auth'):
|
||||
if req.method == 'GET':
|
||||
handler = self.handle_get_token
|
||||
if not handler:
|
||||
req.response = HTTPBadRequest(request=req)
|
||||
else:
|
||||
req.response = handler(req)
|
||||
return req.response
|
||||
|
||||
def handle_get_token(self, req):
|
||||
"""
|
||||
Handles the various `request for token and service end point(s)` calls.
|
||||
There are various formats to support the various auth servers in the
|
||||
past. Examples::
|
||||
|
||||
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>
|
||||
|
||||
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 webob.Request to process.
|
||||
:returns: webob.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)
|
||||
except ValueError:
|
||||
return HTTPNotFound(request=req)
|
||||
if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
|
||||
account = pathsegs[1]
|
||||
user = req.headers.get('x-storage-user')
|
||||
if not user:
|
||||
user = req.headers.get('x-auth-user')
|
||||
if not user or ':' not in user:
|
||||
return HTTPUnauthorized(request=req)
|
||||
account2, user = user.split(':', 1)
|
||||
if account != account2:
|
||||
return HTTPUnauthorized(request=req)
|
||||
key = req.headers.get('x-storage-pass')
|
||||
if not key:
|
||||
key = req.headers.get('x-auth-key')
|
||||
elif pathsegs[0] in ('auth', 'v1.0'):
|
||||
user = req.headers.get('x-auth-user')
|
||||
if not user:
|
||||
user = req.headers.get('x-storage-user')
|
||||
if not user or ':' not in user:
|
||||
return HTTPUnauthorized(request=req)
|
||||
account, user = user.split(':', 1)
|
||||
key = req.headers.get('x-auth-key')
|
||||
if not key:
|
||||
key = req.headers.get('x-storage-pass')
|
||||
else:
|
||||
return HTTPBadRequest(request=req)
|
||||
if not all((account, user, key)):
|
||||
return HTTPUnauthorized(request=req)
|
||||
# Authenticate user
|
||||
account_user = account + ':' + user
|
||||
if account_user not in self.users:
|
||||
return HTTPUnauthorized(request=req)
|
||||
if self.users[account_user]['key'] != key:
|
||||
return HTTPUnauthorized(request=req)
|
||||
# Get memcache client
|
||||
memcache_client = cache_from_env(req.environ)
|
||||
if not memcache_client:
|
||||
raise Exception('Memcache required')
|
||||
# See if a token already exists and hasn't expired
|
||||
token = None
|
||||
memcache_user_key = '%s/user/%s' % (self.reseller_prefix, account_user)
|
||||
candidate_token = memcache_client.get(memcache_user_key)
|
||||
if candidate_token:
|
||||
memcache_token_key = \
|
||||
'%s/token/%s' % (self.reseller_prefix, candidate_token)
|
||||
cached_auth_data = memcache_client.get(memcache_token_key)
|
||||
if cached_auth_data:
|
||||
expires, groups = cached_auth_data
|
||||
if expires > time():
|
||||
token = candidate_token
|
||||
# Create a new token if one didn't exist
|
||||
if not token:
|
||||
# Generate new token
|
||||
token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
|
||||
expires = time() + self.token_life
|
||||
groups = [account, account_user]
|
||||
groups.extend(self.users[account_user]['groups'])
|
||||
if '.admin' in groups:
|
||||
groups.remove('.admin')
|
||||
account_id = self.users[account_user]['url'].rsplit('/', 1)[-1]
|
||||
groups.append(account_id)
|
||||
groups = ','.join(groups)
|
||||
# Save token
|
||||
memcache_token_key = '%s/token/%s' % (self.reseller_prefix, token)
|
||||
memcache_client.set(memcache_token_key, (expires, groups),
|
||||
timeout=float(expires - time()))
|
||||
# Record the token with the user info for future use.
|
||||
memcache_user_key = \
|
||||
'%s/user/%s' % (self.reseller_prefix, account_user)
|
||||
memcache_client.set(memcache_user_key, token,
|
||||
timeout=float(expires - time()))
|
||||
return Response(request=req,
|
||||
headers={'x-auth-token': token, 'x-storage-token': token,
|
||||
'x-storage-url': self.users[account_user]['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.headers.get('x-trans-id', '-'), logged_headers or '-',
|
||||
trans_time)))
|
||||
|
||||
|
||||
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):
|
||||
return TempAuth(app, conf)
|
||||
return auth_filter
|
@ -13,29 +13,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from os import environ, kill
|
||||
from os import kill
|
||||
from signal import SIGTERM
|
||||
from subprocess import call, Popen
|
||||
from time import sleep
|
||||
from ConfigParser import ConfigParser
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
from swift.common.client import get_auth
|
||||
from swift.common.ring import Ring
|
||||
|
||||
|
||||
SUPER_ADMIN_KEY = None
|
||||
|
||||
c = ConfigParser()
|
||||
PROXY_SERVER_CONF_FILE = environ.get('SWIFT_PROXY_SERVER_CONF_FILE',
|
||||
'/etc/swift/proxy-server.conf')
|
||||
if c.read(PROXY_SERVER_CONF_FILE):
|
||||
conf = dict(c.items('filter:swauth'))
|
||||
SUPER_ADMIN_KEY = conf.get('super_admin_key', 'swauthkey')
|
||||
else:
|
||||
exit('Unable to read config file: %s' % PROXY_SERVER_CONF_FILE)
|
||||
|
||||
|
||||
def kill_pids(pids):
|
||||
for pid in pids.values():
|
||||
try:
|
||||
@ -48,8 +35,6 @@ def reset_environment():
|
||||
call(['resetswift'])
|
||||
pids = {}
|
||||
try:
|
||||
pids['proxy'] = Popen(['swift-proxy-server',
|
||||
'/etc/swift/proxy-server.conf']).pid
|
||||
port2server = {}
|
||||
for s, p in (('account', 6002), ('container', 6001), ('object', 6000)):
|
||||
for n in xrange(1, 5):
|
||||
@ -57,14 +42,27 @@ def reset_environment():
|
||||
Popen(['swift-%s-server' % s,
|
||||
'/etc/swift/%s-server/%d.conf' % (s, n)]).pid
|
||||
port2server[p + (n * 10)] = '%s%d' % (s, n)
|
||||
pids['proxy'] = Popen(['swift-proxy-server',
|
||||
'/etc/swift/proxy-server.conf']).pid
|
||||
account_ring = Ring('/etc/swift/account.ring.gz')
|
||||
container_ring = Ring('/etc/swift/container.ring.gz')
|
||||
object_ring = Ring('/etc/swift/object.ring.gz')
|
||||
sleep(5)
|
||||
call(['recreateaccounts'])
|
||||
url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
|
||||
'test:tester', 'testing')
|
||||
account = url.split('/')[-1]
|
||||
attempt = 0
|
||||
while True:
|
||||
attempt += 1
|
||||
try:
|
||||
url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
|
||||
'test:tester', 'testing')
|
||||
account = url.split('/')[-1]
|
||||
break
|
||||
except Exception, err:
|
||||
if attempt > 9:
|
||||
print err
|
||||
print 'Giving up after %s retries.' % attempt
|
||||
raise err
|
||||
print err
|
||||
print 'Retrying in 1 second...'
|
||||
sleep(1)
|
||||
except BaseException, err:
|
||||
kill_pids(pids)
|
||||
raise err
|
||||
|
File diff suppressed because it is too large
Load Diff
388
test/unit/common/middleware/test_tempauth.py
Normal file
388
test/unit/common/middleware/test_tempauth.py
Normal file
@ -0,0 +1,388 @@
|
||||
# Copyright (c) 2011 OpenStack, LLC.
|
||||
#
|
||||
# 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.
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import unittest
|
||||
from contextlib import contextmanager
|
||||
from time import time
|
||||
|
||||
from webob import Request, Response
|
||||
|
||||
from swift.common.middleware import tempauth as auth
|
||||
|
||||
|
||||
class FakeMemcache(object):
|
||||
|
||||
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, timeout=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):
|
||||
self.calls = 0
|
||||
self.status_headers_body_iter = status_headers_body_iter
|
||||
if not self.status_headers_body_iter:
|
||||
self.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
self.calls += 1
|
||||
self.request = Request.blank('', environ=env)
|
||||
if 'swift.authorize' in env:
|
||||
resp = env['swift.authorize'](self.request)
|
||||
if resp:
|
||||
return resp(env, start_response)
|
||||
status, headers, body = self.status_headers_body_iter.next()
|
||||
return Response(status=status, headers=headers,
|
||||
body=body)(env, start_response)
|
||||
|
||||
|
||||
class FakeConn(object):
|
||||
|
||||
def __init__(self, status_headers_body_iter=None):
|
||||
self.calls = 0
|
||||
self.status_headers_body_iter = status_headers_body_iter
|
||||
if not self.status_headers_body_iter:
|
||||
self.status_headers_body_iter = iter([('404 Not Found', {}, '')])
|
||||
|
||||
def request(self, method, path, headers):
|
||||
self.calls += 1
|
||||
self.request_path = path
|
||||
self.status, self.headers, self.body = \
|
||||
self.status_headers_body_iter.next()
|
||||
self.status, self.reason = self.status.split(' ', 1)
|
||||
self.status = int(self.status)
|
||||
|
||||
def getresponse(self):
|
||||
return self
|
||||
|
||||
def read(self):
|
||||
body = self.body
|
||||
self.body = ''
|
||||
return body
|
||||
|
||||
|
||||
class TestAuth(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.test_auth = auth.filter_factory({})(FakeApp())
|
||||
|
||||
def _make_request(self, path, **kwargs):
|
||||
req = Request.blank(path, **kwargs)
|
||||
req.environ['swift.cache'] = FakeMemcache()
|
||||
return req
|
||||
|
||||
def test_reseller_prefix_init(self):
|
||||
app = FakeApp()
|
||||
ath = auth.filter_factory({})(app)
|
||||
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):
|
||||
app = FakeApp()
|
||||
ath = auth.filter_factory({})(app)
|
||||
self.assertEquals(ath.auth_prefix, '/auth/')
|
||||
ath = auth.filter_factory({'auth_prefix': ''})(app)
|
||||
self.assertEquals(ath.auth_prefix, '/auth/')
|
||||
ath = auth.filter_factory({'auth_prefix': '/test/'})(app)
|
||||
self.assertEquals(ath.auth_prefix, '/test/')
|
||||
ath = auth.filter_factory({'auth_prefix': '/test'})(app)
|
||||
self.assertEquals(ath.auth_prefix, '/test/')
|
||||
ath = auth.filter_factory({'auth_prefix': 'test/'})(app)
|
||||
self.assertEquals(ath.auth_prefix, '/test/')
|
||||
ath = auth.filter_factory({'auth_prefix': 'test'})(app)
|
||||
self.assertEquals(ath.auth_prefix, '/test/')
|
||||
|
||||
def test_top_level_ignore(self):
|
||||
resp = self._make_request('/').get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 404)
|
||||
|
||||
def test_anon(self):
|
||||
resp = self._make_request('/v1/AUTH_account').get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertEquals(resp.environ['swift.authorize'],
|
||||
self.test_auth.authorize)
|
||||
|
||||
def test_auth_deny_non_reseller_prefix(self):
|
||||
resp = self._make_request('/v1/BLAH_account',
|
||||
headers={'X-Auth-Token': 'BLAH_t'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertEquals(resp.environ['swift.authorize'],
|
||||
self.test_auth.denied_response)
|
||||
|
||||
def test_auth_deny_non_reseller_prefix_no_override(self):
|
||||
fake_authorize = lambda x: Response(status='500 Fake')
|
||||
resp = self._make_request('/v1/BLAH_account',
|
||||
headers={'X-Auth-Token': 'BLAH_t'},
|
||||
environ={'swift.authorize': fake_authorize}
|
||||
).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 500)
|
||||
self.assertEquals(resp.environ['swift.authorize'], fake_authorize)
|
||||
|
||||
def test_auth_no_reseller_prefix_deny(self):
|
||||
# Ensures that when we have no reseller prefix, we don't deny a request
|
||||
# outright but set up a denial swift.authorize and pass the request on
|
||||
# down the chain.
|
||||
local_app = FakeApp()
|
||||
local_auth = auth.filter_factory({'reseller_prefix': ''})(local_app)
|
||||
resp = self._make_request('/v1/account',
|
||||
headers={'X-Auth-Token': 't'}).get_response(local_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertEquals(local_app.calls, 1)
|
||||
self.assertEquals(resp.environ['swift.authorize'],
|
||||
local_auth.denied_response)
|
||||
|
||||
def test_auth_no_reseller_prefix_no_token(self):
|
||||
# Check that normally we set up a call back to our authorize.
|
||||
local_auth = \
|
||||
auth.filter_factory({'reseller_prefix': ''})(FakeApp(iter([])))
|
||||
resp = self._make_request('/v1/account').get_response(local_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
self.assertEquals(resp.environ['swift.authorize'],
|
||||
local_auth.authorize)
|
||||
# Now make sure we don't override an existing swift.authorize when we
|
||||
# have no reseller prefix.
|
||||
local_auth = \
|
||||
auth.filter_factory({'reseller_prefix': ''})(FakeApp())
|
||||
local_authorize = lambda req: Response('test')
|
||||
resp = self._make_request('/v1/account', environ={'swift.authorize':
|
||||
local_authorize}).get_response(local_auth)
|
||||
self.assertEquals(resp.status_int, 200)
|
||||
self.assertEquals(resp.environ['swift.authorize'], local_authorize)
|
||||
|
||||
def test_auth_fail(self):
|
||||
resp = self._make_request('/v1/AUTH_cfa',
|
||||
headers={'X-Auth-Token': 'AUTH_t'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_authorize_bad_path(self):
|
||||
req = self._make_request('/badpath')
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
req = self._make_request('/badpath')
|
||||
req.remote_user = 'act:usr,act,AUTH_cfa'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
def test_authorize_account_access(self):
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act,AUTH_cfa'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
def test_authorize_acl_group_access(self):
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = 'act'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = 'act:usr'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = 'act2'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
req = self._make_request('/v1/AUTH_cfa')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = 'act:usr2'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
def test_deny_cross_reseller(self):
|
||||
# Tests that cross-reseller is denied, even if ACLs/group names match
|
||||
req = self._make_request('/v1/OTHER_cfa')
|
||||
req.remote_user = 'act:usr,act,AUTH_cfa'
|
||||
req.acl = 'act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
def test_authorize_acl_referrer_access(self):
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.remote_user = 'act:usr,act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = '.r:*,.rlistings'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = '.r:*' # No listings allowed
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.acl = '.r:.example.com,.rlistings'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.remote_user = 'act:usr,act'
|
||||
req.referer = 'http://www.example.com/index.html'
|
||||
req.acl = '.r:.example.com,.rlistings'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.acl = '.r:*,.rlistings'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.acl = '.r:*' # No listings allowed
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.acl = '.r:.example.com,.rlistings'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
req = self._make_request('/v1/AUTH_cfa/c')
|
||||
req.referer = 'http://www.example.com/index.html'
|
||||
req.acl = '.r:.example.com,.rlistings'
|
||||
self.assertEquals(self.test_auth.authorize(req), None)
|
||||
|
||||
def test_account_put_permissions(self):
|
||||
req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
|
||||
req.remote_user = 'act:usr,act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
|
||||
req.remote_user = 'act:usr,act,AUTH_other'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
# Even PUTs to your own account as account admin should fail
|
||||
req = self._make_request('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'})
|
||||
req.remote_user = 'act:usr,act,AUTH_old'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
|
||||
req.remote_user = 'act:usr,act,.reseller_admin'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp, None)
|
||||
|
||||
# .super_admin is not something the middleware should ever see or care
|
||||
# about
|
||||
req = self._make_request('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
|
||||
req.remote_user = 'act:usr,act,.super_admin'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
def test_account_delete_permissions(self):
|
||||
req = self._make_request('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
req = self._make_request('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,AUTH_other'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
# Even DELETEs to your own account as account admin should fail
|
||||
req = self._make_request('/v1/AUTH_old',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,AUTH_old'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
req = self._make_request('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,.reseller_admin'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp, None)
|
||||
|
||||
# .super_admin is not something the middleware should ever see or care
|
||||
# about
|
||||
req = self._make_request('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,.super_admin'
|
||||
resp = self.test_auth.authorize(req)
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp.status_int, 403)
|
||||
|
||||
def test_get_token_fail(self):
|
||||
resp = self._make_request('/auth/v1.0').get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
resp = self._make_request('/auth/v1.0',
|
||||
headers={'X-Auth-User': 'act:usr',
|
||||
'X-Auth-Key': 'key'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_get_token_fail_invalid_x_auth_user_format(self):
|
||||
resp = self._make_request('/auth/v1/act/auth',
|
||||
headers={'X-Auth-User': 'usr',
|
||||
'X-Auth-Key': 'key'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_get_token_fail_non_matching_account_in_request(self):
|
||||
resp = self._make_request('/auth/v1/act/auth',
|
||||
headers={'X-Auth-User': 'act2:usr',
|
||||
'X-Auth-Key': 'key'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
def test_get_token_fail_bad_path(self):
|
||||
resp = self._make_request('/auth/v1/act/auth/invalid',
|
||||
headers={'X-Auth-User': 'act:usr',
|
||||
'X-Auth-Key': 'key'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 400)
|
||||
|
||||
def test_get_token_fail_missing_key(self):
|
||||
resp = self._make_request('/auth/v1/act/auth',
|
||||
headers={'X-Auth-User': 'act:usr'}).get_response(self.test_auth)
|
||||
self.assertEquals(resp.status_int, 401)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user