Removing DevAuth
This commit is contained in:
parent
ca51e87967
commit
bd22dbe712
@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# Copyright (c) 2010-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 ConfigParser import ConfigParser
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
default_conf = '/etc/swift/auth-server.conf'
|
|
||||||
parser = OptionParser(
|
|
||||||
usage='Usage: %prog [options] <account> <user> <password>')
|
|
||||||
parser.add_option('-c', '--conf', dest='conf', default=default_conf,
|
|
||||||
help='Configuration file to determine how to connect to the local '
|
|
||||||
'auth server (default: %s).' % default_conf)
|
|
||||||
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('-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
|
|
||||||
c = ConfigParser()
|
|
||||||
if not c.read(options.conf):
|
|
||||||
exit('Unable to read conf file: %s' % options.conf)
|
|
||||||
conf = dict(c.items('app:auth-server'))
|
|
||||||
host = conf.get('bind_ip', '127.0.0.1')
|
|
||||||
port = int(conf.get('bind_port', 11000))
|
|
||||||
ssl = conf.get('cert_file') is not None
|
|
||||||
path = '/account/%s/%s' % (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(host, port, 'PUT', path, headers, ssl=ssl)
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status == 204:
|
|
||||||
print resp.getheader('x-storage-url')
|
|
||||||
else:
|
|
||||||
print 'Update failed: %s %s' % (resp.status, resp.reason)
|
|
@ -1,53 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# Copyright (c) 2010-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 ConfigParser import ConfigParser
|
|
||||||
from optparse import OptionParser
|
|
||||||
from sys import argv, exit
|
|
||||||
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
default_conf = '/etc/swift/auth-server.conf'
|
|
||||||
parser = OptionParser(usage='Usage: %prog [options]')
|
|
||||||
parser.add_option('-c', '--conf', dest='conf', default=default_conf,
|
|
||||||
help='Configuration file to determine how to connect to the local '
|
|
||||||
'auth server (default: %s).' % default_conf)
|
|
||||||
parser.add_option('-U', '--admin-user', dest='admin_user',
|
|
||||||
default='.super_admin', help='The user with admin rights to recreate '
|
|
||||||
'accounts (default: .super_admin).')
|
|
||||||
parser.add_option('-K', '--admin-key', dest='admin_key',
|
|
||||||
help='The key for the user with admin rights to recreate accounts.')
|
|
||||||
args = argv[1:]
|
|
||||||
if not args:
|
|
||||||
args.append('-h')
|
|
||||||
(options, args) = parser.parse_args(args)
|
|
||||||
c = ConfigParser()
|
|
||||||
if not c.read(options.conf):
|
|
||||||
exit('Unable to read conf file: %s' % options.conf)
|
|
||||||
conf = dict(c.items('app:auth-server'))
|
|
||||||
host = conf.get('bind_ip', '127.0.0.1')
|
|
||||||
port = int(conf.get('bind_port', 11000))
|
|
||||||
ssl = conf.get('cert_file') is not None
|
|
||||||
path = '/recreate_accounts'
|
|
||||||
conn = http_connect(host, port, 'POST', path, ssl=ssl,
|
|
||||||
headers={'X-Auth-Admin-User': options.admin_user,
|
|
||||||
'X-Auth-Admin-Key': options.admin_key})
|
|
||||||
resp = conn.getresponse()
|
|
||||||
if resp.status == 200:
|
|
||||||
print resp.read()
|
|
||||||
else:
|
|
||||||
print 'Recreating accounts failed. (%d)' % resp.status
|
|
@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# Copyright (c) 2010-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 swift.common.utils import parse_options
|
|
||||||
from swift.common.wsgi import run_wsgi
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
conf_file, options = parse_options()
|
|
||||||
run_wsgi(conf_file, 'auth-server', default_port=11000, **options)
|
|
@ -1,44 +0,0 @@
|
|||||||
#!/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) != 2:
|
|
||||||
exit('Syntax: %s <path_to_auth.db>' % argv[0])
|
|
||||||
_junk, auth_db = argv
|
|
||||||
conn = sqlite3.connect(auth_db)
|
|
||||||
try:
|
|
||||||
listing = conn.execute('SELECT account, cfaccount, user, password, '
|
|
||||||
'admin, reseller_admin FROM account')
|
|
||||||
except sqlite3.OperationalError, err:
|
|
||||||
listing = conn.execute('SELECT account, cfaccount, user, password, '
|
|
||||||
'"f", "f" FROM account')
|
|
||||||
for account, cfaccount, user, password, admin, reseller_admin in listing:
|
|
||||||
cmd = ['swauth-add-user', '-K', '<your_swauth_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)
|
|
@ -1,48 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# Copyright (c) 2010-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 os.path import basename
|
|
||||||
from sys import argv, exit
|
|
||||||
|
|
||||||
from swift.common.db import get_db_connection
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app = basename(argv[0])
|
|
||||||
if len(argv) != 3:
|
|
||||||
exit('''
|
|
||||||
Syntax : %s <auth.db> <new_prefix>
|
|
||||||
Example: %s /etc/swift/auth.db AUTH'''.strip() % (app, app))
|
|
||||||
db = argv[1]
|
|
||||||
new_prefix = argv[2].rstrip('_')
|
|
||||||
print 'Updating %s' % db
|
|
||||||
conn = get_db_connection(db)
|
|
||||||
rows = conn.execute('SELECT url, cfaccount FROM account').fetchall()
|
|
||||||
for row in rows:
|
|
||||||
old_prefix = ''
|
|
||||||
uuid = row[1]
|
|
||||||
if '_' in row[1]:
|
|
||||||
old_prefix, uuid = row[1].split('_', 1)
|
|
||||||
new_cfaccount = '%s_%s' % (new_prefix, uuid)
|
|
||||||
new_url = row[0].replace(row[1], new_cfaccount)
|
|
||||||
print '%s ->\n%s' % (row[0], new_url)
|
|
||||||
print '%s ->\n%s' % (row[1], new_cfaccount)
|
|
||||||
print
|
|
||||||
conn.execute('''UPDATE account SET url = ?, cfaccount = ?
|
|
||||||
WHERE url = ? AND cfaccount = ?''',
|
|
||||||
(new_url, new_cfaccount, row[0], row[1]))
|
|
||||||
conn.commit()
|
|
||||||
print 'Updated %s rows.' % len(rows)
|
|
@ -159,15 +159,12 @@ of the cluster, we need to run the swift-stats-report tool to check the health
|
|||||||
of each of these containers and objects.
|
of each of these containers and objects.
|
||||||
|
|
||||||
These tools need direct access to the entire cluster and to the ring files
|
These tools need direct access to the entire cluster and to the ring files
|
||||||
(installing them on an auth server or a proxy server will probably do). Both
|
(installing them on a proxy server will probably do). Both
|
||||||
swift-stats-populate and swift-stats-report use the same configuration file,
|
swift-stats-populate and swift-stats-report use the same configuration file,
|
||||||
/etc/swift/stats.conf. Example conf file::
|
/etc/swift/stats.conf. Example conf file::
|
||||||
|
|
||||||
[stats]
|
[stats]
|
||||||
# For DevAuth:
|
auth_url = http://saio:11000/auth/v1.0
|
||||||
auth_url = http://saio:11000/v1.0
|
|
||||||
# For Swauth:
|
|
||||||
# auth_url = http://saio:11000/auth/v1.0
|
|
||||||
auth_user = test:tester
|
auth_user = test:tester
|
||||||
auth_key = testing
|
auth_key = testing
|
||||||
|
|
||||||
@ -236,15 +233,16 @@ then be graphed to see how cluster performance is trending.
|
|||||||
Additional Cleanup Script for Swauth
|
Additional Cleanup Script for Swauth
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
If you decide to use Swauth, you'll want to install a cronjob to clean up any
|
With Swauth, you'll want to install a cronjob to clean up any
|
||||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
||||||
occurs where a single user authenticates several times concurrently. Generally,
|
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
|
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).
|
up once a "token life" period (default: 1 day or 86400 seconds).
|
||||||
|
|
||||||
This should be as simple as adding `swauth-cleanup-tokens -K swauthkey >
|
This should be as simple as adding `swauth-cleanup-tokens -A
|
||||||
/dev/null` to a crontab entry on one of the proxies that is running Swauth; but
|
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
|
||||||
run `swauth-cleanup-tokens` with no arguments for detailed help on the options
|
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.
|
available.
|
||||||
|
|
||||||
------------------------
|
------------------------
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
.. _auth:
|
|
||||||
|
|
||||||
*************************
|
|
||||||
Developer's Authorization
|
|
||||||
*************************
|
|
||||||
|
|
||||||
.. _auth_server:
|
|
||||||
|
|
||||||
Auth Server
|
|
||||||
===========
|
|
||||||
|
|
||||||
.. automodule:: swift.auth.server
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -6,13 +6,11 @@ Auth Server and Middleware
|
|||||||
Creating Your Own Auth Server and Middleware
|
Creating Your Own Auth Server and Middleware
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
The included swift/auth/server.py and swift/common/middleware/auth.py are good
|
The included swift/common/middleware/swauth.py is a good example of how to
|
||||||
minimal examples of how to create an external auth server and proxy server auth
|
create an auth subsystem with proxy server auth middleware. The main points are
|
||||||
middleware. Also, see swift/common/middleware/swauth.py for
|
that the auth middleware can reject requests up front, before they ever get to
|
||||||
a more complete implementation. The main points are that the auth middleware
|
the Swift Proxy application, and afterwards when the proxy issues callbacks to
|
||||||
can reject requests up front, before they ever get to the Swift Proxy
|
verify authorization.
|
||||||
application, and afterwards when the proxy issues callbacks to verify
|
|
||||||
authorization.
|
|
||||||
|
|
||||||
It's generally good to separate the authentication and authorization
|
It's generally good to separate the authentication and authorization
|
||||||
procedures. Authentication verifies that a request actually comes from who it
|
procedures. Authentication verifies that a request actually comes from who it
|
||||||
@ -29,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 DevAuth will set the REMOTE_USER to a comma separated list of
|
The included Swauth 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
|
||||||
@ -39,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 DevAuth). This prefix will avoid
|
prefix (`AUTH_` by default with the included Swauth). 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.
|
||||||
@ -48,22 +46,20 @@ 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 DevAuth:
|
Example Authentication with Swauth:
|
||||||
|
|
||||||
* Token AUTH_tkabcd is given to the DevAuth middleware in a request's
|
* Token AUTH_tkabcd is given to the Swauth middleware in a request's
|
||||||
X-Auth-Token header.
|
X-Auth-Token header.
|
||||||
* The DevAuth middleware makes a validate token AUTH_tkabcd call to the
|
* The Swauth middleware validates the token AUTH_tkabcd and discovers
|
||||||
external DevAuth server.
|
|
||||||
* The external DevAuth server 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 external DevAuth server responds with "X-Auth-Groups:
|
* The Swauth server 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
|
||||||
other storage accounts, provided the storage account begins with the same
|
other storage accounts, provided the storage account begins with the same
|
||||||
`AUTH_` reseller prefix and the container has an ACL specifying at least
|
`AUTH_` reseller prefix and the container has an ACL specifying at least
|
||||||
one of those three groups returned.
|
one of those three groups.
|
||||||
|
|
||||||
Authorization is performed through callbacks by the Swift Proxy server to the
|
Authorization is performed through callbacks by the Swift Proxy server to the
|
||||||
WSGI environment's swift.authorize value, if one is set. The swift.authorize
|
WSGI environment's swift.authorize value, if one is set. The swift.authorize
|
||||||
@ -283,11 +279,9 @@ sometimes that's less important than meeting certain ACL requirements.
|
|||||||
Integrating With repoze.what
|
Integrating With repoze.what
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
Here's an example of integration with repoze.what, though honestly it just does
|
Here's an example of integration with repoze.what, though honestly I'm no
|
||||||
what the default swift/common/middleware/auth.py does in a slightly different
|
repoze.what expert by any stretch; this is just included here to hopefully give
|
||||||
way. I'm no repoze.what expert by any stretch; this is just included here to
|
folks a start on their own code if they want to use repoze.what::
|
||||||
hopefully give folks a start on their own code if they want to use
|
|
||||||
repoze.what::
|
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
@ -215,22 +215,6 @@ Configuring each node
|
|||||||
|
|
||||||
Sample configuration files are provided with all defaults in line-by-line comments.
|
Sample configuration files are provided with all defaults in line-by-line comments.
|
||||||
|
|
||||||
#. If you're 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>
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = auth-server
|
|
||||||
|
|
||||||
[app:auth-server]
|
|
||||||
use = egg:swift#auth
|
|
||||||
default_cluster_url = http://127.0.0.1:8080/v1
|
|
||||||
# Highly recommended to change this.
|
|
||||||
super_admin_key = devauth
|
|
||||||
|
|
||||||
#. Create `/etc/swift/proxy-server.conf`::
|
#. Create `/etc/swift/proxy-server.conf`::
|
||||||
|
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
@ -238,20 +222,12 @@ Sample configuration files are provided with all defaults in line-by-line commen
|
|||||||
user = <your-user-name>
|
user = <your-user-name>
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
# For DevAuth:
|
pipeline = healthcheck cache swauth proxy-server
|
||||||
pipeline = healthcheck cache auth proxy-server
|
|
||||||
# For Swauth:
|
|
||||||
# pipeline = healthcheck cache swauth proxy-server
|
|
||||||
|
|
||||||
[app:proxy-server]
|
[app:proxy-server]
|
||||||
use = egg:swift#proxy
|
use = egg:swift#proxy
|
||||||
allow_account_management = true
|
allow_account_management = true
|
||||||
|
|
||||||
# Only needed for DevAuth
|
|
||||||
[filter:auth]
|
|
||||||
use = egg:swift#auth
|
|
||||||
|
|
||||||
# Only needed for Swauth
|
|
||||||
[filter:swauth]
|
[filter:swauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#swauth
|
||||||
# Highly recommended to change this.
|
# Highly recommended to change this.
|
||||||
@ -573,14 +549,12 @@ Setting up scripts for running Swift
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
swift-init main start
|
swift-init main start
|
||||||
# The auth-server line is only needed for DevAuth:
|
|
||||||
swift-init auth-server start
|
|
||||||
|
|
||||||
#. For Swauth (not needed for DevAuth), create `~/bin/recreateaccounts`::
|
#. Create `~/bin/recreateaccounts`::
|
||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Replace devauth with whatever your super_admin key is (recorded in
|
# Replace swauthkey with whatever your super_admin key is (recorded in
|
||||||
# /etc/swift/proxy-server.conf).
|
# /etc/swift/proxy-server.conf).
|
||||||
swauth-prep -K swauthkey
|
swauth-prep -K swauthkey
|
||||||
swauth-add-user -K swauthkey -a test tester testing
|
swauth-add-user -K swauthkey -a test tester testing
|
||||||
@ -592,24 +566,17 @@ Setting up scripts for running Swift
|
|||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Replace devauth with whatever your super_admin key is (recorded in
|
|
||||||
# /etc/swift/auth-server.conf). This swift-auth-recreate-accounts line
|
|
||||||
# is only needed for DevAuth:
|
|
||||||
swift-auth-recreate-accounts -K devauth
|
|
||||||
swift-init rest start
|
swift-init rest start
|
||||||
|
|
||||||
#. `chmod +x ~/bin/*`
|
#. `chmod +x ~/bin/*`
|
||||||
#. `remakerings`
|
#. `remakerings`
|
||||||
#. `cd ~/swift/trunk; ./.unittests`
|
#. `cd ~/swift/trunk; ./.unittests`
|
||||||
#. `startmain` (The ``Unable to increase file descriptor limit. Running as non-root?`` warnings are expected and ok.)
|
#. `startmain` (The ``Unable to increase file descriptor limit. Running as non-root?`` warnings are expected and ok.)
|
||||||
#. For Swauth: `recreateaccounts`
|
#. `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:8080/auth/v1.0``
|
||||||
#. 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 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` # For Swauth, make the URL `http://127.0.0.1:8080/auth/v1.0`
|
#. Check that `st` works: `st -A http://127.0.0.1:8080/auth/v1.0 -U test:tester -K testing stat`
|
||||||
#. 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).
|
#. `cp ~/swift/trunk/test/functional/sample.conf /etc/swift/func_test.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
|
#. `cd ~/swift/trunk; ./.functests` (Note: functional tests will first delete
|
||||||
everything in the configured accounts.)
|
everything in the configured accounts.)
|
||||||
#. `cd ~/swift/trunk; ./.probetests` (Note: probe tests will reset your
|
#. `cd ~/swift/trunk; ./.probetests` (Note: probe tests will reset your
|
||||||
@ -634,7 +601,7 @@ If all doesn't go as planned, and tests fail, or you can't auth, or something do
|
|||||||
#. Everything is logged in /var/log/syslog, so that is a good first place to
|
#. Everything is logged in /var/log/syslog, so that is a good first place to
|
||||||
look for errors (most likely python tracebacks).
|
look for errors (most likely python tracebacks).
|
||||||
#. Make sure all of the server processes are running. For the base
|
#. Make sure all of the server processes are running. For the base
|
||||||
functionality, the Proxy, Account, Container, Object and Auth servers
|
functionality, the Proxy, Account, Container, and Object servers
|
||||||
should be running
|
should be running
|
||||||
#. If one of the servers are not running, and no errors are logged to syslog,
|
#. If one of the servers are not running, and no errors are logged to syslog,
|
||||||
it may be useful to try to start the server manually, for example:
|
it may be useful to try to start the server manually, for example:
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
===============================
|
|
||||||
Talking to Swift with Cyberduck
|
|
||||||
===============================
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
Put together by Caleb Tennis, thanks Caleb!
|
|
||||||
|
|
||||||
|
|
||||||
#. 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. (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)::
|
|
||||||
|
|
||||||
ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ st -A https://ec2-184-72-156-130.compute-1.amazonaws.com:11000/v1.0 -U a3:b3 -K c3 stat
|
|
||||||
Account: 06228ccf-6d0a-4395-889e-e971e8de8781
|
|
||||||
Containers: 0
|
|
||||||
Objects: 0
|
|
||||||
Bytes: 0
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
The Swift Tool `st` can be copied from Swift sources to most any
|
|
||||||
machine with Python installed. You can grab it from
|
|
||||||
http://bazaar.launchpad.net/%7Ehudson-openstack/swift/trunk/annotate/head%3A/bin/st
|
|
||||||
if you don't have the Swift code handy.
|
|
||||||
|
|
||||||
#. Download and extract the Cyberduck sources (3.5.1 as of this writing). They
|
|
||||||
should be available at http://trac.cyberduck.ch/
|
|
||||||
|
|
||||||
#. Edit the Cyberduck source. Look for lib/cloudfiles.properties, and edit
|
|
||||||
this file. Change auth_url to your public auth URL (note the https)::
|
|
||||||
|
|
||||||
auth_url=https://ec2-184-72-156-130.compute-1.amazonaws.com:11000/v1.0
|
|
||||||
|
|
||||||
#. Edit source/ch/cyberduck/core/Protocol.java. Look for the line saying
|
|
||||||
"storage.clouddrive.com". Just above that, change::
|
|
||||||
|
|
||||||
public boolean isHostnameConfigurable() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#. In the root directory, run "make" to rebuild Cyberduck. When done, type:
|
|
||||||
`open build/Release/Cyberduck.app/` to start the program.
|
|
||||||
|
|
||||||
#. Go to "Open Connection", select Rackspace Cloud Files, and connect.
|
|
||||||
|
|
||||||
.. image:: images/howto_cyberduck_config.png
|
|
||||||
|
|
||||||
#. If you get SSL errors, make sure your auth and proxy server are both setup
|
|
||||||
for SSL. If you get certificate errors (specifically, 'unable to find valid
|
|
||||||
certification path to requested target'), you are using a self signed
|
|
||||||
certificate, you need to perform a few more steps:
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
For some folks, just telling the OS to trust the cert works fine, for
|
|
||||||
others use the following steps.
|
|
||||||
|
|
||||||
#. As outlined here: http://blogs.sun.com/andreas/entry/no_more_unable_to_find,
|
|
||||||
download http://blogs.sun.com/andreas/resource/InstallCert.java, run "javac
|
|
||||||
InstallCert.java" to compile it, then run "java InstallCert
|
|
||||||
https://your-auth-server-url:8080". This script will pull down that
|
|
||||||
certificate and put it into a Java cert store, in your local directory. The
|
|
||||||
file is jssecacerts.
|
|
||||||
|
|
||||||
#. You need to move that file to $JAVA_HOME/jre/lib/security, so your java run
|
|
||||||
time picks it up.
|
|
||||||
|
|
||||||
#. Restart Cyberduck, and it should now allow you to use that certificate
|
|
||||||
without an error.
|
|
||||||
|
|
||||||
|
|
||||||
---------------------------------------
|
|
||||||
Installing Swift For Use With Cyberduck
|
|
||||||
---------------------------------------
|
|
||||||
|
|
||||||
#. Both the proxy and auth servers will ultimately need to be running with
|
|
||||||
SSL. You will need a key and certificate to do this, self signed is ok (but
|
|
||||||
a little more work getting Cyberduck to accept it). Put these in
|
|
||||||
/etc/swift/cert.crt and /etc/swift/cert.key.
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Creating a self-signed cert can usually be done with::
|
|
||||||
|
|
||||||
cd /etc/swift
|
|
||||||
openssl req -new -x509 -nodes -out cert.crt -keyout cert.key
|
|
||||||
|
|
||||||
#. Example proxy-server config::
|
|
||||||
|
|
||||||
[DEFAULT]
|
|
||||||
cert_file = /etc/swift/cert.crt
|
|
||||||
key_file = /etc/swift/cert.key
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = healthcheck cache auth proxy-server
|
|
||||||
|
|
||||||
[app:proxy-server]
|
|
||||||
use = egg:swift#proxy
|
|
||||||
|
|
||||||
[filter:auth]
|
|
||||||
use = egg:swift#auth
|
|
||||||
ssl = true
|
|
||||||
|
|
||||||
[filter:healthcheck]
|
|
||||||
use = egg:swift#healthcheck
|
|
||||||
|
|
||||||
[filter:cache]
|
|
||||||
use = egg:swift#memcache
|
|
||||||
|
|
||||||
#. Example auth-server config::
|
|
||||||
|
|
||||||
[DEFAULT]
|
|
||||||
cert_file = /etc/swift/cert.crt
|
|
||||||
key_file = /etc/swift/cert.key
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = auth-server
|
|
||||||
|
|
||||||
[app:auth-server]
|
|
||||||
use = egg:swift#auth
|
|
||||||
super_admin_key = devauth
|
|
||||||
default_cluster_url = https://ec2-184-72-156-130.compute-1.amazonaws.com:8080/v1
|
|
||||||
|
|
||||||
#. Use swift-auth-add-user to create a new account and admin user::
|
|
||||||
|
|
||||||
ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ swift-auth-add-user -K devauth -a a3 b3 c3
|
|
||||||
https://ec2-184-72-156-130.compute-1.amazonaws.com:8080/v1/06228ccf-6d0a-4395-889e-e971e8de8781
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
It's important that the URL that is given back to you be accessible
|
|
||||||
publicly. This URL is tied to this account, and will be served
|
|
||||||
back to Cyberduck after authorization. If this URL gives back
|
|
||||||
something like: http://127.0.0.1/v1/... this won't work, because
|
|
||||||
Cyberduck will attempt to connect to 127.0.0.1.
|
|
||||||
|
|
||||||
This URL is specified in the auth-server config's
|
|
||||||
default_cluster_url. However, once you have created an
|
|
||||||
account/user, this URL is fixed and won't change even if you change
|
|
||||||
that configuration item. You will have to use sqlite to manually
|
|
||||||
edit the auth.db in order to change it (limitation of using the
|
|
||||||
development auth server, but perhaps someone will patch in this
|
|
||||||
ability someday).
|
|
||||||
|
|
||||||
#. Verify you can connect using the standard Swift Tool `st`::
|
|
||||||
|
|
||||||
ubuntu@domU-12-31-39-03-CD-06:/home/swift/swift/bin$ st -A https://127.0.0.1:11000/v1.0 -U a3:b3 -K c3 stat
|
|
||||||
Account: 06228ccf-6d0a-4395-889e-e971e8de8781
|
|
||||||
Containers: 0
|
|
||||||
Objects: 0
|
|
||||||
Bytes: 0
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Please let me know if you find any changes that need to be made: ctennis on
|
|
||||||
IRC
|
|
@ -13,8 +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; can also run Swauth
|
- *Proxy node* - node that runs Proxy services; also runs Swauth
|
||||||
- *Auth node* - node that runs the Auth service; only required for DevAuth
|
|
||||||
- *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,15 +22,9 @@ This document shows a cluster using the following types of nodes:
|
|||||||
- one Proxy node
|
- one Proxy node
|
||||||
|
|
||||||
- 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. For Swauth, the proxy server will also contain
|
appropriate Storage nodes. The proxy server will also contain
|
||||||
the Swauth service as WSGI middleware.
|
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. This is only required for DevAuth.
|
|
||||||
|
|
||||||
- five Storage nodes
|
- five Storage nodes
|
||||||
|
|
||||||
- Runs the swift-account-server, swift-container-server, and
|
- Runs the swift-account-server, swift-container-server, and
|
||||||
@ -92,7 +85,6 @@ General OS configuration and partitioning for each node
|
|||||||
|
|
||||||
export STORAGE_LOCAL_NET_IP=10.1.2.3
|
export STORAGE_LOCAL_NET_IP=10.1.2.3
|
||||||
export PROXY_LOCAL_NET_IP=10.1.2.4
|
export PROXY_LOCAL_NET_IP=10.1.2.4
|
||||||
export AUTH_LOCAL_NET_IP=10.1.2.5
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
The random string of text in /etc/swift/swift.conf is
|
The random string of text in /etc/swift/swift.conf is
|
||||||
@ -136,26 +128,14 @@ Configure the Proxy node
|
|||||||
bind_port = 8080
|
bind_port = 8080
|
||||||
workers = 8
|
workers = 8
|
||||||
user = swift
|
user = swift
|
||||||
# For non-local Auth server
|
|
||||||
ip = $AUTH_LOCAL_NET_IP
|
|
||||||
|
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
# For DevAuth:
|
pipeline = healthcheck cache swauth proxy-server
|
||||||
pipeline = healthcheck cache auth proxy-server
|
|
||||||
# For Swauth:
|
|
||||||
# pipeline = healthcheck cache swauth proxy-server
|
|
||||||
|
|
||||||
[app:proxy-server]
|
[app:proxy-server]
|
||||||
use = egg:swift#proxy
|
use = egg:swift#proxy
|
||||||
allow_account_management = true
|
allow_account_management = true
|
||||||
|
|
||||||
# Only needed for DevAuth
|
|
||||||
[filter:auth]
|
|
||||||
use = egg:swift#auth
|
|
||||||
ssl = true
|
|
||||||
|
|
||||||
# Only needed for Swauth
|
|
||||||
[filter:swauth]
|
[filter:swauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#swauth
|
||||||
default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
|
default_swift_cluster = local#https://$PROXY_LOCAL_NET_IP:8080/v1
|
||||||
@ -228,42 +208,6 @@ Configure the Proxy node
|
|||||||
swift-init proxy start
|
swift-init proxy start
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
#. Install swift-auth service::
|
|
||||||
|
|
||||||
apt-get install swift-auth
|
|
||||||
|
|
||||||
#. Create /etc/swift/auth-server.conf::
|
|
||||||
|
|
||||||
cat >/etc/swift/auth-server.conf <<EOF
|
|
||||||
[DEFAULT]
|
|
||||||
cert_file = /etc/swift/cert.crt
|
|
||||||
key_file = /etc/swift/cert.key
|
|
||||||
user = swift
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = auth-server
|
|
||||||
|
|
||||||
[app:auth-server]
|
|
||||||
use = egg:swift#auth
|
|
||||||
default_cluster_url = https://<PROXY_HOSTNAME>:8080/v1
|
|
||||||
# Highly recommended to change this key to something else!
|
|
||||||
super_admin_key = devauth
|
|
||||||
EOF
|
|
||||||
|
|
||||||
#. Start Auth services::
|
|
||||||
|
|
||||||
swift-init auth start
|
|
||||||
chown swift:swift /etc/swift/auth.db
|
|
||||||
swift-init auth restart # 1.1.0 workaround because swift creates auth.db owned as root
|
|
||||||
|
|
||||||
Configure the Storage nodes
|
Configure the Storage nodes
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@ -418,26 +362,21 @@ replicator, updater, or auditor.::
|
|||||||
Create Swift admin account and test
|
Create Swift admin account and test
|
||||||
-----------------------------------
|
-----------------------------------
|
||||||
|
|
||||||
You run these commands from the Auth node.
|
You run these commands from the Proxy 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,
|
#. Create a user with administrative privileges (account = system,
|
||||||
username = root, password = testpass). Make sure to replace
|
username = root, password = testpass). Make sure to replace
|
||||||
``devauth`` (or ``swauthkey``) with whatever super_admin key you assigned in
|
``swauthkey`` with whatever super_admin key you assigned in
|
||||||
the auth-server.conf file (or proxy-server.conf file in the case of Swauth)
|
the proxy-server.conf file
|
||||||
above. *Note: None of the values of
|
above. *Note: None of the values of
|
||||||
account, username, or password are special - they can be anything.*::
|
account, username, or password are special - they can be anything.*::
|
||||||
|
|
||||||
# For DevAuth:
|
swauth-prep -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey
|
||||||
swift-auth-add-user -K devauth -a system root testpass
|
swauth-add-user -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey -a system root testpass
|
||||||
# For Swauth:
|
|
||||||
swauth-add-user -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://<AUTH_HOSTNAME>:11000/v1.0
|
curl -k -v -H 'X-Storage-User: system:root' -H 'X-Storage-Pass: testpass' https://<PROXY_HOSTNAME>:8080/auth/v1.0
|
||||||
|
|
||||||
#. Check that you can HEAD the account::
|
#. Check that you can HEAD the account::
|
||||||
|
|
||||||
@ -445,32 +384,32 @@ You run these commands from the Auth node.
|
|||||||
|
|
||||||
#. Check that ``st`` works (at this point, expect zero containers, zero objects, and zero bytes)::
|
#. Check that ``st`` works (at this point, expect zero containers, zero objects, and zero bytes)::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass stat
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass stat
|
||||||
|
|
||||||
#. Use ``st`` to upload a few files named 'bigfile[1-2].tgz' to a container named 'myfiles'::
|
#. Use ``st`` to upload a few files named 'bigfile[1-2].tgz' to a container named 'myfiles'::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass upload myfiles bigfile1.tgz
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile1.tgz
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass upload myfiles bigfile2.tgz
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass upload myfiles bigfile2.tgz
|
||||||
|
|
||||||
#. Use ``st`` to download all files from the 'myfiles' container::
|
#. Use ``st`` to download all files from the 'myfiles' container::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass download myfiles
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass download myfiles
|
||||||
|
|
||||||
#. Use ``st`` to save a backup of your builder files to a container named 'builders'. Very important not to lose your builders!::
|
#. Use ``st`` to save a backup of your builder files to a container named 'builders'. Very important not to lose your builders!::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass upload builders /etc/swift/*.builder
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass upload builders /etc/swift/*.builder
|
||||||
|
|
||||||
#. Use ``st`` to list your containers::
|
#. Use ``st`` to list your containers::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass list
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass list
|
||||||
|
|
||||||
#. Use ``st`` to list the contents of your 'builders' container::
|
#. Use ``st`` to list the contents of your 'builders' container::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass list builders
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass list builders
|
||||||
|
|
||||||
#. Use ``st`` to download all files from the 'builders' container::
|
#. Use ``st`` to download all files from the 'builders' container::
|
||||||
|
|
||||||
st -A https://<AUTH_HOSTNAME>:11000/v1.0 -U system:root -K testpass download builders
|
st -A https://<PROXY_HOSTNAME>:8080/auth/v1.0 -U system:root -K testpass download builders
|
||||||
|
|
||||||
.. _add-proxy-server:
|
.. _add-proxy-server:
|
||||||
|
|
||||||
@ -489,31 +428,25 @@ 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/auth-server.conf (for DevAuth) or in /etc/swift/proxy-server.conf (for Swauth)::
|
#. 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::
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# For Swauth, in /etc/swift/proxy-server.conf
|
|
||||||
[filter:swauth]
|
[filter:swauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#swauth
|
||||||
default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
|
default_swift_cluster = local#http://<LOAD_BALANCER_HOSTNAME>/v1
|
||||||
# Highly recommended to change this key to something else!
|
# Highly recommended to change this key to something else!
|
||||||
super_admin_key = swauthkey
|
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.
|
#. 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::
|
||||||
|
|
||||||
For Swauth, you can change a service URL with::
|
First retreve what the URL was::
|
||||||
|
|
||||||
swauth-set-account-service -K swauthkey <account> storage local <new_url_for_the_account>
|
swauth-list -A https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey <account>
|
||||||
|
|
||||||
You can obtain old service URLs with::
|
And then update it with::
|
||||||
|
|
||||||
swauth-list -K swauthkey <account>
|
swauth-set-account-service -A https://<PROXY_HOSTNAME>: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.
|
||||||
|
|
||||||
@ -522,15 +455,16 @@ See :ref:`config-proxy` for the initial setup, and then follow these additional
|
|||||||
Additional Cleanup Script for Swauth
|
Additional Cleanup Script for Swauth
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
If you decide to use Swauth, you'll want to install a cronjob to clean up any
|
With Swauth, you'll want to install a cronjob to clean up any
|
||||||
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
orphaned expired tokens. These orphaned tokens can occur when a "stampede"
|
||||||
occurs where a single user authenticates several times concurrently. Generally,
|
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
|
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).
|
up once a "token life" period (default: 1 day or 86400 seconds).
|
||||||
|
|
||||||
This should be as simple as adding `swauth-cleanup-tokens -K swauthkey >
|
This should be as simple as adding `swauth-cleanup-tokens -A
|
||||||
/dev/null` to a crontab entry on one of the proxies that is running Swauth; but
|
https://<PROXY_HOSTNAME>:8080/auth/ -K swauthkey > /dev/null` to a crontab
|
||||||
run `swauth-cleanup-tokens` with no arguments for detailed help on the options
|
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.
|
available.
|
||||||
|
|
||||||
Troubleshooting Notes
|
Troubleshooting Notes
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB |
@ -67,14 +67,6 @@ Administrator Documentation
|
|||||||
admin_guide
|
admin_guide
|
||||||
debian_package_guide
|
debian_package_guide
|
||||||
|
|
||||||
End User Guides
|
|
||||||
===============
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
howto_cyberduck
|
|
||||||
|
|
||||||
Source Documentation
|
Source Documentation
|
||||||
====================
|
====================
|
||||||
|
|
||||||
@ -87,7 +79,6 @@ Source Documentation
|
|||||||
container
|
container
|
||||||
db
|
db
|
||||||
object
|
object
|
||||||
auth
|
|
||||||
misc
|
misc
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,61 +2,57 @@
|
|||||||
The Auth System
|
The Auth System
|
||||||
===============
|
===============
|
||||||
|
|
||||||
--------------
|
|
||||||
Developer Auth
|
|
||||||
--------------
|
|
||||||
|
|
||||||
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
|
|
||||||
therefore a bit disjointed. The distilled points about it are:
|
|
||||||
|
|
||||||
* The authentication/authorization part is outside Swift itself
|
|
||||||
* The user of Swift passes in an auth token with each request
|
|
||||||
* Swift validates each token with the external auth system and caches the
|
|
||||||
result
|
|
||||||
* The token does not change from request to request, but does expire
|
|
||||||
|
|
||||||
The token can be passed into Swift using the X-Auth-Token or the
|
|
||||||
X-Storage-Token header. Both have the same format: just a simple string
|
|
||||||
representing the token. Some external systems use UUID tokens, some an MD5 hash
|
|
||||||
of something unique, some use "something else" but the salient point is that
|
|
||||||
the token is a string which can be sent as-is back to the auth system for
|
|
||||||
validation.
|
|
||||||
|
|
||||||
Swift will make calls to the external auth system, giving the auth token to be
|
|
||||||
validated. For a valid token, the auth system responds with an overall
|
|
||||||
expiration in seconds from now. Swift will cache the token up to the expiration
|
|
||||||
time. The included devauth also has the concept of admin and non-admin users
|
|
||||||
within an account. Admin users can do anything within the account. Non-admin
|
|
||||||
users can only perform operations per container based on the container's
|
|
||||||
X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
|
|
||||||
:mod:`swift.common.middleware.acl`
|
|
||||||
|
|
||||||
The user starts a session by sending a ReST request to the external auth system
|
|
||||||
to receive the auth token and a URL to the Swift system.
|
|
||||||
|
|
||||||
--------------
|
|
||||||
Extending Auth
|
|
||||||
--------------
|
|
||||||
|
|
||||||
Auth is written as wsgi middleware, so implementing your own auth is as easy
|
|
||||||
as writing new wsgi middleware, and plugging it in to the proxy server.
|
|
||||||
|
|
||||||
The current middleware is implemented in the DevAuthMiddleware class in
|
|
||||||
swift/common/middleware/auth.py, and should be a good starting place for
|
|
||||||
implementing your own auth.
|
|
||||||
|
|
||||||
Also, see :doc:`development_auth`.
|
|
||||||
|
|
||||||
|
|
||||||
------
|
------
|
||||||
Swauth
|
Swauth
|
||||||
------
|
------
|
||||||
|
|
||||||
The Swauth system is an optional DevAuth replacement included at
|
The auth system for Swift is loosely based on the auth system from the existing
|
||||||
swift/common/middleware/swauth.py; a scalable authentication and
|
Rackspace architecture -- actually from a few existing auth systems -- and is
|
||||||
authorization system that uses Swift itself as its backing store. This section
|
therefore a bit disjointed. The distilled points about it are:
|
||||||
will describe how it stores its data.
|
|
||||||
|
* The authentication/authorization part can be an external system or a
|
||||||
|
subsystem run within Swift as WSGI middleware
|
||||||
|
* The user of Swift passes in an auth token with each request
|
||||||
|
* Swift validates each token with the external auth system or auth subsystem
|
||||||
|
and caches the result
|
||||||
|
* The token does not change from request to request, but does expire
|
||||||
|
|
||||||
|
The token can be passed into Swift using the X-Auth-Token or the
|
||||||
|
X-Storage-Token header. Both have the same format: just a simple string
|
||||||
|
representing the token. Some auth systems use UUID tokens, some an MD5 hash of
|
||||||
|
something unique, some use "something else" but the salient point is that the
|
||||||
|
token is a string which can be sent as-is back to the auth system for
|
||||||
|
validation.
|
||||||
|
|
||||||
|
Swift will make calls to the auth system, giving the auth token to be
|
||||||
|
validated. For a valid token, the auth system responds with an overall
|
||||||
|
expiration in seconds from now. Swift will cache the token up to the expiration
|
||||||
|
time. The included Swauth also has the concept of admin and non-admin users
|
||||||
|
within an account. Admin users can do anything within the account. Non-admin
|
||||||
|
users can only perform operations per container based on the container's
|
||||||
|
X-Container-Read and X-Container-Write ACLs. For more information on ACLs, see
|
||||||
|
:mod:`swift.common.middleware.acl`
|
||||||
|
|
||||||
|
The user starts a session by sending a ReST request to the auth system to
|
||||||
|
receive the auth token and a URL to the Swift system.
|
||||||
|
|
||||||
|
--------------
|
||||||
|
Extending Auth
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Swauth is written as wsgi middleware, so implementing your own auth is as easy
|
||||||
|
as writing new wsgi middleware, and plugging it in to the proxy server.
|
||||||
|
|
||||||
|
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
|
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
|
own account information within. This Swift account is known as
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
# Only needed for DevAuth; Swauth is within the proxy-server.conf
|
|
||||||
[DEFAULT]
|
|
||||||
# bind_ip = 0.0.0.0
|
|
||||||
# bind_port = 11000
|
|
||||||
# workers = 1
|
|
||||||
# user = swift
|
|
||||||
# swift_dir = /etc/swift
|
|
||||||
# cert_file = Default is no cert; format is path like /etc/swift/auth.crt
|
|
||||||
# key_file = Default is no key; format is path like /etc/swift/auth.key
|
|
||||||
# You can specify default log routing here if you want:
|
|
||||||
# log_name = swift
|
|
||||||
# log_facility = LOG_LOCAL0
|
|
||||||
# log_level = INFO
|
|
||||||
|
|
||||||
[pipeline:main]
|
|
||||||
pipeline = auth-server
|
|
||||||
|
|
||||||
[app:auth-server]
|
|
||||||
use = egg:swift#auth
|
|
||||||
# Highly recommended to change this.
|
|
||||||
super_admin_key = devauth
|
|
||||||
# You can override the default log routing for this app here:
|
|
||||||
# set log_name = proxy-server
|
|
||||||
# set log_facility = LOG_LOCAL0
|
|
||||||
# set log_level = INFO
|
|
||||||
# set log_headers = False
|
|
||||||
# reseller_prefix = AUTH
|
|
||||||
# default_cluster_url = http://127.0.0.1:8080/v1
|
|
||||||
# token_life = 86400
|
|
||||||
# node_timeout = 10
|
|
@ -13,10 +13,7 @@
|
|||||||
# log_level = INFO
|
# log_level = INFO
|
||||||
|
|
||||||
[pipeline:main]
|
[pipeline:main]
|
||||||
# For DevAuth:
|
pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
|
||||||
pipeline = catch_errors healthcheck cache ratelimit auth proxy-server
|
|
||||||
# For Swauth:
|
|
||||||
# pipeline = catch_errors healthcheck cache ratelimit swauth proxy-server
|
|
||||||
|
|
||||||
[app:proxy-server]
|
[app:proxy-server]
|
||||||
use = egg:swift#proxy
|
use = egg:swift#proxy
|
||||||
@ -44,27 +41,6 @@ use = egg:swift#proxy
|
|||||||
# 'false' no one, even authorized, can.
|
# 'false' no one, even authorized, can.
|
||||||
# allow_account_management = false
|
# allow_account_management = false
|
||||||
|
|
||||||
# Only needed for DevAuth
|
|
||||||
[filter:auth]
|
|
||||||
use = egg:swift#auth
|
|
||||||
# You can override the default log routing for this filter here:
|
|
||||||
# set log_name = auth-server
|
|
||||||
# set log_facility = LOG_LOCAL0
|
|
||||||
# set log_level = INFO
|
|
||||||
# set log_headers = False
|
|
||||||
# The reseller prefix will verify a token begins with this prefix before even
|
|
||||||
# attempting to validate it with the external authentication server. 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
|
|
||||||
# ip = 127.0.0.1
|
|
||||||
# port = 11000
|
|
||||||
# ssl = false
|
|
||||||
# prefix = /
|
|
||||||
# node_timeout = 10
|
|
||||||
|
|
||||||
# Only needed for Swauth
|
|
||||||
[filter:swauth]
|
[filter:swauth]
|
||||||
use = egg:swift#swauth
|
use = egg:swift#swauth
|
||||||
# You can override the default log routing for this filter here:
|
# You can override the default log routing for this filter here:
|
||||||
@ -97,7 +73,7 @@ super_admin_key = swauthkey
|
|||||||
[filter:healthcheck]
|
[filter:healthcheck]
|
||||||
use = egg:swift#healthcheck
|
use = egg:swift#healthcheck
|
||||||
# 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 = healthcheck
|
||||||
# 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
|
||||||
@ -105,7 +81,7 @@ use = egg:swift#healthcheck
|
|||||||
[filter:cache]
|
[filter:cache]
|
||||||
use = egg:swift#memcache
|
use = egg:swift#memcache
|
||||||
# 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 = cache
|
||||||
# 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
|
||||||
@ -116,7 +92,7 @@ use = egg:swift#memcache
|
|||||||
[filter:ratelimit]
|
[filter:ratelimit]
|
||||||
use = egg:swift#ratelimit
|
use = egg:swift#ratelimit
|
||||||
# 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 = ratelimit
|
||||||
# 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
|
||||||
@ -148,7 +124,7 @@ use = egg:swift#ratelimit
|
|||||||
[filter:domain_remap]
|
[filter:domain_remap]
|
||||||
use = egg:swift#domain_remap
|
use = egg:swift#domain_remap
|
||||||
# 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 = domain_remap
|
||||||
# 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
|
||||||
@ -159,7 +135,7 @@ use = egg:swift#domain_remap
|
|||||||
[filter:catch_errors]
|
[filter:catch_errors]
|
||||||
use = egg:swift#catch_errors
|
use = egg:swift#catch_errors
|
||||||
# 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 = catch_errors
|
||||||
# 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
|
||||||
@ -168,7 +144,7 @@ use = egg:swift#catch_errors
|
|||||||
# Note: this middleware requires python-dnspython
|
# Note: this middleware requires python-dnspython
|
||||||
use = egg:swift#cname_lookup
|
use = egg:swift#cname_lookup
|
||||||
# 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 = cname_lookup
|
||||||
# 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
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
[stats]
|
[stats]
|
||||||
# For DevAuth:
|
auth_url = http://saio:8080/auth/v1.0
|
||||||
auth_url = http://saio:11000/auth
|
|
||||||
# For Swauth:
|
|
||||||
# auth_url = http://saio:8080/auth/v1.0
|
|
||||||
auth_user = test:tester
|
auth_user = test:tester
|
||||||
auth_key = testing
|
auth_key = testing
|
||||||
# swift_dir = /etc/swift
|
# swift_dir = /etc/swift
|
||||||
|
5
setup.py
5
setup.py
@ -79,9 +79,6 @@ setup(
|
|||||||
'bin/st', 'bin/swift-account-auditor',
|
'bin/st', 'bin/swift-account-auditor',
|
||||||
'bin/swift-account-audit', 'bin/swift-account-reaper',
|
'bin/swift-account-audit', 'bin/swift-account-reaper',
|
||||||
'bin/swift-account-replicator', 'bin/swift-account-server',
|
'bin/swift-account-replicator', 'bin/swift-account-server',
|
||||||
'bin/swift-auth-add-user',
|
|
||||||
'bin/swift-auth-recreate-accounts', 'bin/swift-auth-server',
|
|
||||||
'bin/swift-auth-update-reseller-prefixes',
|
|
||||||
'bin/swift-container-auditor',
|
'bin/swift-container-auditor',
|
||||||
'bin/swift-container-replicator',
|
'bin/swift-container-replicator',
|
||||||
'bin/swift-container-server', 'bin/swift-container-updater',
|
'bin/swift-container-server', 'bin/swift-container-updater',
|
||||||
@ -108,10 +105,8 @@ setup(
|
|||||||
'object=swift.obj.server:app_factory',
|
'object=swift.obj.server:app_factory',
|
||||||
'container=swift.container.server:app_factory',
|
'container=swift.container.server:app_factory',
|
||||||
'account=swift.account.server:app_factory',
|
'account=swift.account.server:app_factory',
|
||||||
'auth=swift.auth.server:app_factory',
|
|
||||||
],
|
],
|
||||||
'paste.filter_factory': [
|
'paste.filter_factory': [
|
||||||
'auth=swift.common.middleware.auth:filter_factory',
|
|
||||||
'swauth=swift.common.middleware.swauth: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',
|
||||||
|
@ -1,693 +0,0 @@
|
|||||||
# Copyright (c) 2010-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 __future__ import with_statement
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from time import gmtime, strftime, time
|
|
||||||
from urllib import unquote, quote
|
|
||||||
from uuid import uuid4
|
|
||||||
from hashlib import md5, sha1
|
|
||||||
import hmac
|
|
||||||
import base64
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
from webob import Request, Response
|
|
||||||
from webob.exc import HTTPBadRequest, HTTPConflict, HTTPForbidden, \
|
|
||||||
HTTPNoContent, HTTPUnauthorized, HTTPServiceUnavailable, HTTPNotFound
|
|
||||||
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
from swift.common.db import get_db_connection
|
|
||||||
from swift.common.utils import get_logger, split_path, urlparse
|
|
||||||
|
|
||||||
|
|
||||||
class AuthController(object):
|
|
||||||
"""
|
|
||||||
Sample implementation of an authorization server for development work. This
|
|
||||||
server only implements the basic functionality and isn't written for high
|
|
||||||
availability or to scale to thousands (or even hundreds) of requests per
|
|
||||||
second. It is mainly for use by developers working on the rest of the
|
|
||||||
system.
|
|
||||||
|
|
||||||
The design of the auth system was restricted by a couple of existing
|
|
||||||
systems.
|
|
||||||
|
|
||||||
This implementation stores an account name, user name, and password (in
|
|
||||||
plain text!) as well as a corresponding Swift cluster url and account hash.
|
|
||||||
One existing auth system used account, user, and password whereas another
|
|
||||||
used just account and an "API key". Here, we support both systems with
|
|
||||||
their various, sometimes colliding headers.
|
|
||||||
|
|
||||||
The most common use case is by the end user:
|
|
||||||
|
|
||||||
* The user makes a ReST call to the auth server requesting a token and url
|
|
||||||
to use to access the Swift cluster.
|
|
||||||
* The auth system validates the user info and returns a token and url for
|
|
||||||
the user to use with the Swift cluster.
|
|
||||||
* The user makes a ReST call to the Swift cluster using the url given with
|
|
||||||
the token as the X-Auth-Token header.
|
|
||||||
* The Swift cluster makes an ReST call to the auth server to validate the
|
|
||||||
token, caching the result for future requests up to the expiration the
|
|
||||||
auth server returns.
|
|
||||||
* The auth server validates the token given and returns the expiration for
|
|
||||||
the token.
|
|
||||||
* The Swift cluster completes the user's request.
|
|
||||||
|
|
||||||
Another use case is creating a new user:
|
|
||||||
|
|
||||||
* The developer makes a ReST call to create a new user.
|
|
||||||
* If the account for the user does not yet exist, the auth server makes
|
|
||||||
a ReST call to the Swift cluster to create a new account on its end.
|
|
||||||
* The auth server records the information in its database.
|
|
||||||
|
|
||||||
A last use case is recreating existing accounts; this is really only useful
|
|
||||||
on a development system when the drives are reformatted quite often but
|
|
||||||
the auth server's database is retained:
|
|
||||||
|
|
||||||
* A developer makes an ReST call to have the existing accounts recreated.
|
|
||||||
* For each account in its database, the auth server makes a ReST call to
|
|
||||||
the Swift cluster to create the specific account on its end.
|
|
||||||
|
|
||||||
:param conf: The [auth-server] dictionary of the auth server configuration
|
|
||||||
file
|
|
||||||
|
|
||||||
See the etc/auth-server.conf-sample for information on the possible
|
|
||||||
configuration parameters.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, conf):
|
|
||||||
self.logger = get_logger(conf, log_route='auth-server')
|
|
||||||
self.super_admin_key = conf.get('super_admin_key')
|
|
||||||
if not self.super_admin_key:
|
|
||||||
msg = _('No super_admin_key set in conf file! Exiting.')
|
|
||||||
try:
|
|
||||||
self.logger.critical(msg)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
raise ValueError(msg)
|
|
||||||
self.swift_dir = conf.get('swift_dir', '/etc/swift')
|
|
||||||
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
|
|
||||||
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
|
|
||||||
self.reseller_prefix += '_'
|
|
||||||
self.default_cluster_url = conf.get('default_cluster_url',
|
|
||||||
'http://127.0.0.1:8080/v1').rstrip('/')
|
|
||||||
self.token_life = int(conf.get('token_life', 86400))
|
|
||||||
self.log_headers = conf.get('log_headers') == 'True'
|
|
||||||
self.db_file = os.path.join(self.swift_dir, 'auth.db')
|
|
||||||
self.conn = get_db_connection(self.db_file, okay_to_create=True)
|
|
||||||
try:
|
|
||||||
self.conn.execute('SELECT admin FROM account LIMIT 1')
|
|
||||||
except sqlite3.OperationalError, err:
|
|
||||||
if str(err) == 'no such column: admin':
|
|
||||||
self.conn.execute("ALTER TABLE account ADD COLUMN admin TEXT")
|
|
||||||
self.conn.execute("UPDATE account SET admin = 't'")
|
|
||||||
try:
|
|
||||||
self.conn.execute('SELECT reseller_admin FROM account LIMIT 1')
|
|
||||||
except sqlite3.OperationalError, err:
|
|
||||||
if str(err) == 'no such column: reseller_admin':
|
|
||||||
self.conn.execute(
|
|
||||||
"ALTER TABLE account ADD COLUMN reseller_admin TEXT")
|
|
||||||
self.conn.execute('''CREATE TABLE IF NOT EXISTS account (
|
|
||||||
account TEXT, url TEXT, cfaccount TEXT,
|
|
||||||
user TEXT, password TEXT, admin TEXT,
|
|
||||||
reseller_admin TEXT)''')
|
|
||||||
self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
|
|
||||||
ON account (account)''')
|
|
||||||
try:
|
|
||||||
self.conn.execute('SELECT user FROM token LIMIT 1')
|
|
||||||
except sqlite3.OperationalError, err:
|
|
||||||
if str(err) == 'no such column: user':
|
|
||||||
self.conn.execute('DROP INDEX IF EXISTS ix_token_created')
|
|
||||||
self.conn.execute('DROP INDEX IF EXISTS ix_token_cfaccount')
|
|
||||||
self.conn.execute('DROP TABLE IF EXISTS token')
|
|
||||||
self.conn.execute('''CREATE TABLE IF NOT EXISTS token (
|
|
||||||
token TEXT, created FLOAT,
|
|
||||||
account TEXT, user TEXT, cfaccount TEXT)''')
|
|
||||||
self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_token
|
|
||||||
ON token (token)''')
|
|
||||||
self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
|
|
||||||
ON token (created)''')
|
|
||||||
self.conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_account
|
|
||||||
ON token (account)''')
|
|
||||||
self.conn.commit()
|
|
||||||
for row in self.conn.execute('SELECT cfaccount FROM account'):
|
|
||||||
if not row[0].startswith(self.reseller_prefix):
|
|
||||||
previous_prefix = ''
|
|
||||||
if '_' in row[0]:
|
|
||||||
previous_prefix = row[0].split('_', 1)[0]
|
|
||||||
msg = (_('''
|
|
||||||
THERE ARE ACCOUNTS IN YOUR auth.db THAT DO NOT BEGIN WITH YOUR NEW RESELLER
|
|
||||||
PREFIX OF "%(reseller)s".
|
|
||||||
YOU HAVE A FEW OPTIONS:
|
|
||||||
1. RUN "swift-auth-update-reseller-prefixes %(db_file)s %(reseller)s",
|
|
||||||
"swift-init auth-server restart", AND
|
|
||||||
"swift-auth-recreate-accounts -K ..." TO CREATE FRESH ACCOUNTS.
|
|
||||||
OR
|
|
||||||
2. REMOVE %(db_file)s, RUN "swift-init auth-server restart", AND RUN
|
|
||||||
"swift-auth-add-user ..." TO CREATE BRAND NEW ACCOUNTS THAT WAY.
|
|
||||||
OR
|
|
||||||
3. ADD "reseller_prefix = %(previous)s" (WITHOUT THE QUOTES) TO YOUR
|
|
||||||
proxy-server.conf IN THE [filter:auth] SECTION AND TO YOUR
|
|
||||||
auth-server.conf IN THE [app:auth-server] SECTION AND RUN
|
|
||||||
"swift-init proxy-server restart" AND "swift-init auth-server restart"
|
|
||||||
TO REVERT BACK TO YOUR PREVIOUS RESELLER PREFIX.
|
|
||||||
|
|
||||||
%(note)s
|
|
||||||
''') % {'reseller': self.reseller_prefix.rstrip('_'),
|
|
||||||
'db_file': self.db_file,
|
|
||||||
'previous': previous_prefix,
|
|
||||||
'note': previous_prefix and ' ' or _('''
|
|
||||||
SINCE YOUR PREVIOUS RESELLER PREFIX WAS AN EMPTY STRING, IT IS NOT
|
|
||||||
RECOMMENDED TO PERFORM OPTION 3 AS THAT WOULD MAKE SUPPORTING MULTIPLE
|
|
||||||
RESELLERS MORE DIFFICULT.
|
|
||||||
''').strip()}).strip()
|
|
||||||
self.logger.critical(_('CRITICAL: ') + ' '.join(msg.split()))
|
|
||||||
raise Exception('\n' + msg)
|
|
||||||
|
|
||||||
def add_storage_account(self, account_name=''):
|
|
||||||
"""
|
|
||||||
Creates an account within the Swift cluster by making a ReST call.
|
|
||||||
|
|
||||||
:param account_name: The desired name for the account; if omitted a
|
|
||||||
UUID4 will be used.
|
|
||||||
:returns: False upon failure, otherwise the name of the account
|
|
||||||
within the Swift cluster.
|
|
||||||
"""
|
|
||||||
orig_account_name = account_name
|
|
||||||
if not account_name:
|
|
||||||
account_name = '%s%s' % (self.reseller_prefix, uuid4().hex)
|
|
||||||
url = '%s/%s' % (self.default_cluster_url, account_name)
|
|
||||||
parsed = urlparse(url)
|
|
||||||
# Create a single use token.
|
|
||||||
token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
conn.execute('''
|
|
||||||
INSERT INTO token
|
|
||||||
(token, created, account, user, cfaccount) VALUES
|
|
||||||
(?, ?, '.super_admin', '.single_use', '.reseller_admin')''',
|
|
||||||
(token, time()))
|
|
||||||
conn.commit()
|
|
||||||
if parsed.port is None:
|
|
||||||
port = {'http': 80, 'https': 443}.get(parsed.scheme, 80)
|
|
||||||
else:
|
|
||||||
port = parsed.port
|
|
||||||
conn = http_connect(parsed.hostname, port, 'PUT', parsed.path,
|
|
||||||
{'X-Auth-Token': token}, ssl=(parsed.scheme == 'https'))
|
|
||||||
resp = conn.getresponse()
|
|
||||||
resp.read()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
self.logger.error(_('ERROR attempting to create account %(url)s:' \
|
|
||||||
' %(status)s %(reason)s') %
|
|
||||||
{'url': url, 'status': resp.status, 'reason': resp.reason})
|
|
||||||
return False
|
|
||||||
return account_name
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def get_conn(self):
|
|
||||||
"""
|
|
||||||
Returns a DB API connection instance to the auth server's SQLite
|
|
||||||
database. This is a contextmanager call to be use with the 'with'
|
|
||||||
statement. It takes no parameters.
|
|
||||||
"""
|
|
||||||
if not self.conn:
|
|
||||||
# We go ahead and make another db connection even if this is a
|
|
||||||
# reentry call; just in case we had an error that caused self.conn
|
|
||||||
# to become None. Even if we make an extra conn, we'll only keep
|
|
||||||
# one after the 'with' block.
|
|
||||||
self.conn = get_db_connection(self.db_file)
|
|
||||||
conn = self.conn
|
|
||||||
self.conn = None
|
|
||||||
try:
|
|
||||||
yield conn
|
|
||||||
conn.rollback()
|
|
||||||
self.conn = conn
|
|
||||||
except Exception, err:
|
|
||||||
try:
|
|
||||||
conn.close()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
self.conn = get_db_connection(self.db_file)
|
|
||||||
raise err
|
|
||||||
|
|
||||||
def validate_s3_sign(self, request, token):
|
|
||||||
account, user, sign = \
|
|
||||||
request.headers['Authorization'].split(' ')[-1].split(':')
|
|
||||||
msg = base64.urlsafe_b64decode(unquote(token))
|
|
||||||
rv = False
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
row = conn.execute('''
|
|
||||||
SELECT password, cfaccount FROM account
|
|
||||||
WHERE account = ? AND user = ?''',
|
|
||||||
(account, user)).fetchone()
|
|
||||||
rv = (84000, account, user, row[1])
|
|
||||||
if rv:
|
|
||||||
s = base64.encodestring(hmac.new(row[0], msg,
|
|
||||||
sha1).digest()).strip()
|
|
||||||
self.logger.info("orig %s, calc %s" % (sign, s))
|
|
||||||
if sign != s:
|
|
||||||
rv = False
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def purge_old_tokens(self):
|
|
||||||
"""
|
|
||||||
Removes tokens that have expired from the auth server's database. This
|
|
||||||
is called by :func:`validate_token` and :func:`GET` to help keep the
|
|
||||||
database clean.
|
|
||||||
"""
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
conn.execute('DELETE FROM token WHERE created < ?',
|
|
||||||
(time() - self.token_life,))
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
def validate_token(self, token):
|
|
||||||
"""
|
|
||||||
Tests if the given token is a valid token
|
|
||||||
|
|
||||||
:param token: The token to validate
|
|
||||||
:returns: (TTL, account, user, cfaccount) if valid, False otherwise.
|
|
||||||
cfaccount will be None for users without admin access for the
|
|
||||||
account. cfaccount will be .reseller_admin for users with
|
|
||||||
full reseller admin rights.
|
|
||||||
"""
|
|
||||||
begin = time()
|
|
||||||
self.purge_old_tokens()
|
|
||||||
rv = False
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
row = conn.execute('''
|
|
||||||
SELECT created, account, user, cfaccount FROM token
|
|
||||||
WHERE token = ?''',
|
|
||||||
(token,)).fetchone()
|
|
||||||
if row is not None:
|
|
||||||
created = row[0]
|
|
||||||
if time() - created < self.token_life:
|
|
||||||
rv = (self.token_life - (time() - created), row[1], row[2],
|
|
||||||
row[3])
|
|
||||||
# Remove the token if it was expired or single use.
|
|
||||||
if not rv or rv[2] == '.single_use':
|
|
||||||
conn.execute('''
|
|
||||||
DELETE FROM token WHERE token = ?''', (token,))
|
|
||||||
conn.commit()
|
|
||||||
self.logger.info('validate_token(%s, _, _) = %s [%.02f]' %
|
|
||||||
(repr(token), repr(rv), time() - begin))
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def create_user(self, account, user, password, admin=False,
|
|
||||||
reseller_admin=False):
|
|
||||||
"""
|
|
||||||
Handles the create_user call for developers, used to request a user be
|
|
||||||
added in the auth server database. If the account does not yet exist,
|
|
||||||
it will be created on the Swift cluster and the details recorded in the
|
|
||||||
auth server database.
|
|
||||||
|
|
||||||
The url for the storage account is constructed now and stored
|
|
||||||
separately to support changing the configuration file's
|
|
||||||
default_cluster_url for directing new accounts to a different Swift
|
|
||||||
cluster while still supporting old accounts going to the Swift clusters
|
|
||||||
they were created on.
|
|
||||||
|
|
||||||
Currently, updating a user's information (password, admin access) must
|
|
||||||
be done by directly updating the sqlite database.
|
|
||||||
|
|
||||||
:param account: The name for the new account
|
|
||||||
:param user: The name for the new user
|
|
||||||
:param password: The password for the new account
|
|
||||||
:param admin: If true, the user will be granted full access to the
|
|
||||||
account; otherwise, another user will have to add the
|
|
||||||
user to the ACLs for containers to grant access.
|
|
||||||
:param reseller_admin: If true, the user will be granted full access to
|
|
||||||
all accounts within this reseller, including the
|
|
||||||
ability to create additional accounts.
|
|
||||||
|
|
||||||
:returns: False if the create fails, 'already exists' if the user
|
|
||||||
already exists, or storage url if successful
|
|
||||||
"""
|
|
||||||
begin = time()
|
|
||||||
if not all((account, user, password)):
|
|
||||||
return False
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
row = conn.execute(
|
|
||||||
'SELECT url FROM account WHERE account = ? AND user = ?',
|
|
||||||
(account, user)).fetchone()
|
|
||||||
if row:
|
|
||||||
self.logger.info(_('ALREADY EXISTS create_user(%(account)s, '
|
|
||||||
'%(user)s, _, %(admin)s, %(reseller_admin)s) '
|
|
||||||
'[%(elapsed).02f]') %
|
|
||||||
{'account': repr(account),
|
|
||||||
'user': repr(user),
|
|
||||||
'admin': repr(admin),
|
|
||||||
'reseller_admin': repr(reseller_admin),
|
|
||||||
'elapsed': time() - begin})
|
|
||||||
return 'already exists'
|
|
||||||
row = conn.execute(
|
|
||||||
'SELECT url, cfaccount FROM account WHERE account = ?',
|
|
||||||
(account,)).fetchone()
|
|
||||||
if row:
|
|
||||||
url = row[0]
|
|
||||||
account_hash = row[1]
|
|
||||||
else:
|
|
||||||
account_hash = self.add_storage_account()
|
|
||||||
if not account_hash:
|
|
||||||
self.logger.info(_('FAILED create_user(%(account)s, '
|
|
||||||
'%(user)s, _, %(admin)s, %(reseller_admin)s) '
|
|
||||||
'[%(elapsed).02f]') %
|
|
||||||
{'account': repr(account),
|
|
||||||
'user': repr(user),
|
|
||||||
'admin': repr(admin),
|
|
||||||
'reseller_admin': repr(reseller_admin),
|
|
||||||
'elapsed': time() - begin})
|
|
||||||
return False
|
|
||||||
url = self.default_cluster_url.rstrip('/') + '/' + account_hash
|
|
||||||
conn.execute('''INSERT INTO account
|
|
||||||
(account, url, cfaccount, user, password, admin,
|
|
||||||
reseller_admin)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)''',
|
|
||||||
(account, url, account_hash, user, password,
|
|
||||||
admin and 't' or '', reseller_admin and 't' or ''))
|
|
||||||
conn.commit()
|
|
||||||
self.logger.info(_('SUCCESS create_user(%(account)s, %(user)s, _, '
|
|
||||||
'%(admin)s, %(reseller_admin)s) = %(url)s [%(elapsed).02f]') %
|
|
||||||
{'account': repr(account), 'user': repr(user),
|
|
||||||
'admin': repr(admin), 'reseller_admin': repr(reseller_admin),
|
|
||||||
'url': repr(url), 'elapsed': time() - begin})
|
|
||||||
return url
|
|
||||||
|
|
||||||
def recreate_accounts(self):
|
|
||||||
"""
|
|
||||||
Recreates the accounts from the existing auth database in the Swift
|
|
||||||
cluster. This is useful on a development system when the drives are
|
|
||||||
reformatted quite often but the auth server's database is retained.
|
|
||||||
|
|
||||||
:returns: A string indicating accounts and failures
|
|
||||||
"""
|
|
||||||
begin = time()
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
account_hashes = [r[0] for r in conn.execute(
|
|
||||||
'SELECT distinct(cfaccount) FROM account').fetchall()]
|
|
||||||
failures = []
|
|
||||||
for i, account_hash in enumerate(account_hashes):
|
|
||||||
if not self.add_storage_account(account_hash):
|
|
||||||
failures.append(account_hash)
|
|
||||||
rv = '%d accounts, failures %s' % (len(account_hashes), repr(failures))
|
|
||||||
self.logger.info('recreate_accounts(_, _) = %s [%.02f]' %
|
|
||||||
(rv, time() - begin))
|
|
||||||
return rv
|
|
||||||
|
|
||||||
def is_account_admin(self, request, for_account):
|
|
||||||
"""
|
|
||||||
Returns True if the request represents coming from .super_admin, a
|
|
||||||
.reseller_admin, or an admin for the account specified.
|
|
||||||
"""
|
|
||||||
if request.headers.get('X-Auth-Admin-User') == '.super_admin' and \
|
|
||||||
request.headers.get('X-Auth-Admin-Key') == self.super_admin_key:
|
|
||||||
return True
|
|
||||||
try:
|
|
||||||
account, user = \
|
|
||||||
request.headers.get('X-Auth-Admin-User').split(':', 1)
|
|
||||||
except (AttributeError, ValueError):
|
|
||||||
return False
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
row = conn.execute('''
|
|
||||||
SELECT reseller_admin, admin FROM account
|
|
||||||
WHERE account = ? AND user = ? AND password = ?''',
|
|
||||||
(account, user,
|
|
||||||
request.headers.get('X-Auth-Admin-Key'))).fetchone()
|
|
||||||
if row:
|
|
||||||
if row[0] == 't':
|
|
||||||
return True
|
|
||||||
if row[1] == 't' and account == for_account:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def handle_token(self, request):
|
|
||||||
"""
|
|
||||||
Handles ReST requests from Swift to validate tokens
|
|
||||||
|
|
||||||
Valid URL paths:
|
|
||||||
* GET /token/<token>
|
|
||||||
|
|
||||||
If the HTTP request returns with a 204, then the token is valid, the
|
|
||||||
TTL of the token will be available in the X-Auth-Ttl header, and a
|
|
||||||
comma separated list of the "groups" the user belongs to will be in the
|
|
||||||
X-Auth-Groups header.
|
|
||||||
|
|
||||||
:param request: webob.Request object
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
_junk, token = split_path(request.path, minsegs=2)
|
|
||||||
except ValueError:
|
|
||||||
return HTTPBadRequest()
|
|
||||||
# Retrieves (TTL, account, user, cfaccount) if valid, False otherwise
|
|
||||||
headers = {}
|
|
||||||
if 'Authorization' in request.headers:
|
|
||||||
validation = self.validate_s3_sign(request, token)
|
|
||||||
if validation:
|
|
||||||
headers['X-Auth-Account-Suffix'] = validation[3]
|
|
||||||
else:
|
|
||||||
validation = self.validate_token(token)
|
|
||||||
if not validation:
|
|
||||||
return HTTPNotFound()
|
|
||||||
groups = ['%s:%s' % (validation[1], validation[2]), validation[1]]
|
|
||||||
if validation[3]:
|
|
||||||
# admin access to a cfaccount or ".reseller_admin" to access to all
|
|
||||||
# accounts, including creating new ones.
|
|
||||||
groups.append(validation[3])
|
|
||||||
headers['X-Auth-TTL'] = validation[0]
|
|
||||||
headers['X-Auth-Groups'] = ','.join(groups)
|
|
||||||
return HTTPNoContent(headers=headers)
|
|
||||||
|
|
||||||
def handle_add_user(self, request):
|
|
||||||
"""
|
|
||||||
Handles Rest requests from developers to have a user added. If the
|
|
||||||
account specified doesn't exist, it will also be added. Currently,
|
|
||||||
updating a user's information (password, admin access) must be done by
|
|
||||||
directly updating the sqlite database.
|
|
||||||
|
|
||||||
Valid URL paths:
|
|
||||||
* PUT /account/<account-name>/<user-name> - create the account
|
|
||||||
|
|
||||||
Valid headers:
|
|
||||||
* X-Auth-User-Key: <password>
|
|
||||||
* X-Auth-User-Admin: <true|false>
|
|
||||||
* X-Auth-User-Reseller-Admin: <true|false>
|
|
||||||
|
|
||||||
If the HTTP request returns with a 204, then the user was added,
|
|
||||||
and the storage url will be available in the X-Storage-Url header.
|
|
||||||
|
|
||||||
:param request: webob.Request object
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
_junk, account_name, user_name = \
|
|
||||||
split_path(request.path, minsegs=3)
|
|
||||||
except ValueError:
|
|
||||||
return HTTPBadRequest()
|
|
||||||
create_reseller_admin = \
|
|
||||||
request.headers.get('x-auth-user-reseller-admin') == 'true'
|
|
||||||
if create_reseller_admin and (
|
|
||||||
request.headers.get('X-Auth-Admin-User') != '.super_admin' or
|
|
||||||
request.headers.get('X-Auth-Admin-Key') != self.super_admin_key):
|
|
||||||
return HTTPUnauthorized(request=request)
|
|
||||||
create_account_admin = \
|
|
||||||
request.headers.get('x-auth-user-admin') == 'true'
|
|
||||||
if create_account_admin and \
|
|
||||||
not self.is_account_admin(request, account_name):
|
|
||||||
return HTTPForbidden(request=request)
|
|
||||||
if 'X-Auth-User-Key' not in request.headers:
|
|
||||||
return HTTPBadRequest(body='X-Auth-User-Key is required')
|
|
||||||
password = request.headers['x-auth-user-key']
|
|
||||||
storage_url = self.create_user(account_name, user_name, password,
|
|
||||||
create_account_admin, create_reseller_admin)
|
|
||||||
if storage_url == 'already exists':
|
|
||||||
return HTTPConflict(body=storage_url)
|
|
||||||
if not storage_url:
|
|
||||||
return HTTPServiceUnavailable()
|
|
||||||
return HTTPNoContent(headers={'x-storage-url': storage_url})
|
|
||||||
|
|
||||||
def handle_account_recreate(self, request):
|
|
||||||
"""
|
|
||||||
Handles ReST requests from developers to have accounts in the Auth
|
|
||||||
system recreated in Swift. I know this is bad ReST style, but this
|
|
||||||
isn't production right? :)
|
|
||||||
|
|
||||||
Valid URL paths:
|
|
||||||
* POST /recreate_accounts
|
|
||||||
|
|
||||||
:param request: webob.Request object
|
|
||||||
"""
|
|
||||||
if request.headers.get('X-Auth-Admin-User') != '.super_admin' or \
|
|
||||||
request.headers.get('X-Auth-Admin-Key') != self.super_admin_key:
|
|
||||||
return HTTPUnauthorized(request=request)
|
|
||||||
result = self.recreate_accounts()
|
|
||||||
return Response(result, 200, request=request)
|
|
||||||
|
|
||||||
def handle_auth(self, request):
|
|
||||||
"""
|
|
||||||
Handles ReST requests from end users for a Swift cluster url and auth
|
|
||||||
token. This can handle all the various headers and formats that
|
|
||||||
existing auth systems used, so it's a bit of a chameleon.
|
|
||||||
|
|
||||||
Valid URL paths:
|
|
||||||
* GET /v1/<account-name>/auth
|
|
||||||
* GET /auth
|
|
||||||
* GET /v1.0
|
|
||||||
|
|
||||||
Valid headers:
|
|
||||||
* X-Auth-User: <account-name>:<user-name>
|
|
||||||
* X-Auth-Key: <password>
|
|
||||||
* X-Storage-User: [<account-name>:]<user-name>
|
|
||||||
The [<account-name>:] is only optional here if the
|
|
||||||
/v1/<account-name>/auth path is used.
|
|
||||||
* X-Storage-Pass: <password>
|
|
||||||
|
|
||||||
The (currently) preferred method is to use /v1.0 path and the
|
|
||||||
X-Auth-User and X-Auth-Key headers.
|
|
||||||
|
|
||||||
:param request: A webob.Request instance.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
pathsegs = split_path(request.path, minsegs=1, maxsegs=3,
|
|
||||||
rest_with_last=True)
|
|
||||||
except ValueError:
|
|
||||||
return HTTPBadRequest()
|
|
||||||
if pathsegs[0] == 'v1' and pathsegs[2] == 'auth':
|
|
||||||
account = pathsegs[1]
|
|
||||||
user = request.headers.get('x-storage-user')
|
|
||||||
if not user:
|
|
||||||
user = request.headers.get('x-auth-user')
|
|
||||||
if not user or ':' not in user:
|
|
||||||
return HTTPUnauthorized()
|
|
||||||
account2, user = user.split(':', 1)
|
|
||||||
if account != account2:
|
|
||||||
return HTTPUnauthorized()
|
|
||||||
password = request.headers.get('x-storage-pass')
|
|
||||||
if not password:
|
|
||||||
password = request.headers.get('x-auth-key')
|
|
||||||
elif pathsegs[0] in ('auth', 'v1.0'):
|
|
||||||
user = request.headers.get('x-auth-user')
|
|
||||||
if not user:
|
|
||||||
user = request.headers.get('x-storage-user')
|
|
||||||
if not user or ':' not in user:
|
|
||||||
return HTTPUnauthorized()
|
|
||||||
account, user = user.split(':', 1)
|
|
||||||
password = request.headers.get('x-auth-key')
|
|
||||||
if not password:
|
|
||||||
password = request.headers.get('x-storage-pass')
|
|
||||||
else:
|
|
||||||
return HTTPBadRequest()
|
|
||||||
if not all((account, user, password)):
|
|
||||||
return HTTPUnauthorized()
|
|
||||||
self.purge_old_tokens()
|
|
||||||
with self.get_conn() as conn:
|
|
||||||
row = conn.execute('''
|
|
||||||
SELECT cfaccount, url, admin, reseller_admin FROM account
|
|
||||||
WHERE account = ? AND user = ? AND password = ?''',
|
|
||||||
(account, user, password)).fetchone()
|
|
||||||
if row is None:
|
|
||||||
return HTTPUnauthorized()
|
|
||||||
cfaccount = row[0]
|
|
||||||
url = row[1]
|
|
||||||
admin = row[2] == 't'
|
|
||||||
reseller_admin = row[3] == 't'
|
|
||||||
row = conn.execute('''
|
|
||||||
SELECT token FROM token WHERE account = ? AND user = ?''',
|
|
||||||
(account, user)).fetchone()
|
|
||||||
if row:
|
|
||||||
token = row[0]
|
|
||||||
else:
|
|
||||||
token = '%stk%s' % (self.reseller_prefix, uuid4().hex)
|
|
||||||
token_cfaccount = ''
|
|
||||||
if admin:
|
|
||||||
token_cfaccount = cfaccount
|
|
||||||
if reseller_admin:
|
|
||||||
token_cfaccount = '.reseller_admin'
|
|
||||||
conn.execute('''
|
|
||||||
INSERT INTO token
|
|
||||||
(token, created, account, user, cfaccount)
|
|
||||||
VALUES (?, ?, ?, ?, ?)''',
|
|
||||||
(token, time(), account, user, token_cfaccount))
|
|
||||||
conn.commit()
|
|
||||||
return HTTPNoContent(headers={'x-auth-token': token,
|
|
||||||
'x-storage-token': token,
|
|
||||||
'x-storage-url': url})
|
|
||||||
|
|
||||||
def handleREST(self, env, start_response):
|
|
||||||
"""
|
|
||||||
Handles routing of ReST requests. This handler also logs all requests.
|
|
||||||
|
|
||||||
:param env: WSGI environment
|
|
||||||
:param start_response: WSGI start_response function
|
|
||||||
"""
|
|
||||||
req = Request(env)
|
|
||||||
logged_headers = None
|
|
||||||
if self.log_headers:
|
|
||||||
logged_headers = '\n'.join('%s: %s' % (k, v)
|
|
||||||
for k, v in req.headers.items()).replace('"', "#042")
|
|
||||||
start_time = time()
|
|
||||||
# Figure out how to handle the request
|
|
||||||
try:
|
|
||||||
if req.method == 'GET' and req.path.startswith('/v1') or \
|
|
||||||
req.path.startswith('/auth'):
|
|
||||||
handler = self.handle_auth
|
|
||||||
elif req.method == 'GET' and req.path.startswith('/token/'):
|
|
||||||
handler = self.handle_token
|
|
||||||
elif req.method == 'PUT' and req.path.startswith('/account/'):
|
|
||||||
handler = self.handle_add_user
|
|
||||||
elif req.method == 'POST' and \
|
|
||||||
req.path == '/recreate_accounts':
|
|
||||||
handler = self.handle_account_recreate
|
|
||||||
else:
|
|
||||||
return HTTPBadRequest(request=env)(env, start_response)
|
|
||||||
response = handler(req)
|
|
||||||
except Exception:
|
|
||||||
self.logger.exception(
|
|
||||||
_('ERROR Unhandled exception in ReST request'))
|
|
||||||
return HTTPServiceUnavailable(request=req)(env, start_response)
|
|
||||||
trans_time = '%.4f' % (time() - start_time)
|
|
||||||
if not response.content_length and response.app_iter and \
|
|
||||||
hasattr(response.app_iter, '__len__'):
|
|
||||||
response.content_length = sum(map(len, response.app_iter))
|
|
||||||
the_request = '%s %s' % (req.method, quote(unquote(req.path)))
|
|
||||||
if req.query_string:
|
|
||||||
the_request = the_request + '?' + req.query_string
|
|
||||||
the_request += ' ' + req.environ['SERVER_PROTOCOL']
|
|
||||||
client = req.headers.get('x-cluster-client-ip')
|
|
||||||
if not client and 'x-forwarded-for' in req.headers:
|
|
||||||
client = req.headers['x-forwarded-for'].split(',')[0].strip()
|
|
||||||
if not client:
|
|
||||||
client = req.remote_addr
|
|
||||||
self.logger.info(
|
|
||||||
'%s - - [%s] "%s" %s %s "%s" "%s" - - - - - - - - - "-" "%s" '
|
|
||||||
'"%s" %s' % (
|
|
||||||
client,
|
|
||||||
strftime('%d/%b/%Y:%H:%M:%S +0000', gmtime()),
|
|
||||||
the_request,
|
|
||||||
response.status_int,
|
|
||||||
response.content_length or '-',
|
|
||||||
req.referer or '-',
|
|
||||||
req.user_agent or '-',
|
|
||||||
req.remote_addr,
|
|
||||||
logged_headers or '-',
|
|
||||||
trans_time))
|
|
||||||
return response(env, start_response)
|
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
|
||||||
""" Used by the eventlet.wsgi.server """
|
|
||||||
return self.handleREST(env, start_response)
|
|
||||||
|
|
||||||
|
|
||||||
def app_factory(global_conf, **local_conf):
|
|
||||||
conf = global_conf.copy()
|
|
||||||
conf.update(local_conf)
|
|
||||||
return AuthController(conf)
|
|
@ -1,213 +0,0 @@
|
|||||||
# Copyright (c) 2010-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 time
|
|
||||||
|
|
||||||
from eventlet.timeout import Timeout
|
|
||||||
from webob.exc import HTTPForbidden, HTTPUnauthorized, HTTPNotFound
|
|
||||||
|
|
||||||
from swift.common.bufferedhttp import http_connect_raw as http_connect
|
|
||||||
from swift.common.middleware.acl import clean_acl, parse_acl, referrer_allowed
|
|
||||||
from swift.common.utils import cache_from_env, split_path, TRUE_VALUES
|
|
||||||
|
|
||||||
|
|
||||||
class DevAuth(object):
|
|
||||||
"""Auth Middleware that uses the dev auth server."""
|
|
||||||
|
|
||||||
def __init__(self, app, conf):
|
|
||||||
self.app = app
|
|
||||||
self.conf = conf
|
|
||||||
self.reseller_prefix = conf.get('reseller_prefix', 'AUTH').strip()
|
|
||||||
if self.reseller_prefix and self.reseller_prefix[-1] != '_':
|
|
||||||
self.reseller_prefix += '_'
|
|
||||||
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):
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
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.
|
|
||||||
# Attempt to auth my token with my auth server
|
|
||||||
groups = self.get_groups(env, token,
|
|
||||||
memcache_client=cache_from_env(env))
|
|
||||||
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, 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, memcache_client=None):
|
|
||||||
"""
|
|
||||||
Get groups for the given token.
|
|
||||||
|
|
||||||
If memcache_client is set, token credentials will be cached
|
|
||||||
appropriately.
|
|
||||||
|
|
||||||
With a cache miss, or no memcache_client, the configurated external
|
|
||||||
authentication server will be queried for the group information.
|
|
||||||
|
|
||||||
:param token: Token to validate and return a group string for.
|
|
||||||
:param memcache_client: Memcached client to use for caching token
|
|
||||||
credentials; None if no caching is desired.
|
|
||||||
: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
|
|
||||||
key = '%s/token/%s' % (self.reseller_prefix, token)
|
|
||||||
cached_auth_data = memcache_client and memcache_client.get(key)
|
|
||||||
if cached_auth_data:
|
|
||||||
start, expiration, groups = cached_auth_data
|
|
||||||
if time() - start > expiration:
|
|
||||||
groups = None
|
|
||||||
|
|
||||||
headers = {}
|
|
||||||
if env.get('HTTP_AUTHORIZATION'):
|
|
||||||
groups = None
|
|
||||||
headers["Authorization"] = env.get('HTTP_AUTHORIZATION')
|
|
||||||
|
|
||||||
if not groups:
|
|
||||||
with Timeout(self.timeout):
|
|
||||||
conn = http_connect(self.auth_host, self.auth_port, 'GET',
|
|
||||||
'%stoken/%s' % (self.auth_prefix, token),
|
|
||||||
headers, ssl=self.ssl)
|
|
||||||
|
|
||||||
resp = conn.getresponse()
|
|
||||||
resp.read()
|
|
||||||
conn.close()
|
|
||||||
if resp.status // 100 != 2:
|
|
||||||
return None
|
|
||||||
expiration = float(resp.getheader('x-auth-ttl'))
|
|
||||||
groups = resp.getheader('x-auth-groups')
|
|
||||||
if memcache_client:
|
|
||||||
memcache_client.set(key, (time(), expiration, groups),
|
|
||||||
timeout=expiration)
|
|
||||||
|
|
||||||
if env.get('HTTP_AUTHORIZATION'):
|
|
||||||
account, user, sign = \
|
|
||||||
env['HTTP_AUTHORIZATION'].split(' ')[-1].split(':')
|
|
||||||
cfaccount = resp.getheader('x-auth-account-suffix')
|
|
||||||
path = env['PATH_INFO']
|
|
||||||
env['PATH_INFO'] = \
|
|
||||||
path.replace("%s:%s" % (account, user), cfaccount, 1)
|
|
||||||
|
|
||||||
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:
|
|
||||||
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):
|
|
||||||
return None
|
|
||||||
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 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 DevAuth(app, conf)
|
|
||||||
return auth_filter
|
|
@ -1,13 +1,9 @@
|
|||||||
[func_test]
|
[func_test]
|
||||||
# sample config
|
# sample config
|
||||||
auth_host = 127.0.0.1
|
auth_host = 127.0.0.1
|
||||||
# For DevAuth:
|
auth_port = 8080
|
||||||
auth_port = 11000
|
|
||||||
# For Swauth:
|
|
||||||
# auth_port = 8080
|
|
||||||
auth_ssl = no
|
auth_ssl = no
|
||||||
# For Swauth:
|
auth_prefix = /auth/
|
||||||
# auth_prefix = /auth/
|
|
||||||
|
|
||||||
# Primary functional test account (needs admin access to the account)
|
# Primary functional test account (needs admin access to the account)
|
||||||
account = test
|
account = test
|
||||||
|
@ -25,24 +25,15 @@ from swift.common.ring import Ring
|
|||||||
|
|
||||||
|
|
||||||
SUPER_ADMIN_KEY = None
|
SUPER_ADMIN_KEY = None
|
||||||
AUTH_TYPE = None
|
|
||||||
|
|
||||||
c = ConfigParser()
|
c = ConfigParser()
|
||||||
AUTH_SERVER_CONF_FILE = environ.get('SWIFT_AUTH_SERVER_CONF_FILE',
|
PROXY_SERVER_CONF_FILE = environ.get('SWIFT_PROXY_SERVER_CONF_FILE',
|
||||||
'/etc/swift/auth-server.conf')
|
'/etc/swift/proxy-server.conf')
|
||||||
if c.read(AUTH_SERVER_CONF_FILE):
|
if c.read(PROXY_SERVER_CONF_FILE):
|
||||||
conf = dict(c.items('app:auth-server'))
|
conf = dict(c.items('filter:swauth'))
|
||||||
SUPER_ADMIN_KEY = conf.get('super_admin_key', 'devauth')
|
SUPER_ADMIN_KEY = conf.get('super_admin_key', 'swauthkey')
|
||||||
AUTH_TYPE = 'devauth'
|
|
||||||
else:
|
else:
|
||||||
PROXY_SERVER_CONF_FILE = environ.get('SWIFT_PROXY_SERVER_CONF_FILE',
|
exit('Unable to read config file: %s' % 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):
|
def kill_pids(pids):
|
||||||
@ -57,9 +48,6 @@ def reset_environment():
|
|||||||
call(['resetswift'])
|
call(['resetswift'])
|
||||||
pids = {}
|
pids = {}
|
||||||
try:
|
try:
|
||||||
if AUTH_TYPE == 'devauth':
|
|
||||||
pids['auth'] = Popen(['swift-auth-server',
|
|
||||||
'/etc/swift/auth-server.conf']).pid
|
|
||||||
pids['proxy'] = Popen(['swift-proxy-server',
|
pids['proxy'] = Popen(['swift-proxy-server',
|
||||||
'/etc/swift/proxy-server.conf']).pid
|
'/etc/swift/proxy-server.conf']).pid
|
||||||
port2server = {}
|
port2server = {}
|
||||||
@ -73,21 +61,9 @@ def reset_environment():
|
|||||||
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)
|
sleep(5)
|
||||||
if AUTH_TYPE == 'devauth':
|
call(['recreateaccounts'])
|
||||||
conn = http_connect('127.0.0.1', '11000', 'POST',
|
url, token = get_auth('http://127.0.0.1:8080/auth/v1.0',
|
||||||
'/recreate_accounts',
|
'test:tester', 'testing')
|
||||||
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]
|
account = url.split('/')[-1]
|
||||||
except BaseException, err:
|
except BaseException, err:
|
||||||
kill_pids(pids)
|
kill_pids(pids)
|
||||||
|
@ -1,977 +0,0 @@
|
|||||||
# Copyright (c) 2010-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 __future__ import with_statement
|
|
||||||
import unittest
|
|
||||||
import os
|
|
||||||
from shutil import rmtree
|
|
||||||
from StringIO import StringIO
|
|
||||||
from uuid import uuid4
|
|
||||||
from logging import StreamHandler
|
|
||||||
|
|
||||||
import sqlite3
|
|
||||||
from webob import Request
|
|
||||||
|
|
||||||
from swift.auth import server as auth_server
|
|
||||||
from swift.common.db import DatabaseConnectionError, get_db_connection
|
|
||||||
from swift.common.utils import get_logger
|
|
||||||
|
|
||||||
|
|
||||||
class TestException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def fake_http_connect(*code_iter, **kwargs):
|
|
||||||
class FakeConn(object):
|
|
||||||
def __init__(self, status):
|
|
||||||
self.status = status
|
|
||||||
self.reason = 'Fake'
|
|
||||||
self.host = '1.2.3.4'
|
|
||||||
self.port = '1234'
|
|
||||||
def getresponse(self):
|
|
||||||
if 'slow' in kwargs:
|
|
||||||
sleep(0.2)
|
|
||||||
if 'raise_exc' in kwargs:
|
|
||||||
raise kwargs['raise_exc']
|
|
||||||
return self
|
|
||||||
def getheaders(self):
|
|
||||||
return {'x-account-bytes-used': '20'}
|
|
||||||
def read(self, amt=None):
|
|
||||||
return ''
|
|
||||||
def getheader(self, name):
|
|
||||||
return self.getheaders().get(name.lower())
|
|
||||||
code_iter = iter(code_iter)
|
|
||||||
def connect(*args, **kwargs):
|
|
||||||
connect.last_args = args
|
|
||||||
connect.last_kwargs = kwargs
|
|
||||||
return FakeConn(code_iter.next())
|
|
||||||
return connect
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuthServer(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.ohttp_connect = auth_server.http_connect
|
|
||||||
self.testdir = os.path.join(os.path.dirname(__file__),
|
|
||||||
'auth_server')
|
|
||||||
rmtree(self.testdir, ignore_errors=1)
|
|
||||||
os.mkdir(self.testdir)
|
|
||||||
self.conf = {'swift_dir': self.testdir, 'log_name': 'auth',
|
|
||||||
'super_admin_key': 'testkey'}
|
|
||||||
self.controller = auth_server.AuthController(self.conf)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
auth_server.http_connect = self.ohttp_connect
|
|
||||||
rmtree(self.testdir, ignore_errors=1)
|
|
||||||
|
|
||||||
def test_get_conn(self):
|
|
||||||
with self.controller.get_conn() as conn:
|
|
||||||
pass
|
|
||||||
exc = False
|
|
||||||
try:
|
|
||||||
with self.controller.get_conn() as conn:
|
|
||||||
raise TestException('test')
|
|
||||||
except TestException:
|
|
||||||
exc = True
|
|
||||||
self.assert_(exc)
|
|
||||||
# We allow reentrant calls for the auth-server
|
|
||||||
with self.controller.get_conn() as conn1:
|
|
||||||
exc = False
|
|
||||||
try:
|
|
||||||
with self.controller.get_conn() as conn2:
|
|
||||||
self.assert_(conn1 is not conn2)
|
|
||||||
except DatabaseConnectionError:
|
|
||||||
exc = True
|
|
||||||
self.assert_(not exc)
|
|
||||||
self.controller.conn = None
|
|
||||||
with self.controller.get_conn() as conn:
|
|
||||||
self.assert_(conn is not None)
|
|
||||||
|
|
||||||
def test_validate_token_non_existant_token(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing',).split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
self.assertEquals(self.controller.validate_token(token + 'bad'), False)
|
|
||||||
|
|
||||||
def test_validate_token_good(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing',).split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
ttl, _junk, _junk, _junk = self.controller.validate_token(token)
|
|
||||||
self.assert_(ttl > 0, repr(ttl))
|
|
||||||
|
|
||||||
def test_validate_token_expired(self):
|
|
||||||
orig_time = auth_server.time
|
|
||||||
try:
|
|
||||||
auth_server.time = lambda: 1
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user('test', 'tester',
|
|
||||||
'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
ttl, _junk, _junk, _junk = self.controller.validate_token(token)
|
|
||||||
self.assert_(ttl > 0, repr(ttl))
|
|
||||||
auth_server.time = lambda: 1 + self.controller.token_life
|
|
||||||
self.assertEquals(self.controller.validate_token(token), False)
|
|
||||||
finally:
|
|
||||||
auth_server.time = orig_time
|
|
||||||
|
|
||||||
def test_create_user_no_new_account(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
result = self.controller.create_user('', 'tester', 'testing')
|
|
||||||
self.assertFalse(result)
|
|
||||||
|
|
||||||
def test_create_user_no_new_user(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
result = self.controller.create_user('test', '', 'testing')
|
|
||||||
self.assertFalse(result)
|
|
||||||
|
|
||||||
def test_create_user_no_new_password(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
result = self.controller.create_user('test', 'tester', '')
|
|
||||||
self.assertFalse(result)
|
|
||||||
|
|
||||||
def test_create_user_good(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test', 'tester', 'testing')
|
|
||||||
self.assert_(url)
|
|
||||||
self.assertEquals('/'.join(url.split('/')[:-1]),
|
|
||||||
self.controller.default_cluster_url.rstrip('/'), repr(url))
|
|
||||||
|
|
||||||
def test_recreate_accounts_none(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
rv = self.controller.recreate_accounts()
|
|
||||||
self.assertEquals(rv.split()[0], '0', repr(rv))
|
|
||||||
self.assertEquals(rv.split()[-1], '[]', repr(rv))
|
|
||||||
|
|
||||||
def test_recreate_accounts_one(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('test', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
rv = self.controller.recreate_accounts()
|
|
||||||
self.assertEquals(rv.split()[0], '1', repr(rv))
|
|
||||||
self.assertEquals(rv.split()[-1], '[]', repr(rv))
|
|
||||||
|
|
||||||
def test_recreate_accounts_several(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('test1', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('test2', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('test3', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('test4', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201, 201, 201, 201)
|
|
||||||
rv = self.controller.recreate_accounts()
|
|
||||||
self.assertEquals(rv.split()[0], '4', repr(rv))
|
|
||||||
self.assertEquals(rv.split()[-1], '[]', repr(rv))
|
|
||||||
|
|
||||||
def test_recreate_accounts_one_fail(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test', 'tester', 'testing')
|
|
||||||
cfaccount = url.split('/')[-1]
|
|
||||||
auth_server.http_connect = fake_http_connect(500)
|
|
||||||
rv = self.controller.recreate_accounts()
|
|
||||||
self.assertEquals(rv.split()[0], '1', repr(rv))
|
|
||||||
self.assertEquals(rv.split()[-1], '[%s]' % repr(cfaccount),
|
|
||||||
repr(rv))
|
|
||||||
|
|
||||||
def test_recreate_accounts_several_fail(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test1', 'tester', 'testing')
|
|
||||||
cfaccounts = [url.split('/')[-1]]
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test2', 'tester', 'testing')
|
|
||||||
cfaccounts.append(url.split('/')[-1])
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test3', 'tester', 'testing')
|
|
||||||
cfaccounts.append(url.split('/')[-1])
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test4', 'tester', 'testing')
|
|
||||||
cfaccounts.append(url.split('/')[-1])
|
|
||||||
auth_server.http_connect = fake_http_connect(500, 500, 500, 500)
|
|
||||||
rv = self.controller.recreate_accounts()
|
|
||||||
self.assertEquals(rv.split()[0], '4', repr(rv))
|
|
||||||
failed = rv.split('[', 1)[-1][:-1].split(', ')
|
|
||||||
self.assertEquals(set(failed), set(repr(a) for a in cfaccounts))
|
|
||||||
|
|
||||||
def test_recreate_accounts_several_fail_some(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test1', 'tester', 'testing')
|
|
||||||
cfaccounts = [url.split('/')[-1]]
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test2', 'tester', 'testing')
|
|
||||||
cfaccounts.append(url.split('/')[-1])
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test3', 'tester', 'testing')
|
|
||||||
cfaccounts.append(url.split('/')[-1])
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test4', 'tester', 'testing')
|
|
||||||
cfaccounts.append(url.split('/')[-1])
|
|
||||||
auth_server.http_connect = fake_http_connect(500, 201, 500, 201)
|
|
||||||
rv = self.controller.recreate_accounts()
|
|
||||||
self.assertEquals(rv.split()[0], '4', repr(rv))
|
|
||||||
failed = rv.split('[', 1)[-1][:-1].split(', ')
|
|
||||||
self.assertEquals(
|
|
||||||
len(set(repr(a) for a in cfaccounts) - set(failed)), 2)
|
|
||||||
|
|
||||||
def test_auth_bad_path(self):
|
|
||||||
res = self.controller.handle_auth(
|
|
||||||
Request.blank('', environ={'REQUEST_METHOD': 'GET'}))
|
|
||||||
self.assertEquals(res.status_int, 400)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/bad',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'}))
|
|
||||||
self.assertEquals(res.status_int, 400)
|
|
||||||
|
|
||||||
def test_auth_SOSO_missing_headers(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-Pass': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_SOSO_bad_account(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/testbad/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1//auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_SOSO_bad_user(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'testerbad',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': '',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_SOSO_bad_password(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testingbad'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': ''}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_SOSO_good(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
ttl, _junk, _junk, _junk = self.controller.validate_token(token)
|
|
||||||
self.assert_(ttl > 0, repr(ttl))
|
|
||||||
|
|
||||||
def test_auth_SOSO_good_Mosso_headers(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:tester',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
ttl, _junk, _junk, _junk = self.controller.validate_token(token)
|
|
||||||
self.assert_(ttl > 0, repr(ttl))
|
|
||||||
|
|
||||||
def test_auth_SOSO_bad_Mosso_headers(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing',).split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test2:tester',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': ':tester',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1/test/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_Mosso_missing_headers(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:tester'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_Mosso_bad_header_format(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'badformat',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': '',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_Mosso_bad_account(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'testbad:tester',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': ':tester',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_Mosso_bad_user(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:testerbad',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_Mosso_bad_password(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:tester',
|
|
||||||
'X-Auth-Key': 'testingbad'}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:tester',
|
|
||||||
'X-Auth-Key': ''}))
|
|
||||||
self.assertEquals(res.status_int, 401)
|
|
||||||
|
|
||||||
def test_auth_Mosso_good(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'test:tester',
|
|
||||||
'X-Auth-Key': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
ttl, _junk, _junk, _junk = self.controller.validate_token(token)
|
|
||||||
self.assert_(ttl > 0, repr(ttl))
|
|
||||||
|
|
||||||
def test_auth_Mosso_good_SOSO_header_names(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
cfaccount = self.controller.create_user(
|
|
||||||
'test', 'tester', 'testing').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/auth',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Storage-User': 'test:tester',
|
|
||||||
'X-Storage-Pass': 'testing'}))
|
|
||||||
token = res.headers['x-storage-token']
|
|
||||||
ttl, _junk, _junk, _junk = self.controller.validate_token(token)
|
|
||||||
self.assert_(ttl > 0, repr(ttl))
|
|
||||||
|
|
||||||
def test_basic_logging(self):
|
|
||||||
log = StringIO()
|
|
||||||
log_handler = StreamHandler(log)
|
|
||||||
logger = get_logger(self.conf, 'auth-server', log_route='auth-server')
|
|
||||||
logger.logger.addHandler(log_handler)
|
|
||||||
try:
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test', 'tester', 'testing')
|
|
||||||
self.assertEquals(log.getvalue().rsplit(' ', 1)[0],
|
|
||||||
"SUCCESS create_user('test', 'tester', _, False, False) "
|
|
||||||
"= %s" % repr(url))
|
|
||||||
log.truncate(0)
|
|
||||||
def start_response(*args):
|
|
||||||
pass
|
|
||||||
self.controller.handleREST({'REQUEST_METHOD': 'GET',
|
|
||||||
'SCRIPT_NAME': '',
|
|
||||||
'PATH_INFO': '/v1/test/auth',
|
|
||||||
'QUERY_STRING': 'test=True',
|
|
||||||
'SERVER_NAME': '127.0.0.1',
|
|
||||||
'SERVER_PORT': '8080',
|
|
||||||
'SERVER_PROTOCOL': 'HTTP/1.0',
|
|
||||||
'CONTENT_LENGTH': '0',
|
|
||||||
'wsgi.version': (1, 0),
|
|
||||||
'wsgi.url_scheme': 'http',
|
|
||||||
'wsgi.input': StringIO(),
|
|
||||||
'wsgi.errors': StringIO(),
|
|
||||||
'wsgi.multithread': False,
|
|
||||||
'wsgi.multiprocess': False,
|
|
||||||
'wsgi.run_once': False,
|
|
||||||
'HTTP_X_FORWARDED_FOR': 'testhost',
|
|
||||||
'HTTP_X_STORAGE_USER': 'tester',
|
|
||||||
'HTTP_X_STORAGE_PASS': 'testing'},
|
|
||||||
start_response)
|
|
||||||
logsegs = log.getvalue().split(' [', 1)
|
|
||||||
logsegs[1:] = logsegs[1].split('] ', 1)
|
|
||||||
logsegs[1] = '[01/Jan/2001:01:02:03 +0000]'
|
|
||||||
logsegs[2:] = logsegs[2].split(' ')
|
|
||||||
logsegs[-1] = '0.1234'
|
|
||||||
self.assertEquals(' '.join(logsegs), 'testhost - - '
|
|
||||||
'[01/Jan/2001:01:02:03 +0000] "GET /v1/test/auth?test=True '
|
|
||||||
'HTTP/1.0" 204 - "-" "-" - - - - - - - - - "-" "None" "-" '
|
|
||||||
'0.1234')
|
|
||||||
self.controller.log_headers = True
|
|
||||||
log.truncate(0)
|
|
||||||
self.controller.handleREST({'REQUEST_METHOD': 'GET',
|
|
||||||
'SCRIPT_NAME': '',
|
|
||||||
'PATH_INFO': '/v1/test/auth',
|
|
||||||
'SERVER_NAME': '127.0.0.1',
|
|
||||||
'SERVER_PORT': '8080',
|
|
||||||
'SERVER_PROTOCOL': 'HTTP/1.0',
|
|
||||||
'CONTENT_LENGTH': '0',
|
|
||||||
'wsgi.version': (1, 0),
|
|
||||||
'wsgi.url_scheme': 'http',
|
|
||||||
'wsgi.input': StringIO(),
|
|
||||||
'wsgi.errors': StringIO(),
|
|
||||||
'wsgi.multithread': False,
|
|
||||||
'wsgi.multiprocess': False,
|
|
||||||
'wsgi.run_once': False,
|
|
||||||
'HTTP_X_STORAGE_USER': 'tester',
|
|
||||||
'HTTP_X_STORAGE_PASS': 'testing'},
|
|
||||||
start_response)
|
|
||||||
logsegs = log.getvalue().split(' [', 1)
|
|
||||||
logsegs[1:] = logsegs[1].split('] ', 1)
|
|
||||||
logsegs[1] = '[01/Jan/2001:01:02:03 +0000]'
|
|
||||||
logsegs[2:] = logsegs[2].split(' ')
|
|
||||||
logsegs[-1] = '0.1234'
|
|
||||||
self.assertEquals(' '.join(logsegs), 'None - - [01/Jan/2001:'
|
|
||||||
'01:02:03 +0000] "GET /v1/test/auth HTTP/1.0" 204 - "-" "-" - '
|
|
||||||
'- - - - - - - - "-" "None" "Content-Length: 0\n'
|
|
||||||
'X-Storage-User: tester\nX-Storage-Pass: testing" 0.1234')
|
|
||||||
finally:
|
|
||||||
logger.logger.handlers.remove(log_handler)
|
|
||||||
|
|
||||||
def test_unhandled_exceptions(self):
|
|
||||||
def request_causing_exception(*args, **kwargs):
|
|
||||||
pass
|
|
||||||
def start_response(*args):
|
|
||||||
pass
|
|
||||||
orig_Request = auth_server.Request
|
|
||||||
log = StringIO()
|
|
||||||
log_handler = StreamHandler(log)
|
|
||||||
logger = get_logger(self.conf, 'auth-server', log_route='auth-server')
|
|
||||||
logger.logger.addHandler(log_handler)
|
|
||||||
try:
|
|
||||||
auth_server.Request = request_causing_exception
|
|
||||||
self.controller.handleREST({'REQUEST_METHOD': 'GET',
|
|
||||||
'SCRIPT_NAME': '',
|
|
||||||
'PATH_INFO': '/v1/test/auth',
|
|
||||||
'SERVER_NAME': '127.0.0.1',
|
|
||||||
'SERVER_PORT': '8080',
|
|
||||||
'SERVER_PROTOCOL': 'HTTP/1.0',
|
|
||||||
'CONTENT_LENGTH': '0',
|
|
||||||
'wsgi.version': (1, 0),
|
|
||||||
'wsgi.url_scheme': 'http',
|
|
||||||
'wsgi.input': StringIO(),
|
|
||||||
'wsgi.errors': StringIO(),
|
|
||||||
'wsgi.multithread': False,
|
|
||||||
'wsgi.multiprocess': False,
|
|
||||||
'wsgi.run_once': False,
|
|
||||||
'HTTP_X_STORAGE_USER': 'tester',
|
|
||||||
'HTTP_X_STORAGE_PASS': 'testing'},
|
|
||||||
start_response)
|
|
||||||
self.assert_(log.getvalue().startswith(
|
|
||||||
'ERROR Unhandled exception in ReST request'),
|
|
||||||
log.getvalue())
|
|
||||||
log.truncate(0)
|
|
||||||
finally:
|
|
||||||
auth_server.Request = orig_Request
|
|
||||||
logger.logger.handlers.remove(log_handler)
|
|
||||||
|
|
||||||
def test_upgrading_from_db1(self):
|
|
||||||
swift_dir = '/tmp/swift_test_auth_%s' % uuid4().hex
|
|
||||||
os.mkdir(swift_dir)
|
|
||||||
try:
|
|
||||||
# Create db1
|
|
||||||
db_file = os.path.join(swift_dir, 'auth.db')
|
|
||||||
conn = get_db_connection(db_file, okay_to_create=True)
|
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS account (
|
|
||||||
account TEXT, url TEXT, cfaccount TEXT,
|
|
||||||
user TEXT, password TEXT)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
|
|
||||||
ON account (account)''')
|
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS token (
|
|
||||||
cfaccount TEXT, token TEXT, created FLOAT)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_cfaccount
|
|
||||||
ON token (cfaccount)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
|
|
||||||
ON token (created)''')
|
|
||||||
conn.execute('''INSERT INTO account
|
|
||||||
(account, url, cfaccount, user, password)
|
|
||||||
VALUES ('act', 'url', 'cfa', 'usr', 'pas')''')
|
|
||||||
conn.execute('''INSERT INTO token (cfaccount, token, created)
|
|
||||||
VALUES ('cfa', 'tok', '1')''')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
# Upgrade to current db
|
|
||||||
conf = {'swift_dir': swift_dir, 'super_admin_key': 'testkey'}
|
|
||||||
exc = None
|
|
||||||
try:
|
|
||||||
auth_server.AuthController(conf)
|
|
||||||
except Exception, err:
|
|
||||||
exc = err
|
|
||||||
self.assert_(str(err).strip().startswith('THERE ARE ACCOUNTS IN '
|
|
||||||
'YOUR auth.db THAT DO NOT BEGIN WITH YOUR NEW RESELLER'), err)
|
|
||||||
# Check new items exist and are correct
|
|
||||||
conn = get_db_connection(db_file)
|
|
||||||
row = conn.execute('SELECT admin FROM account').fetchone()
|
|
||||||
self.assertEquals(row[0], 't')
|
|
||||||
row = conn.execute('SELECT user FROM token').fetchone()
|
|
||||||
self.assert_(not row)
|
|
||||||
finally:
|
|
||||||
rmtree(swift_dir)
|
|
||||||
|
|
||||||
def test_upgrading_from_db2(self):
|
|
||||||
swift_dir = '/tmp/swift_test_auth_%s' % uuid4().hex
|
|
||||||
os.mkdir(swift_dir)
|
|
||||||
try:
|
|
||||||
# Create db1
|
|
||||||
db_file = os.path.join(swift_dir, 'auth.db')
|
|
||||||
conn = get_db_connection(db_file, okay_to_create=True)
|
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS account (
|
|
||||||
account TEXT, url TEXT, cfaccount TEXT,
|
|
||||||
user TEXT, password TEXT, admin TEXT)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_account_account
|
|
||||||
ON account (account)''')
|
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS token (
|
|
||||||
token TEXT, created FLOAT,
|
|
||||||
account TEXT, user TEXT, cfaccount TEXT)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_token
|
|
||||||
ON token (token)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_created
|
|
||||||
ON token (created)''')
|
|
||||||
conn.execute('''CREATE INDEX IF NOT EXISTS ix_token_account
|
|
||||||
ON token (account)''')
|
|
||||||
conn.execute('''INSERT INTO account
|
|
||||||
(account, url, cfaccount, user, password, admin)
|
|
||||||
VALUES ('act', 'url', 'cfa', 'us1', 'pas', '')''')
|
|
||||||
conn.execute('''INSERT INTO account
|
|
||||||
(account, url, cfaccount, user, password, admin)
|
|
||||||
VALUES ('act', 'url', 'cfa', 'us2', 'pas', 't')''')
|
|
||||||
conn.execute('''INSERT INTO token
|
|
||||||
(token, created, account, user, cfaccount)
|
|
||||||
VALUES ('tok', '1', 'act', 'us1', 'cfa')''')
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
# Upgrade to current db
|
|
||||||
conf = {'swift_dir': swift_dir, 'super_admin_key': 'testkey'}
|
|
||||||
exc = None
|
|
||||||
try:
|
|
||||||
auth_server.AuthController(conf)
|
|
||||||
except Exception, err:
|
|
||||||
exc = err
|
|
||||||
self.assert_(str(err).strip().startswith('THERE ARE ACCOUNTS IN '
|
|
||||||
'YOUR auth.db THAT DO NOT BEGIN WITH YOUR NEW RESELLER'), err)
|
|
||||||
# Check new items exist and are correct
|
|
||||||
conn = get_db_connection(db_file)
|
|
||||||
row = conn.execute('''SELECT admin, reseller_admin
|
|
||||||
FROM account WHERE user = 'us1' ''').fetchone()
|
|
||||||
self.assert_(not row[0], row[0])
|
|
||||||
self.assert_(not row[1], row[1])
|
|
||||||
row = conn.execute('''SELECT admin, reseller_admin
|
|
||||||
FROM account WHERE user = 'us2' ''').fetchone()
|
|
||||||
self.assertEquals(row[0], 't')
|
|
||||||
self.assert_(not row[1], row[1])
|
|
||||||
row = conn.execute('SELECT user FROM token').fetchone()
|
|
||||||
self.assert_(row)
|
|
||||||
finally:
|
|
||||||
rmtree(swift_dir)
|
|
||||||
|
|
||||||
def test_create_user_twice(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('test', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.assertEquals(
|
|
||||||
self.controller.create_user('test', 'tester', 'testing'),
|
|
||||||
'already exists')
|
|
||||||
req = Request.blank('/account/test/tester',
|
|
||||||
headers={'X-Auth-User-Key': 'testing'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assertEquals(resp.status_int, 409)
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_2users_1account(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url = self.controller.create_user('test', 'tester', 'testing')
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
url2 = self.controller.create_user('test', 'tester2', 'testing2')
|
|
||||||
self.assertEquals(url, url2)
|
|
||||||
|
|
||||||
def test_no_super_admin_key(self):
|
|
||||||
conf = {'swift_dir': self.testdir, 'log_name': 'auth'}
|
|
||||||
self.assertRaises(ValueError, auth_server.AuthController, conf)
|
|
||||||
conf['super_admin_key'] = 'testkey'
|
|
||||||
controller = auth_server.AuthController(conf)
|
|
||||||
self.assertEquals(controller.super_admin_key, conf['super_admin_key'])
|
|
||||||
|
|
||||||
def test_add_storage_account(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
stgact = self.controller.add_storage_account()
|
|
||||||
self.assert_(stgact.startswith(self.controller.reseller_prefix),
|
|
||||||
stgact)
|
|
||||||
# Make sure token given is the expected single use token
|
|
||||||
token = auth_server.http_connect.last_args[-1]['X-Auth-Token']
|
|
||||||
self.assert_(self.controller.validate_token(token))
|
|
||||||
self.assert_(not self.controller.validate_token(token))
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
stgact = self.controller.add_storage_account('bob')
|
|
||||||
self.assertEquals(stgact, 'bob')
|
|
||||||
# Make sure token given is the expected single use token
|
|
||||||
token = auth_server.http_connect.last_args[-1]['X-Auth-Token']
|
|
||||||
self.assert_(self.controller.validate_token(token))
|
|
||||||
self.assert_(not self.controller.validate_token(token))
|
|
||||||
|
|
||||||
def test_regular_user(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('act', 'usr', 'pas').split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1.0',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
|
|
||||||
_junk, _junk, _junk, stgact = \
|
|
||||||
self.controller.validate_token(res.headers['x-auth-token'])
|
|
||||||
self.assertEquals(stgact, '')
|
|
||||||
|
|
||||||
def test_account_admin(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
stgact = self.controller.create_user(
|
|
||||||
'act', 'usr', 'pas', admin=True).split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1.0',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
|
|
||||||
_junk, _junk, _junk, vstgact = \
|
|
||||||
self.controller.validate_token(res.headers['x-auth-token'])
|
|
||||||
self.assertEquals(stgact, vstgact)
|
|
||||||
|
|
||||||
def test_reseller_admin(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user(
|
|
||||||
'act', 'usr', 'pas', reseller_admin=True).split('/')[-1]
|
|
||||||
res = self.controller.handle_auth(Request.blank('/v1.0',
|
|
||||||
environ={'REQUEST_METHOD': 'GET'},
|
|
||||||
headers={'X-Auth-User': 'act:usr', 'X-Auth-Key': 'pas'}))
|
|
||||||
_junk, _junk, _junk, stgact = \
|
|
||||||
self.controller.validate_token(res.headers['x-auth-token'])
|
|
||||||
self.assertEquals(stgact, '.reseller_admin')
|
|
||||||
|
|
||||||
def test_is_account_admin(self):
|
|
||||||
req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admin',
|
|
||||||
'X-Auth-Admin-Key': 'testkey'})
|
|
||||||
self.assert_(self.controller.is_account_admin(req, 'any'))
|
|
||||||
req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admin',
|
|
||||||
'X-Auth-Admin-Key': 'testkey2'})
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'any'))
|
|
||||||
req = Request.blank('/', headers={'X-Auth-Admin-User': '.super_admi',
|
|
||||||
'X-Auth-Admin-Key': 'testkey'})
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'any'))
|
|
||||||
|
|
||||||
auth_server.http_connect = fake_http_connect(201, 201)
|
|
||||||
self.controller.create_user(
|
|
||||||
'act1', 'resadmin', 'pas', reseller_admin=True).split('/')[-1]
|
|
||||||
self.controller.create_user('act1', 'usr', 'pas').split('/')[-1]
|
|
||||||
self.controller.create_user(
|
|
||||||
'act2', 'actadmin', 'pas', admin=True).split('/')[-1]
|
|
||||||
|
|
||||||
req = Request.blank('/', headers={'X-Auth-Admin-User': 'act1:resadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
self.assert_(self.controller.is_account_admin(req, 'any'))
|
|
||||||
self.assert_(self.controller.is_account_admin(req, 'act1'))
|
|
||||||
self.assert_(self.controller.is_account_admin(req, 'act2'))
|
|
||||||
|
|
||||||
req = Request.blank('/', headers={'X-Auth-Admin-User': 'act1:usr',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'any'))
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'act1'))
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'act2'))
|
|
||||||
|
|
||||||
req = Request.blank('/', headers={'X-Auth-Admin-User': 'act2:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'any'))
|
|
||||||
self.assert_(not self.controller.is_account_admin(req, 'act1'))
|
|
||||||
self.assert_(self.controller.is_account_admin(req, 'act2'))
|
|
||||||
|
|
||||||
def test_handle_add_user_create_reseller_admin(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201)
|
|
||||||
self.controller.create_user('act', 'usr', 'pas')
|
|
||||||
self.controller.create_user('act', 'actadmin', 'pas', admin=True)
|
|
||||||
self.controller.create_user('act', 'resadmin', 'pas',
|
|
||||||
reseller_admin=True)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/resadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Reseller-Admin': 'true'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/resadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Reseller-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:usr',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/resadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Reseller-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/resadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Reseller-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:resadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/resadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Reseller-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': '.super_admin',
|
|
||||||
'X-Auth-Admin-Key': 'testkey'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
def test_handle_add_user_create_account_admin(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201, 201)
|
|
||||||
self.controller.create_user('act', 'usr', 'pas')
|
|
||||||
self.controller.create_user('act', 'actadmin', 'pas', admin=True)
|
|
||||||
self.controller.create_user('act2', 'actadmin', 'pas', admin=True)
|
|
||||||
self.controller.create_user('act2', 'resadmin', 'pas',
|
|
||||||
reseller_admin=True)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/actadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/actadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:usr',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/actadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act2:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/actadmin2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/actadmin3',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act2:resadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/actadmin4',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': '.super_admin',
|
|
||||||
'X-Auth-Admin-Key': 'testkey'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
def test_handle_add_user_create_normal_user(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201, 201)
|
|
||||||
self.controller.create_user('act', 'usr', 'pas')
|
|
||||||
self.controller.create_user('act', 'actadmin', 'pas', admin=True)
|
|
||||||
self.controller.create_user('act2', 'actadmin', 'pas', admin=True)
|
|
||||||
self.controller.create_user('act2', 'resadmin', 'pas',
|
|
||||||
reseller_admin=True)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/usr2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/usr2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:usr',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/usr2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act2:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/usr2',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/usr3',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act2:resadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/account/act/usr4',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': '.super_admin',
|
|
||||||
'X-Auth-Admin-Key': 'testkey'})
|
|
||||||
resp = self.controller.handle_add_user(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
def test_handle_account_recreate_permissions(self):
|
|
||||||
auth_server.http_connect = fake_http_connect(201, 201)
|
|
||||||
self.controller.create_user('act', 'usr', 'pas')
|
|
||||||
self.controller.create_user('act', 'actadmin', 'pas', admin=True)
|
|
||||||
self.controller.create_user('act', 'resadmin', 'pas',
|
|
||||||
reseller_admin=True)
|
|
||||||
|
|
||||||
req = Request.blank('/recreate_accounts',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true'})
|
|
||||||
resp = self.controller.handle_account_recreate(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/recreate_accounts',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:usr',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_account_recreate(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/recreate_accounts',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:actadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_account_recreate(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/recreate_accounts',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': 'act:resadmin',
|
|
||||||
'X-Auth-Admin-Key': 'pas'})
|
|
||||||
resp = self.controller.handle_account_recreate(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 4, resp.status_int)
|
|
||||||
|
|
||||||
req = Request.blank('/recreate_accounts',
|
|
||||||
headers={'X-Auth-User-Key': 'pas',
|
|
||||||
'X-Auth-User-Admin': 'true',
|
|
||||||
'X-Auth-Admin-User': '.super_admin',
|
|
||||||
'X-Auth-Admin-Key': 'testkey'})
|
|
||||||
resp = self.controller.handle_account_recreate(req)
|
|
||||||
self.assert_(resp.status_int // 100 == 2, resp.status_int)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
@ -1,471 +0,0 @@
|
|||||||
# Copyright (c) 2010-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 __future__ import with_statement
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
from webob import Request
|
|
||||||
|
|
||||||
from swift.common.middleware import auth
|
|
||||||
|
|
||||||
# mocks
|
|
||||||
logging.getLogger().addHandler(logging.StreamHandler(sys.stdout))
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def mock_http_connect(response, headers=None, with_exc=False):
|
|
||||||
class FakeConn(object):
|
|
||||||
def __init__(self, status, headers, with_exc):
|
|
||||||
self.status = status
|
|
||||||
self.reason = 'Fake'
|
|
||||||
self.host = '1.2.3.4'
|
|
||||||
self.port = '1234'
|
|
||||||
self.with_exc = with_exc
|
|
||||||
self.headers = headers
|
|
||||||
if self.headers is None:
|
|
||||||
self.headers = {}
|
|
||||||
|
|
||||||
def getresponse(self):
|
|
||||||
if self.with_exc:
|
|
||||||
raise Exception('test')
|
|
||||||
return self
|
|
||||||
|
|
||||||
def getheader(self, header):
|
|
||||||
return self.headers[header]
|
|
||||||
|
|
||||||
def read(self, amt=None):
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
return
|
|
||||||
|
|
||||||
return lambda *args, **kwargs: FakeConn(response, headers, with_exc)
|
|
||||||
|
|
||||||
|
|
||||||
class Logger(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.error_value = None
|
|
||||||
self.exception_value = None
|
|
||||||
|
|
||||||
def error(self, msg, *args, **kwargs):
|
|
||||||
self.error_value = (msg, args, kwargs)
|
|
||||||
|
|
||||||
def exception(self, msg, *args, **kwargs):
|
|
||||||
_junk, exc, _junk = sys.exc_info()
|
|
||||||
self.exception_value = (msg,
|
|
||||||
'%s %s' % (exc.__class__.__name__, str(exc)), args, kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeApp(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.i_was_called = False
|
|
||||||
|
|
||||||
def __call__(self, env, start_response):
|
|
||||||
self.i_was_called = True
|
|
||||||
req = Request.blank('', environ=env)
|
|
||||||
if 'swift.authorize' in env:
|
|
||||||
resp = env['swift.authorize'](req)
|
|
||||||
if resp:
|
|
||||||
return resp(env, start_response)
|
|
||||||
return ['204 No Content']
|
|
||||||
|
|
||||||
|
|
||||||
def start_response(*args):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuth(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.test_auth = auth.filter_factory({})(FakeApp())
|
|
||||||
|
|
||||||
def test_auth_deny_non_reseller_prefix(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account',
|
|
||||||
'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache()}
|
|
||||||
result = ''.join(self.test_auth(reqenv, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('401'), result)
|
|
||||||
self.assertEquals(reqenv['swift.authorize'],
|
|
||||||
self.test_auth.denied_response)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_auth_deny_non_reseller_prefix_no_override(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
fake_authorize = lambda x: lambda x, y: ['500 Fake']
|
|
||||||
reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/BLAH_account',
|
|
||||||
'HTTP_X_AUTH_TOKEN': 'BLAH_t', 'swift.cache': FakeMemcache(),
|
|
||||||
'swift.authorize': fake_authorize}
|
|
||||||
result = ''.join(self.test_auth(reqenv, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('500 Fake'), result)
|
|
||||||
self.assertEquals(reqenv['swift.authorize'], fake_authorize)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
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.
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
local_app = FakeApp()
|
|
||||||
local_auth = \
|
|
||||||
auth.filter_factory({'reseller_prefix': ''})(local_app)
|
|
||||||
auth.http_connect = mock_http_connect(404)
|
|
||||||
reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account',
|
|
||||||
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': FakeMemcache()}
|
|
||||||
result = ''.join(local_auth(reqenv, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('401'), result)
|
|
||||||
self.assert_(local_app.i_was_called)
|
|
||||||
self.assertEquals(reqenv['swift.authorize'],
|
|
||||||
local_auth.denied_response)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_auth_no_reseller_prefix_allow(self):
|
|
||||||
# Ensures that when we have no reseller prefix, we can still allow
|
|
||||||
# access if our auth server accepts requests
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
local_app = FakeApp()
|
|
||||||
local_auth = \
|
|
||||||
auth.filter_factory({'reseller_prefix': ''})(local_app)
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/act',
|
|
||||||
'HTTP_X_AUTH_TOKEN': 't', 'swift.cache': None}
|
|
||||||
result = ''.join(local_auth(reqenv, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
self.assert_(local_app.i_was_called)
|
|
||||||
self.assertEquals(reqenv['swift.authorize'],
|
|
||||||
local_auth.authorize)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
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())
|
|
||||||
reqenv = {'REQUEST_METHOD': 'GET', 'PATH_INFO': '/v1/account',
|
|
||||||
'swift.cache': FakeMemcache()}
|
|
||||||
result = ''.join(local_auth(reqenv, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('401'), result)
|
|
||||||
self.assertEquals(reqenv['swift.authorize'], local_auth.authorize)
|
|
||||||
# Now make sure we don't override an existing swift.authorize when we
|
|
||||||
# have no reseller prefix.
|
|
||||||
local_authorize = lambda req: None
|
|
||||||
reqenv['swift.authorize'] = local_authorize
|
|
||||||
result = ''.join(local_auth(reqenv, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
self.assertEquals(reqenv['swift.authorize'], local_authorize)
|
|
||||||
|
|
||||||
def test_auth_fail(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(404)
|
|
||||||
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
|
|
||||||
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': FakeMemcache()},
|
|
||||||
lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('401'), result)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_auth_success(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
|
|
||||||
'swift.cache': FakeMemcache()}, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_auth_memcache(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
fake_memcache = FakeMemcache()
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
|
|
||||||
'swift.cache': fake_memcache}, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
auth.http_connect = mock_http_connect(404)
|
|
||||||
# Should still be in memcache
|
|
||||||
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
|
|
||||||
'swift.cache': fake_memcache}, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_auth_just_expired(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
fake_memcache = FakeMemcache()
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '0', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
|
|
||||||
'PATH_INFO': '/v/AUTH_cfa', 'HTTP_X_AUTH_TOKEN': 'AUTH_t',
|
|
||||||
'swift.cache': fake_memcache}, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
auth.http_connect = mock_http_connect(404)
|
|
||||||
# Should still be in memcache, but expired
|
|
||||||
result = ''.join(self.test_auth({'REQUEST_METHOD': 'GET',
|
|
||||||
'HTTP_X_AUTH_TOKEN': 'AUTH_t', 'swift.cache': fake_memcache},
|
|
||||||
lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('401'), result)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_middleware_success(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
req = Request.blank('/v/AUTH_cfa/c/o',
|
|
||||||
headers={'x-auth-token': 'AUTH_t'})
|
|
||||||
req.environ['swift.cache'] = FakeMemcache()
|
|
||||||
result = ''.join(self.test_auth(req.environ, start_response))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_middleware_no_header(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
req = Request.blank('/v/AUTH_cfa/c/o')
|
|
||||||
req.environ['swift.cache'] = FakeMemcache()
|
|
||||||
result = ''.join(self.test_auth(req.environ, start_response))
|
|
||||||
self.assert_(result.startswith('401'), result)
|
|
||||||
self.assert_(not req.remote_user, req.remote_user)
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_middleware_storage_token(self):
|
|
||||||
old_http_connect = auth.http_connect
|
|
||||||
try:
|
|
||||||
auth.http_connect = mock_http_connect(204,
|
|
||||||
{'x-auth-ttl': '1234', 'x-auth-groups': 'act:usr,act,AUTH_cfa'})
|
|
||||||
req = Request.blank('/v/AUTH_cfa/c/o',
|
|
||||||
headers={'x-storage-token': 'AUTH_t'})
|
|
||||||
req.environ['swift.cache'] = FakeMemcache()
|
|
||||||
result = ''.join(self.test_auth(req.environ, start_response))
|
|
||||||
self.assert_(result.startswith('204'), result)
|
|
||||||
self.assertEquals(req.remote_user, 'act:usr,act,AUTH_cfa')
|
|
||||||
finally:
|
|
||||||
auth.http_connect = old_http_connect
|
|
||||||
|
|
||||||
def test_authorize_bad_path(self):
|
|
||||||
req = Request.blank('/badpath')
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 401)
|
|
||||||
req = Request.blank('/badpath')
|
|
||||||
req.remote_user = 'act:usr,act,AUTH_cfa'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
req = Request.blank('')
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 404)
|
|
||||||
req = Request.blank('')
|
|
||||||
req.environ['swift.cache'] = FakeMemcache()
|
|
||||||
result = ''.join(self.test_auth(req.environ, lambda x, y: None))
|
|
||||||
self.assert_(result.startswith('404'), result)
|
|
||||||
|
|
||||||
def test_authorize_account_access(self):
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act,AUTH_cfa'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
|
|
||||||
def test_authorize_acl_group_access(self):
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
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_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.acl = 'act'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.acl = 'act:usr'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.acl = 'act2'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.acl = 'act:usr2'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
|
|
||||||
def test_deny_cross_reseller(self):
|
|
||||||
# Tests that cross-reseller is denied, even if ACLs/group names match
|
|
||||||
req = Request.blank('/v1/OTHER_cfa')
|
|
||||||
req.remote_user = 'act:usr,act,AUTH_cfa'
|
|
||||||
req.acl = 'act'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
|
|
||||||
def test_authorize_acl_referrer_access(self):
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
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_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.acl = '.r:*'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.acl = '.r:.example.com'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.remote_user = 'act:usr,act'
|
|
||||||
req.referer = 'http://www.example.com/index.html'
|
|
||||||
req.acl = '.r:.example.com'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 401)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.acl = '.r:*'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.acl = '.r:.example.com'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 401)
|
|
||||||
req = Request.blank('/v1/AUTH_cfa')
|
|
||||||
req.referer = 'http://www.example.com/index.html'
|
|
||||||
req.acl = '.r:.example.com'
|
|
||||||
self.assertEquals(self.test_auth.authorize(req), None)
|
|
||||||
|
|
||||||
def test_account_put_permissions(self):
|
|
||||||
req = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
|
|
||||||
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': 'PUT'})
|
|
||||||
req.remote_user = 'act:usr,act,AUTH_other'
|
|
||||||
resp = self.test_auth.authorize(req)
|
|
||||||
self.assertEquals(resp and resp.status_int, 403)
|
|
||||||
|
|
||||||
# Even PUTs to your own account as account admin should fail
|
|
||||||
req = Request.blank('/v1/AUTH_old', environ={'REQUEST_METHOD': 'PUT'})
|
|
||||||
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': '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 = Request.blank('/v1/AUTH_new', environ={'REQUEST_METHOD': 'PUT'})
|
|
||||||
req.remote_user = 'act:usr,act,.super_admin'
|
|
||||||
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()
|
|
Loading…
Reference in New Issue
Block a user