Merged from trunk
This commit is contained in:
commit
56bafed131
@ -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
|
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
|
Debugging Tips and Tools
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -559,35 +559,17 @@ object_post_as_copy true Set object_post_as_copy = false
|
|||||||
sync posts.
|
sync posts.
|
||||||
============================ =============== =============================
|
============================ =============== =============================
|
||||||
|
|
||||||
[auth]
|
[tempauth]
|
||||||
|
|
||||||
============ =================================== ========================
|
|
||||||
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]
|
|
||||||
|
|
||||||
===================== =============================== =======================
|
===================== =============================== =======================
|
||||||
Option Default Description
|
Option Default Description
|
||||||
--------------------- ------------------------------- -----------------------
|
--------------------- ------------------------------- -----------------------
|
||||||
use Entry point for
|
use Entry point for
|
||||||
paste.deploy to use for
|
paste.deploy to use for
|
||||||
auth. To use the swauth
|
auth. To use tempauth
|
||||||
set to:
|
set to:
|
||||||
`egg:swift#swauth`
|
`egg:swift#tempauth`
|
||||||
set log_name auth-server Label used when logging
|
set log_name tempauth Label used when logging
|
||||||
set log_facility LOG_LOCAL0 Syslog log facility
|
set log_facility LOG_LOCAL0 Syslog log facility
|
||||||
set log_level INFO Log level
|
set log_level INFO Log level
|
||||||
set log_headers True If True, log headers in
|
set log_headers True If True, log headers in
|
||||||
@ -603,16 +585,39 @@ auth_prefix /auth/ The HTTP request path
|
|||||||
reserves anything
|
reserves anything
|
||||||
beginning with the
|
beginning with the
|
||||||
letter `v`.
|
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_life 86400 The number of seconds a
|
||||||
token is valid.
|
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
|
Memcached Considerations
|
||||||
|
@ -6,7 +6,7 @@ Auth Server and Middleware
|
|||||||
Creating Your Own 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
|
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
|
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
|
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
|
environ['REMOTE_USER'] set to the authenticated user string but often more
|
||||||
information is needed than just that.
|
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
|
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",
|
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
|
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
|
It is highly recommended that authentication server implementers prefix their
|
||||||
tokens and Swift storage accounts they create with a configurable reseller
|
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
|
conflicts with other authentication servers that might be using the same
|
||||||
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
|
Swift cluster. Otherwise, the Swift cluster will have to try all the resellers
|
||||||
until one validates a token or all fail.
|
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
|
'.' as that is reserved for internal Swift use (such as the .r for referrer
|
||||||
designations as you'll see later).
|
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.
|
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
|
it matches the "tester" user within the "test" account for the storage
|
||||||
account "AUTH_storage_xyz".
|
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"
|
"test:tester,test,AUTH_storage_xyz"
|
||||||
* Now this user will have full access (via authorization procedures later)
|
* Now this user will have full access (via authorization procedures later)
|
||||||
to the AUTH_storage_xyz Swift storage account and access to containers in
|
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
|
log_facility = LOG_LOCAL1
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
pipeline = healthcheck cache swauth proxy-server
|
pipeline = healthcheck cache tempauth proxy-server
|
||||||
|
|
||||||
[app:proxy-server]
|
[app:proxy-server]
|
||||||
use = egg:swift#proxy
|
use = egg:swift#proxy
|
||||||
allow_account_management = true
|
allow_account_management = true
|
||||||
|
|
||||||
[filter:swauth]
|
[filter:tempauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#tempauth
|
||||||
# Highly recommended to change this.
|
user_admin_admin = admin .admin .reseller_admin
|
||||||
super_admin_key = swauthkey
|
user_test_tester = testing .admin
|
||||||
|
user_test2_tester2 = testing2 .admin
|
||||||
|
user_test_tester3 = testing3
|
||||||
|
|
||||||
[filter:healthcheck]
|
[filter:healthcheck]
|
||||||
use = egg:swift#healthcheck
|
use = egg:swift#healthcheck
|
||||||
@ -558,8 +560,10 @@ Setting up scripts for running Swift
|
|||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
#. Create `~/bin/resetswift.`
|
#. 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
|
#!/bin/bash
|
||||||
|
|
||||||
@ -608,18 +612,6 @@ Setting up scripts for running Swift
|
|||||||
|
|
||||||
swift-init main start
|
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`::
|
#. Create `~/bin/startrest`::
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
@ -13,7 +13,7 @@ Prerequisites
|
|||||||
Basic architecture and terms
|
Basic architecture and terms
|
||||||
----------------------------
|
----------------------------
|
||||||
- *node* - a host machine running one or more Swift services
|
- *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
|
- *Storage node* - node that runs Account, Container, and Object services
|
||||||
- *ring* - a set of mappings of Swift data to physical devices
|
- *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
|
- Runs the swift-proxy-server processes which proxy requests to the
|
||||||
appropriate Storage nodes. The proxy server will also contain
|
appropriate Storage nodes. The proxy server will also contain
|
||||||
the Swauth service as WSGI middleware.
|
the TempAuth service as WSGI middleware.
|
||||||
|
|
||||||
- five Storage nodes
|
- five Storage nodes
|
||||||
|
|
||||||
@ -130,17 +130,15 @@ Configure the Proxy node
|
|||||||
user = swift
|
user = swift
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
pipeline = healthcheck cache swauth proxy-server
|
pipeline = healthcheck cache tempauth proxy-server
|
||||||
|
|
||||||
[app:proxy-server]
|
[app:proxy-server]
|
||||||
use = egg:swift#proxy
|
use = egg:swift#proxy
|
||||||
allow_account_management = true
|
allow_account_management = true
|
||||||
|
|
||||||
[filter:swauth]
|
[filter:tempauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#tempauth
|
||||||
default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
|
user_system_root = testpass .admin https://$PROXY_LOCAL_NET_IP:8080/v1/AUTH_system
|
||||||
# Highly recommended to change this key to something else!
|
|
||||||
super_admin_key = swauthkey
|
|
||||||
|
|
||||||
[filter:healthcheck]
|
[filter:healthcheck]
|
||||||
use = egg:swift#healthcheck
|
use = egg:swift#healthcheck
|
||||||
@ -366,16 +364,6 @@ Create Swift admin account and test
|
|||||||
|
|
||||||
You run these commands from the Proxy node.
|
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::
|
#. 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
|
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
|
use = egg:swift#memcache
|
||||||
memcache_servers = $PROXY_LOCAL_NET_IP:11211
|
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]
|
[filter:tempauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#tempauth
|
||||||
default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
|
user_system_root = testpass .admin http[s]://<LOAD_BALANCER_HOSTNAME>:<PORT>/v1/AUTH_system
|
||||||
# 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.
|
|
||||||
|
|
||||||
#. 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.
|
#. 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.
|
#. 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
|
Troubleshooting Notes
|
||||||
---------------------
|
---------------------
|
||||||
If you see problems, look in var/log/syslog (or messages on some distros).
|
If you see problems, look in var/log/syslog (or messages on some distros).
|
||||||
|
@ -33,12 +33,12 @@ Utils
|
|||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
.. _common_swauth:
|
.. _common_tempauth:
|
||||||
|
|
||||||
Swauth
|
TempAuth
|
||||||
======
|
========
|
||||||
|
|
||||||
.. automodule:: swift.common.middleware.swauth
|
.. automodule:: swift.common.middleware.tempauth
|
||||||
:members:
|
:members:
|
||||||
:show-inheritance:
|
:show-inheritance:
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
The Auth System
|
The Auth System
|
||||||
===============
|
===============
|
||||||
|
|
||||||
------
|
--------
|
||||||
Swauth
|
TempAuth
|
||||||
------
|
--------
|
||||||
|
|
||||||
The auth system for Swift is loosely based on the auth system from the existing
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
Extending Auth
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
Swauth is written as wsgi middleware, so implementing your own auth is as easy
|
TempAuth is written as wsgi middleware, so implementing your own auth is as
|
||||||
as writing new wsgi middleware, and plugging it in to the proxy server.
|
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`.
|
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
|
# log_level = INFO
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
|
pipeline = catch_errors healthcheck cache ratelimit tempauth proxy-server
|
||||||
|
|
||||||
[app:proxy-server]
|
[app:proxy-server]
|
||||||
use = egg:swift#proxy
|
use = egg:swift#proxy
|
||||||
@ -46,10 +46,10 @@ use = egg:swift#proxy
|
|||||||
# this mode, features like container sync won't be able to sync posts.
|
# this mode, features like container sync won't be able to sync posts.
|
||||||
# object_post_as_copy = true
|
# object_post_as_copy = true
|
||||||
|
|
||||||
[filter:swauth]
|
[filter:tempauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#tempauth
|
||||||
# You can override the default log routing for this filter here:
|
# 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_facility = LOG_LOCAL0
|
||||||
# set log_level = INFO
|
# set log_level = INFO
|
||||||
# set log_headers = False
|
# set log_headers = False
|
||||||
@ -59,21 +59,28 @@ use = egg:swift#swauth
|
|||||||
# multiple auth systems are in use for one Swift cluster.
|
# multiple auth systems are in use for one Swift cluster.
|
||||||
# reseller_prefix = AUTH
|
# reseller_prefix = AUTH
|
||||||
# The auth prefix will cause requests beginning with this prefix to be routed
|
# 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/
|
# 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
|
# token_life = 86400
|
||||||
# node_timeout = 10
|
# Lastly, you need to list all the accounts/users you want here. The format is:
|
||||||
# Highly recommended to change this.
|
# user_<account>_<user> = <key> [group] [group] [...] [storage_url]
|
||||||
super_admin_key = swauthkey
|
# 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]
|
[filter:healthcheck]
|
||||||
use = egg:swift#healthcheck
|
use = egg:swift#healthcheck
|
||||||
|
6
setup.py
6
setup.py
@ -96,10 +96,6 @@ setup(
|
|||||||
'bin/swift-log-stats-collector',
|
'bin/swift-log-stats-collector',
|
||||||
'bin/swift-account-stats-logger',
|
'bin/swift-account-stats-logger',
|
||||||
'bin/swift-container-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={
|
entry_points={
|
||||||
'paste.app_factory': [
|
'paste.app_factory': [
|
||||||
@ -109,7 +105,6 @@ setup(
|
|||||||
'account=swift.account.server:app_factory',
|
'account=swift.account.server:app_factory',
|
||||||
],
|
],
|
||||||
'paste.filter_factory': [
|
'paste.filter_factory': [
|
||||||
'swauth=swift.common.middleware.swauth:filter_factory',
|
|
||||||
'healthcheck=swift.common.middleware.healthcheck:filter_factory',
|
'healthcheck=swift.common.middleware.healthcheck:filter_factory',
|
||||||
'memcache=swift.common.middleware.memcache:filter_factory',
|
'memcache=swift.common.middleware.memcache:filter_factory',
|
||||||
'ratelimit=swift.common.middleware.ratelimit:filter_factory',
|
'ratelimit=swift.common.middleware.ratelimit:filter_factory',
|
||||||
@ -118,6 +113,7 @@ setup(
|
|||||||
'domain_remap=swift.common.middleware.domain_remap:filter_factory',
|
'domain_remap=swift.common.middleware.domain_remap:filter_factory',
|
||||||
'swift3=swift.common.middleware.swift3:filter_factory',
|
'swift3=swift.common.middleware.swift3:filter_factory',
|
||||||
'staticweb=swift.common.middleware.staticweb: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: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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from os import environ, kill
|
from os import kill
|
||||||
from signal import SIGTERM
|
from signal import SIGTERM
|
||||||
from subprocess import call, Popen
|
from subprocess import call, Popen
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from ConfigParser import ConfigParser
|
|
||||||
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||||
from swift.common.client import get_auth
|
from swift.common.client import get_auth
|
||||||
from swift.common.ring import Ring
|
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):
|
def kill_pids(pids):
|
||||||
for pid in pids.values():
|
for pid in pids.values():
|
||||||
try:
|
try:
|
||||||
@ -48,8 +35,6 @@ def reset_environment():
|
|||||||
call(['resetswift'])
|
call(['resetswift'])
|
||||||
pids = {}
|
pids = {}
|
||||||
try:
|
try:
|
||||||
pids['proxy'] = Popen(['swift-proxy-server',
|
|
||||||
'/etc/swift/proxy-server.conf']).pid
|
|
||||||
port2server = {}
|
port2server = {}
|
||||||
for s, p in (('account', 6002), ('container', 6001), ('object', 6000)):
|
for s, p in (('account', 6002), ('container', 6001), ('object', 6000)):
|
||||||
for n in xrange(1, 5):
|
for n in xrange(1, 5):
|
||||||
@ -57,14 +42,27 @@ def reset_environment():
|
|||||||
Popen(['swift-%s-server' % s,
|
Popen(['swift-%s-server' % s,
|
||||||
'/etc/swift/%s-server/%d.conf' % (s, n)]).pid
|
'/etc/swift/%s-server/%d.conf' % (s, n)]).pid
|
||||||
port2server[p + (n * 10)] = '%s%d' % (s, n)
|
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')
|
account_ring = Ring('/etc/swift/account.ring.gz')
|
||||||
container_ring = Ring('/etc/swift/container.ring.gz')
|
container_ring = Ring('/etc/swift/container.ring.gz')
|
||||||
object_ring = Ring('/etc/swift/object.ring.gz')
|
object_ring = Ring('/etc/swift/object.ring.gz')
|
||||||
sleep(5)
|
attempt = 0
|
||||||
call(['recreateaccounts'])
|
while True:
|
||||||
url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
|
attempt += 1
|
||||||
'test:tester', 'testing')
|
try:
|
||||||
account = url.split('/')[-1]
|
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:
|
except BaseException, err:
|
||||||
kill_pids(pids)
|
kill_pids(pids)
|
||||||
raise err
|
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…
Reference in New Issue
Block a user