merging to trunk, otra
This commit is contained in:
commit
f18e20ab38
67
bin/swauth-add-account
Executable file
67
bin/swauth-add-account
Executable file
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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:
|
||||
print 'Account creation failed: %s %s' % (resp.status, resp.reason)
|
92
bin/swauth-add-user
Executable file
92
bin/swauth-add-user
Executable file
@ -0,0 +1,92 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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:
|
||||
print 'User creation failed: %s %s' % (resp.status, resp.reason)
|
104
bin/swauth-cleanup-tokens
Executable file
104
bin/swauth-cleanup-tokens
Executable file
@ -0,0 +1,104 @@
|
||||
#!/usr/bin/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
|
||||
|
||||
|
||||
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)
|
||||
objs = conn.get_container(container, marker=marker)[1]
|
||||
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'])
|
||||
conn.delete_object(container, obj['name'])
|
||||
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.'
|
59
bin/swauth-delete-account
Executable file
59
bin/swauth-delete-account
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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:
|
||||
print 'Account deletion failed: %s %s' % (resp.status, resp.reason)
|
59
bin/swauth-delete-user
Executable file
59
bin/swauth-delete-user
Executable file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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:
|
||||
print 'User deletion failed: %s %s' % (resp.status, resp.reason)
|
85
bin/swauth-list
Executable file
85
bin/swauth-list
Executable file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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()
|
||||
if resp.status // 100 != 2:
|
||||
print 'List failed: %s %s' % (resp.status, resp.reason)
|
||||
body = resp.read()
|
||||
if options.plain_text:
|
||||
info = json.loads(body)
|
||||
for group in info[['accounts', 'users', 'groups'][len(args)]]:
|
||||
print group['name']
|
||||
else:
|
||||
print body
|
58
bin/swauth-prep
Executable file
58
bin/swauth-prep
Executable file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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:
|
||||
print 'Auth subsystem prep failed: %s %s' % (resp.status, resp.reason)
|
72
bin/swauth-set-account-service
Executable file
72
bin/swauth-set-account-service
Executable file
@ -0,0 +1,72 @@
|
||||
#!/usr/bin/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 urlparse import urlparse
|
||||
|
||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
||||
|
||||
|
||||
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)))
|
||||
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:
|
||||
print 'Service set failed: %s %s' % (resp.status, resp.reason)
|
46
bin/swift-auth-to-swauth
Executable file
46
bin/swift-auth-to-swauth
Executable file
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/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 subprocess import call
|
||||
from sys import argv, exit
|
||||
|
||||
import sqlite3
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
gettext.install('swift', unicode=1)
|
||||
if len(argv) != 4 or argv[1] != '-K':
|
||||
exit('Syntax: %s -K <super_admin_key> <path to auth.db>' % argv[0])
|
||||
_, _, super_admin_key, auth_db = argv
|
||||
call(['swauth-prep', '-K', super_admin_key])
|
||||
conn = sqlite3.connect(auth_db)
|
||||
for account, cfaccount, user, password, admin, reseller_admin in \
|
||||
conn.execute('SELECT account, cfaccount, user, password, admin, '
|
||||
'reseller_admin FROM account'):
|
||||
cmd = ['swauth-add-user', '-K', super_admin_key, '-s',
|
||||
cfaccount.split('_', 1)[1]]
|
||||
if admin == 't':
|
||||
cmd.append('-a')
|
||||
if reseller_admin == 't':
|
||||
cmd.append('-r')
|
||||
cmd.extend([account, user, password])
|
||||
print ' '.join(cmd)
|
||||
call(cmd)
|
||||
print '----------------------------------------------------------------'
|
||||
print ' Assuming the above worked perfectly, you should copy and paste '
|
||||
print ' those lines into your ~/bin/recreateaccounts script.'
|
||||
print '----------------------------------------------------------------'
|
@ -164,7 +164,10 @@ swift-stats-populate and swift-stats-report use the same configuration file,
|
||||
/etc/swift/stats.conf. Example conf file::
|
||||
|
||||
[stats]
|
||||
# For DevAuth:
|
||||
auth_url = http://saio:11000/v1.0
|
||||
# For Swauth:
|
||||
# auth_url = http://saio:11000/auth/v1.0
|
||||
auth_user = test:tester
|
||||
auth_key = testing
|
||||
|
||||
@ -229,6 +232,21 @@ get performance timings (warning: the initial populate takes a while). These
|
||||
timings are dumped into a CSV file (/etc/swift/stats.csv by default) and can
|
||||
then be graphed to see how cluster performance is trending.
|
||||
|
||||
------------------------------------
|
||||
Additional Cleanup Script for Swauth
|
||||
------------------------------------
|
||||
|
||||
If you decide to use 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 -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
|
||||
------------------------
|
||||
|
@ -489,6 +489,43 @@ ssl False If True, use SSL to
|
||||
node_timeout 10 Request timeout
|
||||
============ =================================== ========================
|
||||
|
||||
[swauth]
|
||||
|
||||
===================== =============================== =======================
|
||||
Option Default Description
|
||||
--------------------- ------------------------------- -----------------------
|
||||
use Entry point for
|
||||
paste.deploy to use for
|
||||
auth. To use the swauth
|
||||
set to:
|
||||
`egg:swift#swauth`
|
||||
log_name auth-server Label used when logging
|
||||
log_facility LOG_LOCAL0 Syslog log facility
|
||||
log_level INFO Log level
|
||||
log_headers True If True, log headers in
|
||||
each request
|
||||
reseller_prefix AUTH The naming scope for the
|
||||
auth service. Swift
|
||||
storage accounts and
|
||||
auth tokens will begin
|
||||
with this prefix.
|
||||
auth_prefix /auth/ The HTTP request path
|
||||
prefix for the auth
|
||||
service. Swift itself
|
||||
reserves anything
|
||||
beginning with the
|
||||
letter `v`.
|
||||
default_swift_cluster local:http://127.0.0.1:8080/v1 The default Swift
|
||||
cluster to place newly
|
||||
created accounts on.
|
||||
token_life 86400 The number of seconds a
|
||||
token is valid.
|
||||
node_timeout 10 Request timeout
|
||||
super_admin_key None The key for the
|
||||
.super_admin account.
|
||||
===================== =============================== =======================
|
||||
|
||||
|
||||
------------------------
|
||||
Memcached Considerations
|
||||
------------------------
|
||||
|
@ -8,7 +8,7 @@ Creating Your Own Auth Server and Middleware
|
||||
|
||||
The included swift/auth/server.py and swift/common/middleware/auth.py are good
|
||||
minimal examples of how to create an external auth server and proxy server auth
|
||||
middleware. Also, see the `Swauth <https://launchpad.net/swauth>`_ project for
|
||||
middleware. Also, see swift/common/middleware/swauth.py for
|
||||
a more complete implementation. The main points are that the auth middleware
|
||||
can reject requests up front, before they ever get to the Swift Proxy
|
||||
application, and afterwards when the proxy issues callbacks to verify
|
||||
@ -356,6 +356,7 @@ repoze.what::
|
||||
self.auth_port = int(conf.get('port', 11000))
|
||||
self.ssl = \
|
||||
conf.get('ssl', 'false').lower() in ('true', 'on', '1', 'yes')
|
||||
self.auth_prefix = conf.get('prefix', '/')
|
||||
self.timeout = int(conf.get('node_timeout', 10))
|
||||
|
||||
def authenticate(self, env, identity):
|
||||
@ -371,7 +372,7 @@ repoze.what::
|
||||
return user
|
||||
with Timeout(self.timeout):
|
||||
conn = http_connect(self.auth_host, self.auth_port, 'GET',
|
||||
'/token/%s' % token, ssl=self.ssl)
|
||||
'%stoken/%s' % (self.auth_prefix, token), ssl=self.ssl)
|
||||
resp = conn.getresponse()
|
||||
resp.read()
|
||||
conn.close()
|
||||
|
@ -216,7 +216,9 @@ Configuring each node
|
||||
|
||||
Sample configuration files are provided with all defaults in line-by-line comments.
|
||||
|
||||
#. Create `/etc/swift/auth-server.conf`::
|
||||
#. If your going to use the DevAuth (the default swift-auth-server), create
|
||||
`/etc/swift/auth-server.conf` (you can skip this if you're going to use
|
||||
Swauth)::
|
||||
|
||||
[DEFAULT]
|
||||
user = <your-user-name>
|
||||
@ -237,15 +239,25 @@ Sample configuration files are provided with all defaults in line-by-line commen
|
||||
user = <your-user-name>
|
||||
|
||||
[pipeline:main]
|
||||
# For DevAuth:
|
||||
pipeline = healthcheck cache auth proxy-server
|
||||
# For Swauth:
|
||||
# pipeline = healthcheck cache swauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
||||
# Only needed for DevAuth
|
||||
[filter:auth]
|
||||
use = egg:swift#auth
|
||||
|
||||
# Only needed for Swauth
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
# Highly recommended to change this.
|
||||
super_admin_key = swauthkey
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
|
||||
@ -562,18 +574,32 @@ Setting up scripts for running Swift
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# The auth-server line is only needed for DevAuth:
|
||||
swift-init auth-server start
|
||||
swift-init proxy-server start
|
||||
swift-init account-server start
|
||||
swift-init container-server start
|
||||
swift-init object-server start
|
||||
|
||||
#. For Swauth (not needed for DevAuth), create `~/bin/recreateaccounts`::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Replace devauth with whatever your super_admin key is (recorded in
|
||||
# /etc/swift/proxy-server.conf).
|
||||
swauth-prep -K swauthkey
|
||||
swauth-add-user -K swauthkey -a test tester testing
|
||||
swauth-add-user -K swauthkey -a test2 tester2 testing2
|
||||
swauth-add-user -K swauthkey test tester3 testing3
|
||||
swauth-add-user -K swauthkey -a -r reseller reseller reseller
|
||||
|
||||
#. Create `~/bin/startrest`::
|
||||
|
||||
#!/bin/bash
|
||||
|
||||
# Replace devauth with whatever your super_admin key is (recorded in
|
||||
# /etc/swift/auth-server.conf).
|
||||
# /etc/swift/auth-server.conf). This swift-auth-recreate-accounts line
|
||||
# is only needed for DevAuth:
|
||||
swift-auth-recreate-accounts -K devauth
|
||||
swift-init object-updater start
|
||||
swift-init container-updater start
|
||||
@ -589,13 +615,14 @@ Setting up scripts for running Swift
|
||||
#. `remakerings`
|
||||
#. `cd ~/swift/trunk; ./.unittests`
|
||||
#. `startmain` (The ``Unable to increase file descriptor limit. Running as non-root?`` warnings are expected and ok.)
|
||||
#. `swift-auth-add-user -K devauth -a test tester testing` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
|
||||
#. Get an `X-Storage-Url` and `X-Auth-Token`: ``curl -v -H 'X-Storage-User: test:tester' -H 'X-Storage-Pass: testing' http://127.0.0.1:11000/v1.0``
|
||||
#. For Swauth: `recreateaccounts`
|
||||
#. For DevAuth: `swift-auth-add-user -K devauth -a test tester testing` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
|
||||
#. Get an `X-Storage-Url` and `X-Auth-Token`: ``curl -v -H 'X-Storage-User: test:tester' -H 'X-Storage-Pass: testing' http://127.0.0.1:11000/v1.0`` # For Swauth, make the last URL `http://127.0.0.1:8080/auth/v1.0`
|
||||
#. Check that you can GET account: ``curl -v -H 'X-Auth-Token: <token-from-x-auth-token-above>' <url-from-x-storage-url-above>``
|
||||
#. Check that `st` works: `st -A http://127.0.0.1:11000/v1.0 -U test:tester -K testing stat`
|
||||
#. `swift-auth-add-user -K devauth -a test2 tester2 testing2` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
|
||||
#. `swift-auth-add-user -K devauth test tester3 testing3` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
|
||||
#. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.conf`
|
||||
#. Check that `st` works: `st -A http://127.0.0.1:11000/v1.0 -U test:tester -K testing stat` # For Swauth, make the URL `http://127.0.0.1:8080/auth/v1.0`
|
||||
#. For DevAuth: `swift-auth-add-user -K devauth -a test2 tester2 testing2` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
|
||||
#. For DevAuth: `swift-auth-add-user -K devauth test tester3 testing3` # Replace ``devauth`` with whatever your super_admin key is (recorded in /etc/swift/auth-server.conf).
|
||||
#. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.conf` # For Swauth, add auth_prefix = /auth/ and change auth_port = 8080.
|
||||
#. `cd ~/swift/trunk; ./.functests` (Note: functional tests will first delete
|
||||
everything in the configured accounts.)
|
||||
#. `cd ~/swift/trunk; ./.probetests` (Note: probe tests will reset your
|
||||
|
@ -8,7 +8,9 @@ Talking to Swift with Cyberduck
|
||||
|
||||
#. Install Swift, or have credentials for an existing Swift installation. If
|
||||
you plan to install Swift on your own server, follow the general guidelines
|
||||
in the section following this one.
|
||||
in the section following this one. (This documentation assumes the use of
|
||||
the DevAuth auth server; if you're using Swauth, you should change all auth
|
||||
URLs /v1.0 to /auth/v1.0)
|
||||
|
||||
#. Verify you can connect using the standard Swift Tool `st` from your
|
||||
"public" URL (yes I know this resolves privately inside EC2)::
|
||||
|
@ -13,8 +13,8 @@ Prerequisites
|
||||
Basic architecture and terms
|
||||
----------------------------
|
||||
- *node* - a host machine running one or more Swift services
|
||||
- *Proxy node* - node that runs Proxy services
|
||||
- *Auth node* - node that runs the Auth service
|
||||
- *Proxy node* - node that runs Proxy services; can also run Swauth
|
||||
- *Auth node* - node that runs the Auth service; only required for DevAuth
|
||||
- *Storage node* - node that runs Account, Container, and Object services
|
||||
- *ring* - a set of mappings of Swift data to physical devices
|
||||
|
||||
@ -23,13 +23,14 @@ This document shows a cluster using the following types of nodes:
|
||||
- one Proxy node
|
||||
|
||||
- Runs the swift-proxy-server processes which proxy requests to the
|
||||
appropriate Storage nodes.
|
||||
appropriate Storage nodes. For Swauth, the proxy server will also contain
|
||||
the Swauth service as WSGI middleware.
|
||||
|
||||
- one Auth node
|
||||
|
||||
- Runs the swift-auth-server which controls authentication and
|
||||
authorization for all requests. This can be on the same node as a
|
||||
Proxy node.
|
||||
Proxy node. This is only required for DevAuth.
|
||||
|
||||
- five Storage nodes
|
||||
|
||||
@ -120,16 +121,27 @@ Configure the Proxy node
|
||||
user = swift
|
||||
|
||||
[pipeline:main]
|
||||
# For DevAuth:
|
||||
pipeline = healthcheck cache auth proxy-server
|
||||
# For Swauth:
|
||||
# pipeline = healthcheck cache swauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
allow_account_management = true
|
||||
|
||||
# Only needed for DevAuth
|
||||
[filter:auth]
|
||||
use = egg:swift#auth
|
||||
ssl = true
|
||||
|
||||
# Only needed for Swauth
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
default_swift_cluster = https://<PROXY_LOCAL_NET_IP>:8080/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = swauthkey
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
|
||||
@ -194,6 +206,8 @@ Configure the Proxy node
|
||||
Configure the Auth node
|
||||
-----------------------
|
||||
|
||||
.. note:: Only required for DevAuth; you can skip this section for Swauth.
|
||||
|
||||
#. If this node is not running on the same node as a proxy, create a
|
||||
self-signed cert as you did for the Proxy node
|
||||
|
||||
@ -358,13 +372,20 @@ Create Swift admin account and test
|
||||
|
||||
You run these commands from the Auth node.
|
||||
|
||||
.. note:: For Swauth, replace the https://<AUTH_HOSTNAME>:11000/v1.0 with
|
||||
https://<PROXY_HOSTNAME>:8080/auth/v1.0
|
||||
|
||||
#. Create a user with administrative privileges (account = system,
|
||||
username = root, password = testpass). Make sure to replace
|
||||
``devauth`` with whatever super_admin key you assigned in the
|
||||
auth-server.conf file above. *Note: None of the values of
|
||||
``devauth`` (or ``swauthkey``) with whatever super_admin key you assigned in
|
||||
the auth-server.conf file (or proxy-server.conf file in the case of Swauth)
|
||||
above. *Note: None of the values of
|
||||
account, username, or password are special - they can be anything.*::
|
||||
|
||||
# For DevAuth:
|
||||
swift-auth-add-user -K devauth -a system root testpass
|
||||
# For Swauth:
|
||||
swauth-add-user -K swauthkey -a system root testpass
|
||||
|
||||
#. Get an X-Storage-Url and X-Auth-Token::
|
||||
|
||||
@ -404,20 +425,50 @@ See :ref:`config-proxy` for the initial setup, and then follow these additional
|
||||
use = egg:swift#memcache
|
||||
memcache_servers = <PROXY_LOCAL_NET_IP>:11211
|
||||
|
||||
#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/auth-server.conf::
|
||||
#. Change the default_cluster_url to point to the load balanced url, rather than the first proxy server you created in /etc/swift/auth-server.conf (for DevAuth) or in /etc/swift/proxy-server.conf (for Swauth)::
|
||||
|
||||
# For DevAuth, in /etc/swift/auth-server.conf
|
||||
[app:auth-server]
|
||||
use = egg:swift#auth
|
||||
default_cluster_url = https://<LOAD_BALANCER_HOSTNAME>/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = devauth
|
||||
|
||||
#. After you change the default_cluster_url setting, you have to delete the auth database and recreate the Swift users, or manually update the auth database with the correct URL for each account.
|
||||
# For Swauth, in /etc/swift/proxy-server.conf
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
default_swift_cluster = local:http://<LOAD_BALANCER_HOSTNAME>/v1
|
||||
# Highly recommended to change this key to something else!
|
||||
super_admin_key = swauthkey
|
||||
|
||||
#. For DevAuth, after you change the default_cluster_url setting, you have to delete the auth database and recreate the Swift users, or manually update the auth database with the correct URL for each account.
|
||||
|
||||
For Swauth, you can change a service URL with::
|
||||
|
||||
swauth-set-account-service -K swauthkey <account> storage local <new_url_for_the_account>
|
||||
|
||||
You can obtain old service URLs with::
|
||||
|
||||
swauth-list -K swauthkey <account>
|
||||
|
||||
#. Next, copy all the ring information to all the nodes, including your new proxy nodes, and ensure the ring info gets to all the storage nodes as well.
|
||||
|
||||
#. After you sync all the nodes, make sure the admin has the keys in /etc/swift and the ownership for the ring file is correct.
|
||||
|
||||
Additional Cleanup Script for Swauth
|
||||
------------------------------------
|
||||
|
||||
If you decide to use 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 -K swauthkey >
|
||||
/dev/null` to a crontab entry on one of the proxies that is running Swauth; but
|
||||
run `swauth-cleanup-tokens` with no arguments for detailed help on the options
|
||||
available.
|
||||
|
||||
Troubleshooting Notes
|
||||
---------------------
|
||||
If you see problems, look in var/log/syslog (or messages on some distros).
|
||||
|
@ -42,6 +42,15 @@ Auth
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _common_swauth:
|
||||
|
||||
Swauth
|
||||
======
|
||||
|
||||
.. automodule:: swift.common.middleware.swauth
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. _acls:
|
||||
|
||||
ACLs
|
||||
|
@ -48,9 +48,148 @@ implementing your own auth.
|
||||
|
||||
Also, see :doc:`development_auth`.
|
||||
|
||||
------------------
|
||||
History and Future
|
||||
------------------
|
||||
|
||||
What's established in Swift for authentication/authorization has history from
|
||||
before Swift, so that won't be recorded here.
|
||||
------
|
||||
Swauth
|
||||
------
|
||||
|
||||
The Swauth system is an optional DevAuth replacement 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
|
||||
|
@ -1,3 +1,4 @@
|
||||
# Only needed for DevAuth; Swauth is within the proxy-server.conf
|
||||
[DEFAULT]
|
||||
# bind_ip = 0.0.0.0
|
||||
# bind_port = 11000
|
||||
|
@ -9,7 +9,10 @@
|
||||
# key_file = /etc/swift/proxy.key
|
||||
|
||||
[pipeline:main]
|
||||
# For DevAuth:
|
||||
pipeline = catch_errors healthcheck cache ratelimit auth proxy-server
|
||||
# For Swauth:
|
||||
# pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
|
||||
|
||||
[app:proxy-server]
|
||||
use = egg:swift#proxy
|
||||
@ -33,6 +36,7 @@ use = egg:swift#proxy
|
||||
# 'false' no one, even authorized, can.
|
||||
# allow_account_management = false
|
||||
|
||||
# Only needed for DevAuth
|
||||
[filter:auth]
|
||||
use = egg:swift#auth
|
||||
# The reseller prefix will verify a token begins with this prefix before even
|
||||
@ -44,8 +48,38 @@ use = egg:swift#auth
|
||||
# ip = 127.0.0.1
|
||||
# port = 11000
|
||||
# ssl = false
|
||||
# prefix = /
|
||||
# node_timeout = 10
|
||||
|
||||
# Only needed for Swauth
|
||||
[filter:swauth]
|
||||
use = egg:swift#swauth
|
||||
# log_name = auth-server
|
||||
# log_facility = LOG_LOCAL0
|
||||
# log_level = INFO
|
||||
# log_headers = False
|
||||
# The reseller prefix will verify a token begins with this prefix before even
|
||||
# attempting to validate it. Also, with authorization, only Swift storage
|
||||
# accounts with this prefix will be authorized by this middleware. Useful if
|
||||
# multiple auth systems are in use for one Swift cluster.
|
||||
# reseller_prefix = AUTH
|
||||
# The auth prefix will cause requests beginning with this prefix to be routed
|
||||
# to the auth subsystem, for granting tokens, creating accounts, users, etc.
|
||||
# auth_prefix = /auth/
|
||||
# Cluster strings are of the format name:url where name is a short name for the
|
||||
# Swift cluster and url is the url to the proxy server(s) for the cluster.
|
||||
# default_swift_cluster = local:http://127.0.0.1:8080/v1
|
||||
# You may also use the format name::url::url where the first url is the one
|
||||
# given to users to access their account (public url) and the second is the one
|
||||
# used by swauth itself to create and delete accounts (private url). This is
|
||||
# useful when a load balancer url should be used by users, but swauth itself is
|
||||
# behind the load balancer. Example:
|
||||
# default_swift_cluster = local::https://public.com:8080/v1::http://private.com:8080/v1
|
||||
# token_life = 86400
|
||||
# node_timeout = 10
|
||||
# Highly recommended to change this.
|
||||
super_admin_key = swauthkey
|
||||
|
||||
[filter:healthcheck]
|
||||
use = egg:swift#healthcheck
|
||||
|
||||
|
@ -1,5 +1,8 @@
|
||||
[stats]
|
||||
# For DevAuth:
|
||||
auth_url = http://saio:11000/auth
|
||||
# For Swauth:
|
||||
# auth_url = http://saio:8080/auth/v1.0
|
||||
auth_user = test:tester
|
||||
auth_key = testing
|
||||
# swift_dir = /etc/swift
|
||||
|
6
setup.py
6
setup.py
@ -21,6 +21,7 @@ import subprocess
|
||||
|
||||
from swift import __version__ as version
|
||||
|
||||
|
||||
class local_sdist(sdist):
|
||||
"""Customized sdist hook - builds the ChangeLog file from VC first"""
|
||||
|
||||
@ -79,6 +80,10 @@ setup(
|
||||
'bin/swift-log-uploader',
|
||||
'bin/swift-log-stats-collector',
|
||||
'bin/swift-account-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', 'bin/swift-auth-to-swauth',
|
||||
],
|
||||
entry_points={
|
||||
'paste.app_factory': [
|
||||
@ -90,6 +95,7 @@ setup(
|
||||
],
|
||||
'paste.filter_factory': [
|
||||
'auth=swift.common.middleware.auth:filter_factory',
|
||||
'swauth=swift.common.middleware.swauth:filter_factory',
|
||||
'healthcheck=swift.common.middleware.healthcheck:filter_factory',
|
||||
'memcache=swift.common.middleware.memcache:filter_factory',
|
||||
'ratelimit=swift.common.middleware.ratelimit:filter_factory',
|
||||
|
@ -35,6 +35,7 @@ class DevAuth(object):
|
||||
self.auth_host = conf.get('ip', '127.0.0.1')
|
||||
self.auth_port = int(conf.get('port', 11000))
|
||||
self.ssl = conf.get('ssl', 'false').lower() in TRUE_VALUES
|
||||
self.auth_prefix = conf.get('prefix', '/')
|
||||
self.timeout = int(conf.get('node_timeout', 10))
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
@ -131,7 +132,7 @@ class DevAuth(object):
|
||||
if not groups:
|
||||
with Timeout(self.timeout):
|
||||
conn = http_connect(self.auth_host, self.auth_port, 'GET',
|
||||
'/token/%s' % token, ssl=self.ssl)
|
||||
'%stoken/%s' % (self.auth_prefix, token), ssl=self.ssl)
|
||||
resp = conn.getresponse()
|
||||
resp.read()
|
||||
conn.close()
|
||||
@ -158,9 +159,10 @@ class DevAuth(object):
|
||||
user_groups = (req.remote_user or '').split(',')
|
||||
if '.reseller_admin' in user_groups:
|
||||
return None
|
||||
if account in user_groups and (req.method != 'PUT' or container):
|
||||
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 PUT...
|
||||
# account DELETE or PUT...
|
||||
return None
|
||||
referrers, groups = parse_acl(getattr(req, 'acl', None))
|
||||
if referrer_allowed(req.referer, referrers):
|
||||
|
1312
swift/common/middleware/swauth.py
Normal file
1312
swift/common/middleware/swauth.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -396,6 +396,7 @@ def get_logger(conf, name=None, log_to_console=False):
|
||||
root_logger = logging.getLogger()
|
||||
if hasattr(get_logger, 'handler') and get_logger.handler:
|
||||
root_logger.removeHandler(get_logger.handler)
|
||||
get_logger.handler.close()
|
||||
get_logger.handler = None
|
||||
if log_to_console:
|
||||
# check if a previous call to get_logger already added a console logger
|
||||
|
@ -911,12 +911,14 @@ class ObjectController(Controller):
|
||||
self.account_name, self.container_name, self.object_name)
|
||||
req.headers['X-Timestamp'] = normalize_timestamp(time.time())
|
||||
# Sometimes the 'content-type' header exists, but is set to None.
|
||||
content_type_manually_set = True
|
||||
if not req.headers.get('content-type'):
|
||||
guessed_type, _junk = mimetypes.guess_type(req.path_info)
|
||||
if not guessed_type:
|
||||
req.headers['Content-Type'] = 'application/octet-stream'
|
||||
else:
|
||||
req.headers['Content-Type'] = guessed_type
|
||||
content_type_manually_set = False
|
||||
error_response = check_object_creation(req, self.object_name)
|
||||
if error_response:
|
||||
return error_response
|
||||
@ -950,17 +952,20 @@ class ObjectController(Controller):
|
||||
self.container_name = orig_container_name
|
||||
new_req = Request.blank(req.path_info,
|
||||
environ=req.environ, headers=req.headers)
|
||||
if 'x-object-manifest' in source_resp.headers:
|
||||
data_source = iter([''])
|
||||
new_req.content_length = 0
|
||||
new_req.headers['X-Object-Manifest'] = \
|
||||
source_resp.headers['x-object-manifest']
|
||||
else:
|
||||
data_source = source_resp.app_iter
|
||||
new_req.content_length = source_resp.content_length
|
||||
new_req.etag = source_resp.etag
|
||||
data_source = source_resp.app_iter
|
||||
new_req.content_length = source_resp.content_length
|
||||
if new_req.content_length is None:
|
||||
# This indicates a transfer-encoding: chunked source object,
|
||||
# which currently only happens because there are more than
|
||||
# CONTAINER_LISTING_LIMIT segments in a segmented object. In
|
||||
# this case, we're going to refuse to do the server-side copy.
|
||||
return HTTPRequestEntityTooLarge(request=req)
|
||||
new_req.etag = source_resp.etag
|
||||
# we no longer need the X-Copy-From header
|
||||
del new_req.headers['X-Copy-From']
|
||||
if not content_type_manually_set:
|
||||
new_req.headers['Content-Type'] = \
|
||||
source_resp.headers['Content-Type']
|
||||
for k, v in source_resp.headers.items():
|
||||
if k.lower().startswith('x-object-meta-'):
|
||||
new_req.headers[k] = v
|
||||
@ -1683,7 +1688,8 @@ class BaseApplication(object):
|
||||
def update_request(self, req):
|
||||
req.bytes_transferred = '-'
|
||||
req.client_disconnect = False
|
||||
req.headers['x-cf-trans-id'] = 'tx' + str(uuid.uuid4())
|
||||
if 'x-cf-trans-id' not in req.headers:
|
||||
req.headers['x-cf-trans-id'] = 'tx' + str(uuid.uuid4())
|
||||
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']
|
||||
|
@ -1,7 +1,12 @@
|
||||
# sample config
|
||||
auth_host = 127.0.0.1
|
||||
# For DevAuth:
|
||||
auth_port = 11000
|
||||
# For Swauth:
|
||||
# auth_port = 8080
|
||||
auth_ssl = no
|
||||
# For Swauth:
|
||||
# auth_prefix = /auth/
|
||||
|
||||
# Primary functional test account (needs admin access to the account)
|
||||
account = test
|
||||
|
@ -82,6 +82,7 @@ class Connection(object):
|
||||
self.auth_host = config['auth_host']
|
||||
self.auth_port = int(config['auth_port'])
|
||||
self.auth_ssl = config['auth_ssl'] in ('on', 'true', 'yes', '1')
|
||||
self.auth_prefix = config.get('auth_prefix', '/')
|
||||
|
||||
self.account = config['account']
|
||||
self.username = config['username']
|
||||
@ -105,11 +106,11 @@ class Connection(object):
|
||||
return
|
||||
|
||||
headers = {
|
||||
'x-storage-user': self.username,
|
||||
'x-storage-pass': self.password,
|
||||
'x-auth-user': '%s:%s' % (self.account, self.username),
|
||||
'x-auth-key': self.password,
|
||||
}
|
||||
|
||||
path = '/v1/%s/auth' % (self.account)
|
||||
path = '%sv1.0' % (self.auth_prefix)
|
||||
if self.auth_ssl:
|
||||
connection = httplib.HTTPSConnection(self.auth_host,
|
||||
port=self.auth_port)
|
||||
|
@ -31,7 +31,10 @@ if not all([swift_test_auth, swift_test_user[0], swift_test_key[0]]):
|
||||
swift_test_auth = 'http'
|
||||
if conf.get('auth_ssl', 'no').lower() in ('yes', 'true', 'on', '1'):
|
||||
swift_test_auth = 'https'
|
||||
swift_test_auth += '://%(auth_host)s:%(auth_port)s/v1.0' % conf
|
||||
if 'auth_prefix' not in conf:
|
||||
conf['auth_prefix'] = '/'
|
||||
swift_test_auth += \
|
||||
'://%(auth_host)s:%(auth_port)s%(auth_prefix)sv1.0' % conf
|
||||
swift_test_user[0] = '%(account)s:%(username)s' % conf
|
||||
swift_test_key[0] = conf['password']
|
||||
try:
|
||||
|
@ -24,13 +24,25 @@ from swift.common.client import get_auth
|
||||
from swift.common.ring import Ring
|
||||
|
||||
|
||||
SUPER_ADMIN_KEY = None
|
||||
AUTH_TYPE = None
|
||||
|
||||
c = ConfigParser()
|
||||
AUTH_SERVER_CONF_FILE = environ.get('SWIFT_AUTH_SERVER_CONF_FILE',
|
||||
'/etc/swift/auth-server.conf')
|
||||
c = ConfigParser()
|
||||
if not c.read(AUTH_SERVER_CONF_FILE):
|
||||
exit('Unable to read config file: %s' % AUTH_SERVER_CONF_FILE)
|
||||
conf = dict(c.items('app:auth-server'))
|
||||
SUPER_ADMIN_KEY = conf.get('super_admin_key', 'devauth')
|
||||
if c.read(AUTH_SERVER_CONF_FILE):
|
||||
conf = dict(c.items('app:auth-server'))
|
||||
SUPER_ADMIN_KEY = conf.get('super_admin_key', 'devauth')
|
||||
AUTH_TYPE = 'devauth'
|
||||
else:
|
||||
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')
|
||||
AUTH_TYPE = 'swauth'
|
||||
else:
|
||||
exit('Unable to read config file: %s' % AUTH_SERVER_CONF_FILE)
|
||||
|
||||
|
||||
def kill_pids(pids):
|
||||
@ -45,8 +57,9 @@ def reset_environment():
|
||||
call(['resetswift'])
|
||||
pids = {}
|
||||
try:
|
||||
pids['auth'] = Popen(['swift-auth-server',
|
||||
'/etc/swift/auth-server.conf']).pid
|
||||
if AUTH_TYPE == 'devauth':
|
||||
pids['auth'] = Popen(['swift-auth-server',
|
||||
'/etc/swift/auth-server.conf']).pid
|
||||
pids['proxy'] = Popen(['swift-proxy-server',
|
||||
'/etc/swift/proxy-server.conf']).pid
|
||||
port2server = {}
|
||||
@ -60,14 +73,21 @@ def reset_environment():
|
||||
container_ring = Ring('/etc/swift/container.ring.gz')
|
||||
object_ring = Ring('/etc/swift/object.ring.gz')
|
||||
sleep(5)
|
||||
conn = http_connect('127.0.0.1', '11000', 'POST', '/recreate_accounts',
|
||||
headers={'X-Auth-Admin-User': '.super_admin',
|
||||
'X-Auth-Admin-Key': SUPER_ADMIN_KEY})
|
||||
resp = conn.getresponse()
|
||||
if resp.status != 200:
|
||||
raise Exception('Recreating accounts failed. (%d)' % resp.status)
|
||||
url, token = \
|
||||
get_auth('http://127.0.0.1:11000/auth', 'test:tester', 'testing')
|
||||
if AUTH_TYPE == 'devauth':
|
||||
conn = http_connect('127.0.0.1', '11000', 'POST',
|
||||
'/recreate_accounts',
|
||||
headers={'X-Auth-Admin-User': '.super_admin',
|
||||
'X-Auth-Admin-Key': SUPER_ADMIN_KEY})
|
||||
resp = conn.getresponse()
|
||||
if resp.status != 200:
|
||||
raise Exception('Recreating accounts failed. (%d)' %
|
||||
resp.status)
|
||||
url, token = get_auth('http://127.0.0.1:11000/auth', 'test:tester',
|
||||
'testing')
|
||||
elif AUTH_TYPE == 'swauth':
|
||||
call(['recreateaccounts'])
|
||||
url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
|
||||
'test:tester', 'testing')
|
||||
account = url.split('/')[-1]
|
||||
except BaseException, err:
|
||||
kill_pids(pids)
|
||||
|
@ -432,6 +432,40 @@ class TestAuth(unittest.TestCase):
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp and resp.status_int, 403)
|
||||
|
||||
def test_account_delete_permissions(self):
|
||||
req = Request.blank('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp and resp.status_int, 403)
|
||||
|
||||
req = Request.blank('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,AUTH_other'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp and resp.status_int, 403)
|
||||
|
||||
# Even DELETEs to your own account as account admin should fail
|
||||
req = Request.blank('/v1/AUTH_old',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,AUTH_old'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp and resp.status_int, 403)
|
||||
|
||||
req = Request.blank('/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 = Request.blank('/v1/AUTH_new',
|
||||
environ={'REQUEST_METHOD': 'DELETE'})
|
||||
req.remote_user = 'act:usr,act,.super_admin'
|
||||
resp = self.test_auth.authorize(req)
|
||||
self.assertEquals(resp and resp.status_int, 403)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
3117
test/unit/common/middleware/test_swauth.py
Normal file
3117
test/unit/common/middleware/test_swauth.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1264,6 +1264,7 @@ class TestObjectController(unittest.TestCase):
|
||||
with save_globals():
|
||||
controller = proxy_server.ObjectController(self.app, 'account',
|
||||
'container', 'object')
|
||||
# initial source object PUT
|
||||
req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'Content-Length': '0'})
|
||||
self.app.update_request(req)
|
||||
@ -1273,6 +1274,7 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = controller.PUT(req)
|
||||
self.assertEquals(resp.status_int, 201)
|
||||
|
||||
# basic copy
|
||||
req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'Content-Length': '0',
|
||||
'X-Copy-From': 'c/o'})
|
||||
@ -1285,6 +1287,7 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEquals(resp.status_int, 201)
|
||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o')
|
||||
|
||||
# non-zero content length
|
||||
req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'Content-Length': '5',
|
||||
'X-Copy-From': 'c/o'})
|
||||
@ -1296,6 +1299,7 @@ class TestObjectController(unittest.TestCase):
|
||||
resp = controller.PUT(req)
|
||||
self.assertEquals(resp.status_int, 400)
|
||||
|
||||
# extra source path parsing
|
||||
req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'Content-Length': '0',
|
||||
'X-Copy-From': 'c/o/o2'})
|
||||
@ -1308,6 +1312,7 @@ class TestObjectController(unittest.TestCase):
|
||||
self.assertEquals(resp.status_int, 201)
|
||||
self.assertEquals(resp.headers['x-copied-from'], 'c/o/o2')
|
||||
|
||||
# space in soure path
|
||||
req = Request.blank('/a/c/o', environ={'REQUEST_METHOD': 'PUT'},
|
||||
headers={'Content-Length': '0',
|
||||
'X-Copy-From': 'c/o%20o2'})
|
||||
@ -1995,6 +2000,125 @@ class TestObjectController(unittest.TestCase):
|
||||
# will be sent in a single chunk.
|
||||
self.assertEquals(body,
|
||||
'19\r\n1234 1234 1234 1234 1234 \r\n0\r\n\r\n')
|
||||
# Make a copy of the manifested object, which should
|
||||
# error since the number of segments exceeds
|
||||
# CONTAINER_LISTING_LIMIT.
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/segmented/copy HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Auth-Token: '
|
||||
't\r\nX-Copy-From: segmented/name\r\nContent-Length: '
|
||||
'0\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 413'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
body = fd.read()
|
||||
# After adjusting the CONTAINER_LISTING_LIMIT, make a copy of
|
||||
# the manifested object which should consolidate the segments.
|
||||
proxy_server.CONTAINER_LISTING_LIMIT = 10000
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/segmented/copy HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Auth-Token: '
|
||||
't\r\nX-Copy-From: segmented/name\r\nContent-Length: '
|
||||
'0\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 201'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
body = fd.read()
|
||||
# Retrieve and validate the copy.
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /v1/a/segmented/copy HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Auth-Token: '
|
||||
't\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
self.assert_('x-object-manifest:' not in headers.lower())
|
||||
self.assert_('Content-Length: 25\r' in headers)
|
||||
body = fd.read()
|
||||
self.assertEquals(body, '1234 1234 1234 1234 1234 ')
|
||||
# Check copy content type
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/c/obj HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Storage-Token: '
|
||||
't\r\nContent-Length: 0\r\nContent-Type: text/jibberish'
|
||||
'\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 201'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/c/obj2 HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Storage-Token: '
|
||||
't\r\nContent-Length: 0\r\nX-Copy-From: c/obj\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 201'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
# Ensure getting the copied file gets original content-type
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /v1/a/c/obj2 HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Auth-Token: '
|
||||
't\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
self.assert_('Content-Type: text/jibberish' in headers)
|
||||
# Check set content type
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/c/obj3 HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Storage-Token: '
|
||||
't\r\nContent-Length: 0\r\nContent-Type: foo/bar'
|
||||
'\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 201'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
# Ensure getting the copied file gets original content-type
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /v1/a/c/obj3 HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Auth-Token: '
|
||||
't\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
self.assert_('Content-Type: foo/bar' in
|
||||
headers.split('\r\n'), repr(headers.split('\r\n')))
|
||||
# Check set content type with charset
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('PUT /v1/a/c/obj4 HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Storage-Token: '
|
||||
't\r\nContent-Length: 0\r\nContent-Type: foo/bar'
|
||||
'; charset=UTF-8\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 201'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
# Ensure getting the copied file gets original content-type
|
||||
sock = connect_tcp(('localhost', prolis.getsockname()[1]))
|
||||
fd = sock.makefile()
|
||||
fd.write('GET /v1/a/c/obj4 HTTP/1.1\r\nHost: '
|
||||
'localhost\r\nConnection: close\r\nX-Auth-Token: '
|
||||
't\r\n\r\n')
|
||||
fd.flush()
|
||||
headers = readuntil2crlfs(fd)
|
||||
exp = 'HTTP/1.1 200'
|
||||
self.assertEquals(headers[:len(exp)], exp)
|
||||
self.assert_('Content-Type: foo/bar; charset=UTF-8' in
|
||||
headers.split('\r\n'), repr(headers.split('\r\n')))
|
||||
finally:
|
||||
prospa.kill()
|
||||
acc1spa.kill()
|
||||
|
Loading…
x
Reference in New Issue
Block a user