From b76348a384c287d3abe5a7aa6b9ab827d7a40903 Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Wed, 22 Jun 2011 16:17:28 -0500 Subject: Issue 31: Switching default ports to 5000/5001 (public/admin) --- README.md | 18 +++++++++--------- bin/keystone | 2 +- bin/sampledata.sh | 2 +- doc/guide/src/docbkx/identity.wadl | 2 +- etc/keystone.conf | 4 ++-- examples/echo/echo/echo.ini | 4 ++-- examples/echo/echo/echo_basic.ini | 4 ++-- examples/echo/echo/echo_remote.ini | 2 +- examples/echo/echo_client.py | 3 +-- examples/paste/auth_token.ini | 2 +- examples/paste/nova-api-paste.ini | 2 +- keystone/common/config.py | 4 ++-- keystone/test/IdentitySOAPUI.xml | 8 ++++---- keystone/test/unit/test_common.py | 4 ++-- 14 files changed, 30 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 16257e2f..f5a52fdc 100644 --- a/README.md +++ b/README.md @@ -196,15 +196,15 @@ in troubleshooting:
     # Get an unscoped token
     
-    $ curl -d '{"passwordCredentials": {"username": "joeuser", "password": "secrete"}}' -H "Content-type: application/json" http://localhost:8080/v2.0/tokens
+    $ curl -d '{"passwordCredentials": {"username": "joeuser", "password": "secrete"}}' -H "Content-type: application/json" http://localhost:5000/v2.0/tokens
 
     # Get a token for a tenant
 
-    $ curl -d '{"passwordCredentials": {"username": "joeuser", "password": "secrete", "tenantId": "1234"}}' -H "Content-type: application/json" http://localhost:8080/v2.0/tokens
+    $ curl -d '{"passwordCredentials": {"username": "joeuser", "password": "secrete", "tenantId": "1234"}}' -H "Content-type: application/json" http://localhost:5000/v2.0/tokens
 
     # Get an admin token
 
-    $ curl -d '{"passwordCredentials": {"username": "admin", "password": "secrete"}}' -H "Content-type: application/json" http://localhost:8081/v2.0/tokens
+    $ curl -d '{"passwordCredentials": {"username": "admin", "password": "secrete"}}' -H "Content-type: application/json" http://localhost:5001/v2.0/tokens
 
#### Load Testing @@ -216,7 +216,7 @@ in troubleshooting: # Call Apache Bench - $ ab -c 30 -n 1000 -T "application/json" -p post_data http://127.0.0.1:8081/v2.0/tokens + $ ab -c 30 -n 1000 -T "application/json" -p post_data http://127.0.0.1:5001/v2.0/tokens ## NOVA Integration @@ -258,8 +258,8 @@ Assuming you added the test data using bin/sampledata.sh, you can then use joeus $ cd ~/keystone/bin && ./keystone Starting the Legacy Authentication component - Service API listening on 0.0.0.0:8080 - Admin API listening on 0.0.0.0:8081 + Service API listening on 0.0.0.0:5000 + Admin API listening on 0.0.0.0:5001 4. In another window, edit the `~/keystone/bin/sampledata.sh` file, find the `public.cloudfiles.com` text and replace it with the URL to your Swift @@ -289,7 +289,7 @@ Assuming you added the test data using bin/sampledata.sh, you can then use joeus use = egg:keystone#tokenauth auth_protocol = http auth_host = 127.0.0.1 - auth_port = 8081 + auth_port = 5001 admin_token = 999888777666 delay_auth_decision = 0 service_protocol = http @@ -313,8 +313,8 @@ Assuming you added the test data using bin/sampledata.sh, you can then use joeus container or upload something as your first action to have the account created; there's a Swift bug to be fixed soon): - $ st -A http://127.0.0.1:8080/v1.0 -U joeuser -K secrete post container - $ st -A http://127.0.0.1:8080/v1.0 -U joeuser -K secrete stat -v + $ st -A http://127.0.0.1:5000/v1.0 -U joeuser -K secrete post container + $ st -A http://127.0.0.1:5000/v1.0 -U joeuser -K secrete stat -v StorageURL: http://127.0.0.1:8888/v1/AUTH_1234 Auth Token: 74ce1b05-e839-43b7-bd76-85ef178726c3 Account: AUTH_1234 diff --git a/bin/keystone b/bin/keystone index 30c5653c..3349f634 100755 --- a/bin/keystone +++ b/bin/keystone @@ -48,7 +48,7 @@ if __name__ == '__main__': # Handle a special argument to support starting two endpoints common_group.add_option( '-a', '--admin-port', dest="admin_port", metavar="PORT", - help="specifies port for Admin API to listen on (default is 8081)") + help="specifies port for Admin API to listen on (default is 5001)") # Parse arguments and load config (options, args) = config.parse_options(parser) diff --git a/bin/sampledata.sh b/bin/sampledata.sh index 3c22dc2f..0b2fe8bd 100755 --- a/bin/sampledata.sh +++ b/bin/sampledata.sh @@ -43,7 +43,7 @@ `dirname $0`/keystone-manage $* endpointTemplates add RegionOne nova http://nova.publicinternets.com/v1.1/ http://127.0.0.1:8774/v1.1 http://localhost:8774/v1.1 1 `dirname $0`/keystone-manage $* endpointTemplates add RegionOne glance http://glance.publicinternets.com/v1.1/%tenant_id% http://nova.admin-nets.local/v1.1/%tenant_id% http://127.0.0.1:9292/v1.1/%tenant_id% 1 `dirname $0`/keystone-manage $* endpointTemplates add RegionOne cdn http://cdn.publicinternets.com/v1.1/%tenant_id% http://cdn.admin-nets.local/v1.1/%tenant_id% http://127.0.0.1:7777/v1.1/%tenant_id% 1 -`dirname $0`/keystone-manage $* endpointTemplates add RegionOne keystone http://keystone.publicinternets.com/v2.0 http://127.0.0.1:8081/v2.0 http://127.0.0.1:8080/v2.0 1 +`dirname $0`/keystone-manage $* endpointTemplates add RegionOne keystone http://keystone.publicinternets.com/v2.0 http://127.0.0.1:5001/v2.0 http://127.0.0.1:5000/v2.0 1 diff --git a/doc/guide/src/docbkx/identity.wadl b/doc/guide/src/docbkx/identity.wadl index b153904d..2d1755ee 100644 --- a/doc/guide/src/docbkx/identity.wadl +++ b/doc/guide/src/docbkx/identity.wadl @@ -17,7 +17,7 @@ - + diff --git a/etc/keystone.conf b/etc/keystone.conf index 8efa5d7e..c2f30556 100755 --- a/etc/keystone.conf +++ b/etc/keystone.conf @@ -32,7 +32,7 @@ service-header-mappings = {'nova' : 'X-Server-Management-Url' , 'swift' : 'X-Sto server_bind_host = 0.0.0.0 # Port the bind the API server to -server_bind_port = 8080 +server_bind_port = 5000 [app:admin] paste.app_factory = keystone.server:admin_app_factory @@ -40,7 +40,7 @@ paste.app_factory = keystone.server:admin_app_factory bind_host = 0.0.0.0 # Port the bind the Admin API server to -bind_port = 8081 +bind_port = 5001 [app:server] paste.app_factory = keystone.server:app_factory diff --git a/examples/echo/echo/echo.ini b/examples/echo/echo/echo.ini index a7b95e9e..cc21b947 100644 --- a/examples/echo/echo/echo.ini +++ b/examples/echo/echo/echo.ini @@ -6,7 +6,7 @@ delay_auth_decision = 0 ;where to find the OpenStack service (if not in local WSGI chain) service_protocol = http service_host = 127.0.0.1 -service_port = 8081 +service_port = 5001 ;used to verify this component with the OpenStack service (or PAPIAuth) service_pass = dTpw @@ -23,7 +23,7 @@ pipeline = paste.filter_factory = keystone.auth_protocols.auth_token:filter_factory ;where to find the token auth service auth_host = 127.0.0.1 -auth_port = 8081 +auth_port = 5001 auth_protocol = http ;how to authenticate to the auth service for priviledged operations ;like validate token diff --git a/examples/echo/echo/echo_basic.ini b/examples/echo/echo/echo_basic.ini index 65c85357..e1c7a42a 100644 --- a/examples/echo/echo/echo_basic.ini +++ b/examples/echo/echo/echo_basic.ini @@ -6,7 +6,7 @@ delay_auth_decision = 0 ;where to find the OpenStack service (if not in local WSGI chain) service_protocol = http service_host = 127.0.0.1 -service_port = 8081 +service_port = 5001 ;used to verify this component with the OpenStack service (or PAPIAuth) service_pass = dTpw @@ -23,7 +23,7 @@ pipeline = paste.filter_factory = keystone.auth_protocols.auth_token:filter_factory ;where to find the token auth service auth_host = 127.0.0.1 -auth_port = 8081 +auth_port = 5001 auth_protocol = http ;how to authenticate to the auth service for priviledged operations ;like validate token diff --git a/examples/echo/echo/echo_remote.ini b/examples/echo/echo/echo_remote.ini index d225f756..a429485d 100644 --- a/examples/echo/echo/echo_remote.ini +++ b/examples/echo/echo/echo_remote.ini @@ -14,6 +14,6 @@ paste.filter_factory = keystone.middleware.remoteauth:filter_factory ; (otherwise we redirect call) remote_auth_pass = dTpw ;where to redirect untrusted calls to -auth_location = http://127.0.0.1:8081/ +auth_location = http://127.0.0.1:5001/ diff --git a/examples/echo/echo_client.py b/examples/echo/echo_client.py index ce4e8da0..256c55ec 100755 --- a/examples/echo/echo_client.py +++ b/examples/echo/echo_client.py @@ -20,7 +20,6 @@ Implement a client for Echo service using Identity service import httplib import json -import sys def get_auth_token(username, password, tenant): @@ -28,7 +27,7 @@ def get_auth_token(username, password, tenant): params = {"passwordCredentials": {"username": username, "password": password, "tenantId": tenant}} - conn = httplib.HTTPConnection("localhost:8080") + conn = httplib.HTTPConnection("localhost:5000") conn.request("POST", "/v2.0/tokens", json.dumps(params), headers=headers) response = conn.getresponse() data = response.read() diff --git a/examples/paste/auth_token.ini b/examples/paste/auth_token.ini index 25f2cbbf..b2899e36 100644 --- a/examples/paste/auth_token.ini +++ b/examples/paste/auth_token.ini @@ -5,7 +5,7 @@ paste.app_factory = auth_token:app_factory auth_protocol = http auth_host = 127.0.0.1 -auth_port = 8081 +auth_port = 5001 admin_token = 999888777666 delay_auth_decision = 0 diff --git a/examples/paste/nova-api-paste.ini b/examples/paste/nova-api-paste.ini index b4a94557..9836b29e 100644 --- a/examples/paste/nova-api-paste.ini +++ b/examples/paste/nova-api-paste.ini @@ -88,7 +88,7 @@ service_protocol = http service_host = 127.0.0.1 service_port = 808 auth_host = 127.0.0.1 -auth_port = 8081 +auth_port = 5001 auth_protocol = http admin_token = 999888777666 diff --git a/keystone/common/config.py b/keystone/common/config.py index 71925239..592e6cdd 100755 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -76,9 +76,9 @@ def add_common_options(parser): "argument specified to be a config file, and if "\ "that is also missing, we search standard "\ "directories for a config file.") - group.add_option('-p', '--port', '--bind-port', default=8080, + group.add_option('-p', '--port', '--bind-port', default=5000, dest="bind_port", - help="specifies port to listen on (default is 8080)") + help="specifies port to listen on (default is 5000)") group.add_option('--host', '--bind-host', default="0.0.0.0", dest="bind_host", help="specifies host address to listen on "\ diff --git a/keystone/test/IdentitySOAPUI.xml b/keystone/test/IdentitySOAPUI.xml index 3cf2984e..de072afb 100644 --- a/keystone/test/IdentitySOAPUI.xml +++ b/keystone/test/IdentitySOAPUI.xml @@ -34,7 +34,7 @@ - + @@ -1094,17 +1094,17 @@ -]]>http://www.w3.org/2001/XMLSchemahttp://localhost:8080aliasTEMPLATExs:stringapplication/xml200 203v1:extensionapplication/json200 203application/xml400v1:badRequestapplication/xml404v1:itemNotFoundapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 404 500 503<xml-fragment/>http://localhost:8080application/xml200 203v1:extensionsapplication/json200 203application/xml400v1:badRequestapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 500 503<xml-fragment/>http://localhost:8080X-Auth-TokenHEADERxs:stringtokenIdTEMPLATExs:stringbelongsToQUERYxs:stringapplication/xml200 203v1:authapplication/json200 203application/xml401v1:unauthorizedapplication/xml403v1:forbiddenapplication/xml403v1:userDisabledapplication/xml400v1:badRequestapplication/xml404v1:itemNotFoundapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 401 403 404 500 503<xml-fragment/>http://localhost:8080 +]]>http://www.w3.org/2001/XMLSchemahttp://localhost:5000aliasTEMPLATExs:stringapplication/xml200 203v1:extensionapplication/json200 203application/xml400v1:badRequestapplication/xml404v1:itemNotFoundapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 404 500 503<xml-fragment/>http://localhost:8080application/xml200 203v1:extensionsapplication/json200 203application/xml400v1:badRequestapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 500 503<xml-fragment/>http://localhost:8080X-Auth-TokenHEADERxs:stringtokenIdTEMPLATExs:stringbelongsToQUERYxs:stringapplication/xml200 203v1:authapplication/json200 203application/xml401v1:unauthorizedapplication/xml403v1:forbiddenapplication/xml403v1:userDisabledapplication/xml400v1:badRequestapplication/xml404v1:itemNotFoundapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 401 403 404 500 503<xml-fragment/>http://localhost:8080 -application/xml401v1:unauthorizedapplication/xml403v1:forbiddenapplication/xml400v1:badRequestapplication/xml404v1:itemNotFoundapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 401 403 404 500 503<xml-fragment/>http://localhost:8080 +application/xml401v1:unauthorizedapplication/xml403v1:forbiddenapplication/xml400v1:badRequestapplication/xml404v1:itemNotFoundapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json400 401 403 404 500 503<xml-fragment/>http://localhost:5000 application/xmlv1:passwordCredentialsapplication/jsonapplication/xml200 203v1:authapplication/json200 203application/xml401v1:unauthorizedapplication/xml403v1:userDisabledapplication/xml400v1:badRequestapplication/xml500v1:identityFaultapplication/xml503v1:serviceUnavailableapplication/json401 -403 400 500 503<xml-fragment/>http://localhost:8080<passwordCredentials +403 400 500 503<xml-fragment/>http://localhost:5000<passwordCredentials password="secrete" username="joeuser" xmlns="http://docs.openstack.org/identity/api/v2.0"/>X-Auth-TokenHEADERxs:stringtenantIdTEMPLATExs:stringapplication/xml200 203v1:tenantapplication/json200 diff --git a/keystone/test/unit/test_common.py b/keystone/test/unit/test_common.py index 06409937..706d59f7 100755 --- a/keystone/test/unit/test_common.py +++ b/keystone/test/unit/test_common.py @@ -26,8 +26,8 @@ import unittest -URL = 'http://localhost:8081/v2.0/' -URLv1 = 'http://localhost:8080/v1.0/' +URL = 'http://localhost:5001/v2.0/' +URLv1 = 'http://localhost:5000/v1.0/' def get_token(user, pswd, tenant_id, kind=''): header = httplib2.Http(".cache") -- cgit From 6d2b51e8711ee96212efbdb65c2747a11b16ebf6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 22 Jun 2011 17:27:01 -0400 Subject: Swift-specific middleware. --- keystone/auth_protocols/swift_auth_token.py | 239 ++++++++++++++++++++++++++++ setup.py | 1 + 2 files changed, 240 insertions(+) create mode 100755 keystone/auth_protocols/swift_auth_token.py diff --git a/keystone/auth_protocols/swift_auth_token.py b/keystone/auth_protocols/swift_auth_token.py new file mode 100755 index 00000000..77aa0226 --- /dev/null +++ b/keystone/auth_protocols/swift_auth_token.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# 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. +# Not Yet PEP8 standardized + + +""" +TOKEN-BASED AUTH MIDDLEWARE FOR SWIFT + +Authentication on incoming request + * grab token from X-Auth-Token header + * TODO: grab the memcache servers from the request env + * TODOcheck for auth information in memcache + * check for auth information from keystone + * return if unauthorized + * decorate the request for authorization in swift + * forward to the swift proxy app + +Authorization via callback + * check the path and extract the tenant + * get the auth information stored in keystone.identity during authentication + * TODO: check if the user is an account admin or a reseller admin + * determine what object-type to authorize (account, container, object) + * use knowledge of tenant, admin status, and container acls to authorize + +""" + +import json +from urlparse import urlparse +from webob.exc import HTTPUnauthorized, HTTPNotFound, HTTPExpectationFailed + +from keystone.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, get_logger, split_path + + +PROTOCOL_NAME = "Swift Token Authentication" + + +class AuthProtocol(object): + """Handles authenticating and aurothrizing client calls. + + Add to your pipeline in paste config like: + + [pipeline:main] + pipeline = catch_errors healthcheck cache keystone proxy-server + + [filter:keystone] + use = egg:keystone#swiftauth + keystone_url = http://127.0.0.1:8080 + keystone_admin_token = 999888777666 + + """ + + def __init__(self, app, conf): + """Store valuable bits from the conf and set up logging.""" + self.app = app + self.keystone_url = urlparse(conf.get('keystone_url')) + self.admin_token = conf.get('keystone_admin_token') + self.reseller_prefix = conf.get('reseller_prefix', 'AUTH') + self.log = get_logger(conf, log_route='keystone') + self.log.info('Keystone middleware started') + + def __call__(self, env, start_response): + """Authenticate the incoming request. + + If authentication fails return an appropriate http status here, + otherwise forward through the rest of the app. + + """ + + self.log.debug('Keystone middleware called') + token = self._get_claims(env) + self.log.debug('token: %s', token) + if token: + identity = self._validate_claims(token) + if identity: + self.log.debug('request authenticated: %r', identity) + return self.perform_authenticated_request(identity, env, + start_response) + else: + self.log.debug('anonymous request') + return self.unauthorized_request(env, start_response) + self.log.debug('no auth token in request headers') + return self.perform_unidentified_request(env, start_response) + + def unauthorized_request(self, env, start_response): + """Clinet provided a token that wasn't acceptable, error out.""" + return HTTPUnauthorized()(env, start_response) + + def unauthorized(self, req): + """Return unauthorized given a webob Request object. + + This can be stuffed into the evironment for swift.authorize or + called from the authoriztion callback when authorization fails. + + """ + return HTTPUnauthorized(request=req) + + def perform_authenticated_request(self, identity, env, start_response): + """Client provieded a valid identity, so use it for authorization.""" + env['keystone.identity'] = identity + env['swift.authorize'] = self.authorize + env['swift.clean_acl'] = clean_acl + self.log.debug('calling app: %s // %r', start_response, env) + rv = self.app(env, start_response) + self.log.debug('return from app: %r', rv) + return rv + + def perform_unidentified_request(self, env, start_response): + """Withouth authentication data, use acls for access control.""" + env['swift.authorize'] = self.authorize_via_acl + env['swift.clean_acl'] = self.authorize_via_acl + return self.app(env, start_response) + + def authorize(self, req): + """Used when we have a valid identity from keystone.""" + self.log.debug('keystone middleware authorization begin') + env = req.environ + tenant = env.get('keystone.identity', {}).get('tenant') + if not tenant: + self.log.warn('identity info not present in authorize request') + return HTTPExpectationFailed('Unable to locate auth claim', + request=req) + # TODO(todd): everyone under a tenant can do anything to that tenant. + # more realistic would be role/group checking to do things + # like deleting the account or creating/deleting containers + # esp. when owned by other users in the same tenant. + if req.path.startswith('/v1/%s_%s' % (self.reseller_prefix, tenant)): + self.log.debug('AUTHORIZED OKAY') + return None + + self.log.debug('tenant mismatch: %r', tenant) + return self.unauthorized(req) + + def authorize_via_acl(self, req): + """Anon request handling. + + For now this only allows anon read of objects. Container and account + actions are prohibited. + + """ + + self.log.debug('authorizing anonymous request') + try: + version, account, container, obj = split_path(req.path, 1, 4, True) + except ValueError: + return HTTPNotFound(request=req) + + if obj: + return self._authorize_anon_object(req, account, container, obj) + + if container: + return self._authorize_anon_container(req, account, container) + + if account: + return self._authorize_anon_account(req, account) + + return self._authorize_anon_toplevel(req) + + def _authorize_anon_object(self, req, account, container, obj): + referrers, groups = parse_acl(getattr(req, 'acl', None)) + if referrer_allowed(req.referer, referrers): + self.log.debug('anonymous request AUTHORIZED OKAY') + return None + return self.unauthorized(req) + + def _authorize_anon_container(self, req, account, container): + return self.unauthorized(req) + + def _authorize_anon_account(self, req, account): + return self.unauthorized(req) + + def _authorize_anon_toplevel(self, req): + return self.unauthorized(req) + + def _get_claims(self, env): + claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) + return claims + + def _validate_claims(self, claims): + """Ask keystone (as keystone admin) for information for this user.""" + + # TODO(todd): cache + + self.log.debug('Asking keystone to validate token') + headers = {"Content-type": "application/json", + "Accept": "text/json", + "X-Auth-Token": self.admin_token} + self.log.debug('headers: %r', headers) + self.log.debug('url: %s', self.keystone_url) + conn = http_connect(self.keystone_url.hostname, self.keystone_url.port, + 'GET', '/v2.0/tokens/%s' % claims, headers=headers) + resp = conn.getresponse() + data = resp.read() + conn.close() + + # Check http status code for the "OK" family of responses + if not str(resp.status).startswith('20'): + return False + + identity_info = json.loads(data) + roles = [] + role_refs = identity_info["auth"]["user"]["roleRefs"] + if role_refs is not None: + for role_ref in role_refs: + roles.append(role_ref["roleId"]) + + # TODO(Ziad): add groups back in + identity = {'user': identity_info['auth']['user']['username'], + 'tenant': identity_info['auth']['user']['tenantId'], + 'roles':roles} + + return identity + + +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 AuthProtocol(app, conf) + return auth_filter + diff --git a/setup.py b/setup.py index 6e366ac6..42e8ad1b 100644 --- a/setup.py +++ b/setup.py @@ -59,6 +59,7 @@ setup( 'paste.filter_factory': [ 'remoteauth=keystone.middleware.remoteauth:remoteauth_factory', 'tokenauth=keystone.auth_protocols.auth_token:filter_factory', + 'swiftauth=keystone.auth_protocols.swift_auth_token:filter_factory', ], }, ) -- cgit From dbbc17b8a5ae96152296eb893a2635909e785bcc Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 22 Jun 2011 19:14:07 -0400 Subject: Remove swift-y bits from generic token auth. --- keystone/auth_protocols/auth_token.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/keystone/auth_protocols/auth_token.py b/keystone/auth_protocols/auth_token.py index 107d688b..95049213 100755 --- a/keystone/auth_protocols/auth_token.py +++ b/keystone/auth_protocols/auth_token.py @@ -159,8 +159,6 @@ class AuthProtocol(object): claims = self._expound_claims() # Store authentication data - self.env['keystone.claims'] = claims - self.env['swift.authorize'] = self.authorize if claims: # TODO(Ziad): add additional details we may need, # like tenant and group info @@ -185,17 +183,6 @@ class AuthProtocol(object): #Send request downstream return self._forward_request() - def authorize(self, req): - env = req.environ - tenant = env.get('keystone.claims', {}).get('tenant') - if not tenant: - return HTTPExpectationFailed('Unable to locate auth claim', - request=req) - if req.path.startswith('/v1/AUTH_%s' % tenant): - return None - return HTTPUnauthorized(request=req) - - # NOTE(todd): unused def get_admin_auth_token(self, username, password, tenant): """ -- cgit From 4d6e13e528b91e7dfe502e582cc68144a9a9518e Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 22 Jun 2011 19:14:26 -0400 Subject: Make swift middleware live where it should. --- keystone/auth_protocols/swift_auth_token.py | 239 ---------------------------- keystone/middleware/swift_auth.py | 239 ++++++++++++++++++++++++++++ setup.py | 2 +- 3 files changed, 240 insertions(+), 240 deletions(-) delete mode 100755 keystone/auth_protocols/swift_auth_token.py create mode 100755 keystone/middleware/swift_auth.py diff --git a/keystone/auth_protocols/swift_auth_token.py b/keystone/auth_protocols/swift_auth_token.py deleted file mode 100755 index 77aa0226..00000000 --- a/keystone/auth_protocols/swift_auth_token.py +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# 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. -# Not Yet PEP8 standardized - - -""" -TOKEN-BASED AUTH MIDDLEWARE FOR SWIFT - -Authentication on incoming request - * grab token from X-Auth-Token header - * TODO: grab the memcache servers from the request env - * TODOcheck for auth information in memcache - * check for auth information from keystone - * return if unauthorized - * decorate the request for authorization in swift - * forward to the swift proxy app - -Authorization via callback - * check the path and extract the tenant - * get the auth information stored in keystone.identity during authentication - * TODO: check if the user is an account admin or a reseller admin - * determine what object-type to authorize (account, container, object) - * use knowledge of tenant, admin status, and container acls to authorize - -""" - -import json -from urlparse import urlparse -from webob.exc import HTTPUnauthorized, HTTPNotFound, HTTPExpectationFailed - -from keystone.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, get_logger, split_path - - -PROTOCOL_NAME = "Swift Token Authentication" - - -class AuthProtocol(object): - """Handles authenticating and aurothrizing client calls. - - Add to your pipeline in paste config like: - - [pipeline:main] - pipeline = catch_errors healthcheck cache keystone proxy-server - - [filter:keystone] - use = egg:keystone#swiftauth - keystone_url = http://127.0.0.1:8080 - keystone_admin_token = 999888777666 - - """ - - def __init__(self, app, conf): - """Store valuable bits from the conf and set up logging.""" - self.app = app - self.keystone_url = urlparse(conf.get('keystone_url')) - self.admin_token = conf.get('keystone_admin_token') - self.reseller_prefix = conf.get('reseller_prefix', 'AUTH') - self.log = get_logger(conf, log_route='keystone') - self.log.info('Keystone middleware started') - - def __call__(self, env, start_response): - """Authenticate the incoming request. - - If authentication fails return an appropriate http status here, - otherwise forward through the rest of the app. - - """ - - self.log.debug('Keystone middleware called') - token = self._get_claims(env) - self.log.debug('token: %s', token) - if token: - identity = self._validate_claims(token) - if identity: - self.log.debug('request authenticated: %r', identity) - return self.perform_authenticated_request(identity, env, - start_response) - else: - self.log.debug('anonymous request') - return self.unauthorized_request(env, start_response) - self.log.debug('no auth token in request headers') - return self.perform_unidentified_request(env, start_response) - - def unauthorized_request(self, env, start_response): - """Clinet provided a token that wasn't acceptable, error out.""" - return HTTPUnauthorized()(env, start_response) - - def unauthorized(self, req): - """Return unauthorized given a webob Request object. - - This can be stuffed into the evironment for swift.authorize or - called from the authoriztion callback when authorization fails. - - """ - return HTTPUnauthorized(request=req) - - def perform_authenticated_request(self, identity, env, start_response): - """Client provieded a valid identity, so use it for authorization.""" - env['keystone.identity'] = identity - env['swift.authorize'] = self.authorize - env['swift.clean_acl'] = clean_acl - self.log.debug('calling app: %s // %r', start_response, env) - rv = self.app(env, start_response) - self.log.debug('return from app: %r', rv) - return rv - - def perform_unidentified_request(self, env, start_response): - """Withouth authentication data, use acls for access control.""" - env['swift.authorize'] = self.authorize_via_acl - env['swift.clean_acl'] = self.authorize_via_acl - return self.app(env, start_response) - - def authorize(self, req): - """Used when we have a valid identity from keystone.""" - self.log.debug('keystone middleware authorization begin') - env = req.environ - tenant = env.get('keystone.identity', {}).get('tenant') - if not tenant: - self.log.warn('identity info not present in authorize request') - return HTTPExpectationFailed('Unable to locate auth claim', - request=req) - # TODO(todd): everyone under a tenant can do anything to that tenant. - # more realistic would be role/group checking to do things - # like deleting the account or creating/deleting containers - # esp. when owned by other users in the same tenant. - if req.path.startswith('/v1/%s_%s' % (self.reseller_prefix, tenant)): - self.log.debug('AUTHORIZED OKAY') - return None - - self.log.debug('tenant mismatch: %r', tenant) - return self.unauthorized(req) - - def authorize_via_acl(self, req): - """Anon request handling. - - For now this only allows anon read of objects. Container and account - actions are prohibited. - - """ - - self.log.debug('authorizing anonymous request') - try: - version, account, container, obj = split_path(req.path, 1, 4, True) - except ValueError: - return HTTPNotFound(request=req) - - if obj: - return self._authorize_anon_object(req, account, container, obj) - - if container: - return self._authorize_anon_container(req, account, container) - - if account: - return self._authorize_anon_account(req, account) - - return self._authorize_anon_toplevel(req) - - def _authorize_anon_object(self, req, account, container, obj): - referrers, groups = parse_acl(getattr(req, 'acl', None)) - if referrer_allowed(req.referer, referrers): - self.log.debug('anonymous request AUTHORIZED OKAY') - return None - return self.unauthorized(req) - - def _authorize_anon_container(self, req, account, container): - return self.unauthorized(req) - - def _authorize_anon_account(self, req, account): - return self.unauthorized(req) - - def _authorize_anon_toplevel(self, req): - return self.unauthorized(req) - - def _get_claims(self, env): - claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - return claims - - def _validate_claims(self, claims): - """Ask keystone (as keystone admin) for information for this user.""" - - # TODO(todd): cache - - self.log.debug('Asking keystone to validate token') - headers = {"Content-type": "application/json", - "Accept": "text/json", - "X-Auth-Token": self.admin_token} - self.log.debug('headers: %r', headers) - self.log.debug('url: %s', self.keystone_url) - conn = http_connect(self.keystone_url.hostname, self.keystone_url.port, - 'GET', '/v2.0/tokens/%s' % claims, headers=headers) - resp = conn.getresponse() - data = resp.read() - conn.close() - - # Check http status code for the "OK" family of responses - if not str(resp.status).startswith('20'): - return False - - identity_info = json.loads(data) - roles = [] - role_refs = identity_info["auth"]["user"]["roleRefs"] - if role_refs is not None: - for role_ref in role_refs: - roles.append(role_ref["roleId"]) - - # TODO(Ziad): add groups back in - identity = {'user': identity_info['auth']['user']['username'], - 'tenant': identity_info['auth']['user']['tenantId'], - 'roles':roles} - - return identity - - -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 AuthProtocol(app, conf) - return auth_filter - diff --git a/keystone/middleware/swift_auth.py b/keystone/middleware/swift_auth.py new file mode 100755 index 00000000..77aa0226 --- /dev/null +++ b/keystone/middleware/swift_auth.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# 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. +# Not Yet PEP8 standardized + + +""" +TOKEN-BASED AUTH MIDDLEWARE FOR SWIFT + +Authentication on incoming request + * grab token from X-Auth-Token header + * TODO: grab the memcache servers from the request env + * TODOcheck for auth information in memcache + * check for auth information from keystone + * return if unauthorized + * decorate the request for authorization in swift + * forward to the swift proxy app + +Authorization via callback + * check the path and extract the tenant + * get the auth information stored in keystone.identity during authentication + * TODO: check if the user is an account admin or a reseller admin + * determine what object-type to authorize (account, container, object) + * use knowledge of tenant, admin status, and container acls to authorize + +""" + +import json +from urlparse import urlparse +from webob.exc import HTTPUnauthorized, HTTPNotFound, HTTPExpectationFailed + +from keystone.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, get_logger, split_path + + +PROTOCOL_NAME = "Swift Token Authentication" + + +class AuthProtocol(object): + """Handles authenticating and aurothrizing client calls. + + Add to your pipeline in paste config like: + + [pipeline:main] + pipeline = catch_errors healthcheck cache keystone proxy-server + + [filter:keystone] + use = egg:keystone#swiftauth + keystone_url = http://127.0.0.1:8080 + keystone_admin_token = 999888777666 + + """ + + def __init__(self, app, conf): + """Store valuable bits from the conf and set up logging.""" + self.app = app + self.keystone_url = urlparse(conf.get('keystone_url')) + self.admin_token = conf.get('keystone_admin_token') + self.reseller_prefix = conf.get('reseller_prefix', 'AUTH') + self.log = get_logger(conf, log_route='keystone') + self.log.info('Keystone middleware started') + + def __call__(self, env, start_response): + """Authenticate the incoming request. + + If authentication fails return an appropriate http status here, + otherwise forward through the rest of the app. + + """ + + self.log.debug('Keystone middleware called') + token = self._get_claims(env) + self.log.debug('token: %s', token) + if token: + identity = self._validate_claims(token) + if identity: + self.log.debug('request authenticated: %r', identity) + return self.perform_authenticated_request(identity, env, + start_response) + else: + self.log.debug('anonymous request') + return self.unauthorized_request(env, start_response) + self.log.debug('no auth token in request headers') + return self.perform_unidentified_request(env, start_response) + + def unauthorized_request(self, env, start_response): + """Clinet provided a token that wasn't acceptable, error out.""" + return HTTPUnauthorized()(env, start_response) + + def unauthorized(self, req): + """Return unauthorized given a webob Request object. + + This can be stuffed into the evironment for swift.authorize or + called from the authoriztion callback when authorization fails. + + """ + return HTTPUnauthorized(request=req) + + def perform_authenticated_request(self, identity, env, start_response): + """Client provieded a valid identity, so use it for authorization.""" + env['keystone.identity'] = identity + env['swift.authorize'] = self.authorize + env['swift.clean_acl'] = clean_acl + self.log.debug('calling app: %s // %r', start_response, env) + rv = self.app(env, start_response) + self.log.debug('return from app: %r', rv) + return rv + + def perform_unidentified_request(self, env, start_response): + """Withouth authentication data, use acls for access control.""" + env['swift.authorize'] = self.authorize_via_acl + env['swift.clean_acl'] = self.authorize_via_acl + return self.app(env, start_response) + + def authorize(self, req): + """Used when we have a valid identity from keystone.""" + self.log.debug('keystone middleware authorization begin') + env = req.environ + tenant = env.get('keystone.identity', {}).get('tenant') + if not tenant: + self.log.warn('identity info not present in authorize request') + return HTTPExpectationFailed('Unable to locate auth claim', + request=req) + # TODO(todd): everyone under a tenant can do anything to that tenant. + # more realistic would be role/group checking to do things + # like deleting the account or creating/deleting containers + # esp. when owned by other users in the same tenant. + if req.path.startswith('/v1/%s_%s' % (self.reseller_prefix, tenant)): + self.log.debug('AUTHORIZED OKAY') + return None + + self.log.debug('tenant mismatch: %r', tenant) + return self.unauthorized(req) + + def authorize_via_acl(self, req): + """Anon request handling. + + For now this only allows anon read of objects. Container and account + actions are prohibited. + + """ + + self.log.debug('authorizing anonymous request') + try: + version, account, container, obj = split_path(req.path, 1, 4, True) + except ValueError: + return HTTPNotFound(request=req) + + if obj: + return self._authorize_anon_object(req, account, container, obj) + + if container: + return self._authorize_anon_container(req, account, container) + + if account: + return self._authorize_anon_account(req, account) + + return self._authorize_anon_toplevel(req) + + def _authorize_anon_object(self, req, account, container, obj): + referrers, groups = parse_acl(getattr(req, 'acl', None)) + if referrer_allowed(req.referer, referrers): + self.log.debug('anonymous request AUTHORIZED OKAY') + return None + return self.unauthorized(req) + + def _authorize_anon_container(self, req, account, container): + return self.unauthorized(req) + + def _authorize_anon_account(self, req, account): + return self.unauthorized(req) + + def _authorize_anon_toplevel(self, req): + return self.unauthorized(req) + + def _get_claims(self, env): + claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) + return claims + + def _validate_claims(self, claims): + """Ask keystone (as keystone admin) for information for this user.""" + + # TODO(todd): cache + + self.log.debug('Asking keystone to validate token') + headers = {"Content-type": "application/json", + "Accept": "text/json", + "X-Auth-Token": self.admin_token} + self.log.debug('headers: %r', headers) + self.log.debug('url: %s', self.keystone_url) + conn = http_connect(self.keystone_url.hostname, self.keystone_url.port, + 'GET', '/v2.0/tokens/%s' % claims, headers=headers) + resp = conn.getresponse() + data = resp.read() + conn.close() + + # Check http status code for the "OK" family of responses + if not str(resp.status).startswith('20'): + return False + + identity_info = json.loads(data) + roles = [] + role_refs = identity_info["auth"]["user"]["roleRefs"] + if role_refs is not None: + for role_ref in role_refs: + roles.append(role_ref["roleId"]) + + # TODO(Ziad): add groups back in + identity = {'user': identity_info['auth']['user']['username'], + 'tenant': identity_info['auth']['user']['tenantId'], + 'roles':roles} + + return identity + + +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 AuthProtocol(app, conf) + return auth_filter + diff --git a/setup.py b/setup.py index 42e8ad1b..f936b418 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ setup( 'paste.filter_factory': [ 'remoteauth=keystone.middleware.remoteauth:remoteauth_factory', 'tokenauth=keystone.auth_protocols.auth_token:filter_factory', - 'swiftauth=keystone.auth_protocols.swift_auth_token:filter_factory', + 'swiftauth=keystone.middleware.swift_auth:filter_factory', ], }, ) -- cgit