From 87c469c630a32bdf407bfa569a5872943ec5fe9b Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Wed, 27 Apr 2011 12:16:15 -0700 Subject: Renamed delegated to 'delay_auth_decision' Remove PAPIAuth Rename folder to Auth_protocols (that is where we add protocol components)Get_request -> get_content Make protocol module more generic (prepare for superclassing and multiple protocol support Refactor Auth_protocol_token If no token, bail out quick (clearer) same with if app Break out headers: - here is what is coming in - here is what we add - explain the X in headers: extended header --- README.md | 6 +- echo/echo/echo.ini | 39 ++--- echo/echo/echo.py | 7 +- keystone/__init__.py | 12 +- keystone/auth_protocol/__init__.py | 0 keystone/auth_protocol/auth_protocol_token.ini | 15 -- keystone/auth_protocol/auth_protocol_token.py | 209 ------------------------- keystone/identity.py | 29 +++- keystone/middleware/papiauth.py | 13 +- 9 files changed, 71 insertions(+), 259 deletions(-) delete mode 100644 keystone/auth_protocol/__init__.py delete mode 100644 keystone/auth_protocol/auth_protocol_token.ini delete mode 100644 keystone/auth_protocol/auth_protocol_token.py diff --git a/README.md b/README.md index 89a27db2..7938cb8e 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,12 @@ SERVICES: --------- * Keystone - authentication service -* PAPIAuth - WSGI middleware that can be used in services (like Swift, Nova, and Glance) to perform authentication -* Auth_Protocal_Token - WSGI middleware that can be used ti handle token auth protocol (WSGI or remote proxy) +* Auth_Protocal_Token - WSGI middleware that can be used to handle token auth protocol (WSGI or remote proxy) * Echo - A sample service that responds by returning call details +Also included: +* Auth_Protocal_Basic - Stub for WSGI middleware that will be used to handle basic auth +* Auth_Protocal_OpenID - Stub for WSGI middleware that will be used to handle openid auth protocol DEPENDENCIES: ------------- diff --git a/echo/echo/echo.ini b/echo/echo/echo.ini index cadd3fb5..e4ac690f 100644 --- a/echo/echo/echo.ini +++ b/echo/echo/echo.ini @@ -1,24 +1,25 @@ [DEFAULT] +;delegated means we still allow unauthenticated requests through so the +;service can make the final decision on authentication +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 = 8090 +;used to verify this component with the OpenStack service (or PAPIAuth) +service_pass = dTpw + [app:echo] paste.app_factory = echo:app_factory [pipeline:main] pipeline = - auth - papiauth + tokenauth echo -;remove last entry to use the service_settings below if echo is remote - -[filter:papiauth] -paste.filter_factory = keystone:papiauth_factory -;password which will tell us caller is trusted (otherwise we redirect them) -auth_pass = dTpw -;where to redirect untrusted calls to -proxy_location = http://127.0.0.1:8080/ - -[filter:auth] +[filter:tokenauth] paste.filter_factory = keystone:tokenauth_factory ;where to find the token auth service auth_host = 127.0.0.1 @@ -27,14 +28,8 @@ auth_port = 8080 ;like validate token auth_token = 999888777666 -;delegated means we still allow unauthenticated requests through so the -;service can make the final decision on authentication -delegated = 0 - -;where to find the OpenStack service (if not in local WSGI chain) -service_protocol = http -service_host = 127.0.0.1 -service_port = 8090 -;used to verify this component with the OpenStack service (or PAPIAuth) -service_pass = dTpw +[filter:basicauth] +paste.filter_factory = keystone:basicauth_factory +[filter:openidauth] +paste.filter_factory = keystone:openidauth_factory diff --git a/echo/echo/echo.py b/echo/echo/echo.py index dada3392..b0fd9c7b 100644 --- a/echo/echo/echo.py +++ b/echo/echo/echo.py @@ -72,9 +72,10 @@ class EchoApp(object): def toDOM(self, environ): echo = etree.Element("{http://docs.openstack.org/echo/api/v1.0}echo", - method=environ["REQUEST_METHOD"], - pathInfo=environ["PATH_INFO"], - queryString=environ.get('QUERY_STRING', "")) + method= environ["REQUEST_METHOD"], + pathInfo= environ["PATH_INFO"], + queryString= environ.get('QUERY_STRING', ""), + caller_identity= self.envr['HTTP_X_AUTHORIZATION']) content = etree.Element( "{http://docs.openstack.org/echo/api/v1.0}content") content.set("type", environ["CONTENT_TYPE"]) diff --git a/keystone/__init__.py b/keystone/__init__.py index 3f30dd74..44c15ef3 100644 --- a/keystone/__init__.py +++ b/keystone/__init__.py @@ -1,3 +1,13 @@ from middleware.papiauth import filter_factory as papiauth_factory -from auth_protocol.auth_protocol_token \ + +#TOKEN AUTH +from auth_protocols.auth_protocol_token \ import filter_factory as tokenauth_factory + +#BASIC AUTH +from auth_protocols.auth_protocol_basic \ + import filter_factory as basicauth_factory + +#OPENID AUTH +from auth_protocols.auth_protocol_openid \ + import filter_factory as openidauth_factory diff --git a/keystone/auth_protocol/__init__.py b/keystone/auth_protocol/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/keystone/auth_protocol/auth_protocol_token.ini b/keystone/auth_protocol/auth_protocol_token.ini deleted file mode 100644 index 41e903d9..00000000 --- a/keystone/auth_protocol/auth_protocol_token.ini +++ /dev/null @@ -1,15 +0,0 @@ -[DEFAULT] - -[app:main] -paste.app_factory = auth_protocol_token:app_factory -auth_host = 127.0.0.1 -auth_port = 8080 -auth_token = 999888777666 -delegated = 0 - -service_protocol = http -service_host = 127.0.0.1 -service_port = 8100 -service_pass = dTpw - - diff --git a/keystone/auth_protocol/auth_protocol_token.py b/keystone/auth_protocol/auth_protocol_token.py deleted file mode 100644 index 92c88035..00000000 --- a/keystone/auth_protocol/auth_protocol_token.py +++ /dev/null @@ -1,209 +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 - -This WSGI component performs multiple jobs: -- it verifies that incoming client requests have valid tokens by verifying - tokens with the auth service. -- it will reject unauthenticated requests UNLESS it is in 'delegated' mode, - which means the final decision is delegated to the service -- it will collect and forward identity information from a valid token - such as user name, groups, etc... - -Refer to: http://wiki.openstack.org/openstack-authn - - -HEADERS -------- -www-authenticate : only used if this component is being used remotely -HTTP_AUTHORIZATION : basic auth password used to validate the connection -HTTP_X_AUTHORIZATION: the client identity being passed in -HTTP_X_AUTH_TOKEN : the client token being passed in -HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) - to support cloud files - -""" - -import eventlet -from eventlet import wsgi -import json -import os -from paste.deploy import loadapp -from urlparse import urlparse -from webob.exc import HTTPUnauthorized -from webob.exc import Request, Response -import httplib - -from keystone.common.bufferedhttp import http_connect_raw as http_connect - - -class TokenAuth(object): - """Auth Middleware that handles token authentication with an auth service""" - - def __init__(self, app, conf): - print "Starting the Token Auth component" - - self.conf = conf - self.app = app #if app is not set, this should forward requests - - # where to find the OpenStack service (if not in local WSGI chain) - self.service_protocol = conf.get('service_protocol', 'http') - self.service_host = conf.get('service_host', '127.0.0.1') - self.service_port = int(conf.get('service_port', 8090)) - self.service_url = '%s://%s:%s' % (self.service_protocol, self.service_host, self.service_port) - # used to verify this component with the OpenStack service (or PAPIAuth) - self.service_pass = conf.get('service_pass', 'dTpw') - - # where to find the auth service (we use this to validate tokens) - self.auth_host = conf.get('auth_ip', '127.0.0.1') - self.auth_port = int(conf.get('auth_port', 8080)) - # used to verify this component with the Auth service - self.auth_token = conf.get('auth_token', 'dTpw') - - # delegated means we still allow unauthenticated requests through - self.delegated = int(conf.get('delegated', 0)) - - - def get_admin_auth_token(self, username, password, tenant): - """ - This function gets an admin auth token to be used by this service to - validate a user's token. Validate_token is a priviledged call so - it needs to be authenticated by a service that is calling it - """ - headers = {"Content-type": "application/json", "Accept": "text/json"} - params = {"passwordCredentials": {"username": username, - "password": password, - "tenantId": "1"}} - conn = httplib.HTTPConnection("%s:%s" \ - % (self.auth_host, self.auth_port)) - conn.request("POST", "/v1.0/token", json.dumps(params), \ - headers=headers) - response = conn.getresponse() - data = response.read() - ret = data - return ret - - - def __call__(self, env, start_response): - def custom_start_response(status, headers): - if self.delegated: - headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) - return start_response(status, headers) - - #Prep for proxy request - proxy_headers = env.copy() - user = '' - - #Look for token in request - token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - if token: - # this request is claiming it has a valid token, let's check - # with the auth service - # Step 1: We need to auth with the keystone service, so get an - # admin token - #TODO: Need to properly implement this, where to store creds - # for now using token from ini - #auth = self.get_admin_auth_token("admin", "secrete", "1") - #admin_token = json.loads(auth)["auth"]["token"]["id"] - - # Step 2: validate the user's token using the admin token - headers = {"Content-type": "application/json", - "Accept": "text/json", - "X-Auth-Token": self.auth_token} - ##TODO:we need to figure out how to auth to keystone - #since validate_token is a priviledged call - #Khaled's version uses creds to get a token - # "X-Auth-Token": admin_token} - # we're using a test token from the ini file for now - conn = http_connect(self.auth_host, self.auth_port, 'GET', - '/v1.0/token/%s' % token, headers=headers) - resp = conn.getresponse() - data = resp.read() - conn.close() - - - if not str(resp.status).startswith('20'): - # Keystone rejected claim - if self.delegated: - # Downstream service will receive call still and decide - proxy_headers['X_IDENTITY_STATUS'] = "Invalid" - env['HTTPX_IDENTITY_STATUS'] = "Invalid" - else: - # Reject the response & send back the error (not delegated) - return HTTPUnauthorized(headers=headers)(env, start_response) - else: - # Valid token. Get user data and put it in to the call - # so the downstream service can use iot - dict_response = json.loads(data) - user = dict_response['auth']['user']['username'] - proxy_headers['X_AUTHORIZATION'] = "Proxy " + user - proxy_headers['X_IDENTITY_STATUS'] = "Confirmed" - env['HTTP_X_AUTHORIZATION'] = "Proxy " + user - env['HTTP_X_IDENTITY_STATUS'] = "Confirmed" - else: - #No token was provided - if self.delegated: - proxy_headers['X_IDENTITY_STATUS'] = "Invalid" - env['HTTP_X_IDENTITY_STATUS'] = "Invalid" - else: - return HTTPUnauthorized() - - #Token/Auth processed, headers added now decide how to pass on the call - if self.app is None: - proxy_headers['AUTHORIZATION'] = "Basic dTpw" - # We are forwarding to a remote service (no downstream WSGI app) - req = Request(proxy_headers) - parsed = urlparse(req.url) - conn = http_connect(self.service_host, self.service_port, \ - req.method, parsed.path, \ - proxy_headers,\ - ssl=(self.service_protocol == 'https')) - resp = conn.getresponse() - data = resp.read() - #TODO: use a more sophisticated proxy - # we are rewriting the headers now - return Response(status=resp.status, body=data)(env, start_response) - else: - # Pass to downstream WSGI component - env['HTTP_AUTHORIZATION'] = "Basic dTpw" - return self.app(env, custom_start_response) - - -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 TokenAuth(app, conf) - return auth_filter - -def app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return TokenAuth(None, conf) - -if __name__ == "__main__": - app = loadapp("config:" + \ - os.path.join(os.path.abspath(os.path.dirname(__file__)), - "auth_protocol_token.ini"), global_conf={"log_name": "echo.log"}) - wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/identity.py b/keystone/identity.py index 21228ed0..2e2184d8 100644 --- a/keystone/identity.py +++ b/keystone/identity.py @@ -15,6 +15,27 @@ # limitations under the License. +""" +Service that stores identoties and issues and manages tokens + +HEADERS +------- +HTTP_ is a standard http header +HTTP_X is an extended http header + +> Coming in from initial call +HTTP_X_AUTH_TOKEN : the client token being passed in +HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) + to support cloud files +> Used for communication between components +www-authenticate : only used if this component is being used remotely +HTTP_AUTHORIZATION : basic auth password used to validate the connection + +> What we add to the request for use by the OpenStack service +HTTP_X_AUTHORIZATION: the client identity being passed in + +""" + import functools import logging import os @@ -87,7 +108,7 @@ def send_result(code, result): return content -def get_request(model): +def get_normalized_request_content(model): """initialize a model from json/xml contents of request body""" ctype = request.environ.get("CONTENT_TYPE") @@ -181,7 +202,7 @@ def get_xsd_atom_contract(xsd): @bottle.route('/v1.0/token', method='POST') @wrap_error def authenticate(): - creds = get_request(auth.PasswordCredentials) + creds = get_normalized_request_content(auth.PasswordCredentials) return send_result(200, service.authenticate(creds)) @@ -209,7 +230,7 @@ def delete_token(token_id): @bottle.route('/v1.0/tenants', method='POST') @wrap_error def create_tenant(): - tenant = get_request(tenants.Tenant) + tenant = get_normalized_request_content(tenants.Tenant) return send_result(201, service.create_tenant(get_auth_token(), tenant)) @@ -237,7 +258,7 @@ def get_tenant(tenant_id): @bottle.route('/v1.0/tenants/:tenant_id', method='PUT') @wrap_error def update_tenant(tenant_id): - tenant = get_request(tenants.Tenant) + tenant = get_normalized_request_content(tenants.Tenant) rval = service.update_tenant(get_auth_token(), tenant_id, tenant) return send_result(200, rval) diff --git a/keystone/middleware/papiauth.py b/keystone/middleware/papiauth.py index 0e86523a..ee2645ad 100644 --- a/keystone/middleware/papiauth.py +++ b/keystone/middleware/papiauth.py @@ -38,13 +38,20 @@ Refer to: http://wiki.openstack.org/openstack-authn HEADERS ------- +HTTP_ is a standard http header +HTTP_X is an extended http header -www-authenticate : only used if this component is being used remotely -HTTP_AUTHORIZATION : basic auth password used to validate the connection -HTTP_X_AUTHORIZATION: the client token being passed in +> Coming in from initial call HTTP_X_AUTH_TOKEN : the client token being passed in HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) to support cloud files +> Used for communication between components +www-authenticate : only used if this component is being used remotely +HTTP_AUTHORIZATION : basic auth password used to validate the connection + +> What we add to the request for use by the OpenStack service +HTTP_X_AUTHORIZATION: the client identity being passed in + """ -- cgit From 79c78088b600beb5351fed180ada384e012c9db1 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Wed, 27 Apr 2011 12:18:32 -0700 Subject: Added protocol stubs (openid and basic auth) --- keystone/auth_protocols/__init__.py | 0 keystone/auth_protocols/auth_protocol_basic.py | 113 +++++++++++ keystone/auth_protocols/auth_protocol_openid.py | 114 +++++++++++ keystone/auth_protocols/auth_protocol_token.ini | 15 ++ keystone/auth_protocols/auth_protocol_token.py | 243 ++++++++++++++++++++++++ 5 files changed, 485 insertions(+) create mode 100644 keystone/auth_protocols/__init__.py create mode 100644 keystone/auth_protocols/auth_protocol_basic.py create mode 100644 keystone/auth_protocols/auth_protocol_openid.py create mode 100644 keystone/auth_protocols/auth_protocol_token.ini create mode 100644 keystone/auth_protocols/auth_protocol_token.py diff --git a/keystone/auth_protocols/__init__.py b/keystone/auth_protocols/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/keystone/auth_protocols/auth_protocol_basic.py b/keystone/auth_protocols/auth_protocol_basic.py new file mode 100644 index 00000000..d529eb6e --- /dev/null +++ b/keystone/auth_protocols/auth_protocol_basic.py @@ -0,0 +1,113 @@ +#!/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 + + +""" +BASIC AUTH MIDDLEWARE - STUB + +This WSGI component should perform multiple jobs: +- validate incoming basic claims +- perform all basic auth interactions with clients +- collect and forward identity information from the authentication process + such as user name, groups, etc... + +This is an Auth component as per: http://wiki.openstack.org/openstack-authn + +""" + + +PROTOCOL_NAME = "Basic Authentication" + +class AuthProtocol(object): + """Auth Middleware that handles authenticating client calls""" + + def __init__(self, app, conf): + print "Starting the %s component" % PROTOCOL_NAME + + self.conf = conf + self.app = app + #if app is set, then we are in a WSGI pipeline and requests get passed + # on to app. If it is not set, this component should forward requests + + # where to find the OpenStack service (if not in local WSGI chain) + # these settings are only used if this component is acting as a proxy + # and the OpenSTack service is running remotely + self.service_protocol = conf.get('service_protocol', 'http') + self.service_host = conf.get('service_host', '127.0.0.1') + self.service_port = int(conf.get('service_port', 8090)) + self.service_url = '%s://%s:%s' % (self.service_protocol, + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service (or PAPIAuth) + self.service_pass = conf.get('service_pass', 'dTpw') + + # delay_auth_decision means we still allow unauthenticated requests + # through and we let the downstream service make the final decision + self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) + + + + def __call__(self, env, start_response): + def custom_start_response(status, headers): + if self.delay_auth_decision: + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + return start_response(status, headers) + + #TODO(Ziad): PERFORM BASIC AUTH + + #Auth processed, headers added now decide how to pass on the call + if self.app: + # Pass to downstream WSGI component + env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass + return self.app(env, custom_start_response) + + proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers,\ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(env, start_response) + + +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 + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AuthProtocol(None, conf) + +if __name__ == "__main__": + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_protocol_basic.ini"), global_conf={"log_name": "auth_protocol_basic.log"}) + wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_openid.py b/keystone/auth_protocols/auth_protocol_openid.py new file mode 100644 index 00000000..0901533a --- /dev/null +++ b/keystone/auth_protocols/auth_protocol_openid.py @@ -0,0 +1,114 @@ +#!/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 + + +""" +OPENID AUTH MIDDLEWARE - STUB + +This WSGI component should perform multiple jobs: +- validate incoming openid claims +- perform all openid interactions with clients +- collect and forward identity information from the openid authentication + such as user name, groups, etc... + +This is an Auth component as per: http://wiki.openstack.org/openstack-authn + + +""" + + +PROTOCOL_NAME = "OpenID Authentication" + +class AuthProtocol(object): + """Auth Middleware that handles authenticating client calls""" + + def __init__(self, app, conf): + print "Starting the %s component" % PROTOCOL_NAME + + self.conf = conf + self.app = app + #if app is set, then we are in a WSGI pipeline and requests get passed + # on to app. If it is not set, this component should forward requests + + # where to find the OpenStack service (if not in local WSGI chain) + # these settings are only used if this component is acting as a proxy + # and the OpenSTack service is running remotely + self.service_protocol = conf.get('service_protocol', 'http') + self.service_host = conf.get('service_host', '127.0.0.1') + self.service_port = int(conf.get('service_port', 8090)) + self.service_url = '%s://%s:%s' % (self.service_protocol, + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service (or PAPIAuth) + self.service_pass = conf.get('service_pass', 'dTpw') + + # delay_auth_decision means we still allow unauthenticated requests + # through and we let the downstream service make the final decision + self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) + + + + def __call__(self, env, start_response): + def custom_start_response(status, headers): + if self.delay_auth_decision: + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + return start_response(status, headers) + + #TODO(Rasib): PERFORM OPENID AUTH + + #Auth processed, headers added now decide how to pass on the call + if self.app: + # Pass to downstream WSGI component + env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass + return self.app(env, custom_start_response) + + proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers,\ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(env, start_response) + + +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 + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AuthProtocol(None, conf) + +if __name__ == "__main__": + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_protocol_openid.ini"), global_conf={"log_name": "auth_protocol_openid.log"}) + wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_token.ini b/keystone/auth_protocols/auth_protocol_token.ini new file mode 100644 index 00000000..094c3aa2 --- /dev/null +++ b/keystone/auth_protocols/auth_protocol_token.ini @@ -0,0 +1,15 @@ +[DEFAULT] + +[app:main] +paste.app_factory = auth_protocol_token:app_factory +auth_host = 127.0.0.1 +auth_port = 8080 +auth_token = 999888777666 +delay_auth_decision = 0 + +service_protocol = http +service_host = 127.0.0.1 +service_port = 8100 +service_pass = dTpw + + diff --git a/keystone/auth_protocols/auth_protocol_token.py b/keystone/auth_protocols/auth_protocol_token.py new file mode 100644 index 00000000..1f3637e3 --- /dev/null +++ b/keystone/auth_protocols/auth_protocol_token.py @@ -0,0 +1,243 @@ +#!/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 + +This WSGI component performs multiple jobs: +- it verifies that incoming client requests have valid tokens by verifying + tokens with the auth service. +- it will reject unauthenticated requests UNLESS it is in 'delay_auth_decision' + mode, which means the final decision is delegated to the downstream WSGI + component (usually the OpenStack service) +- it will collect and forward identity information from a valid token + such as user name, groups, etc... + +Refer to: http://wiki.openstack.org/openstack-authn + + +HEADERS +------- +Headers starting with HTTP_ is a standard http header +Headers starting with HTTP_X is an extended http header + +> Coming in from initial call from client or customer +HTTP_X_AUTH_TOKEN : the client token being passed in +HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) + to support cloud files +> Used for communication between components +www-authenticate : only used if this component is being used remotely +HTTP_AUTHORIZATION : basic auth password used to validate the connection + +> What we add to the request for use by the OpenStack service +HTTP_X_AUTHORIZATION: the client identity being passed in + +""" + +import eventlet +from eventlet import wsgi +import json +import os +from paste.deploy import loadapp +from urlparse import urlparse +from webob.exc import HTTPUnauthorized +from webob.exc import Request, Response +import httplib + +from keystone.common.bufferedhttp import http_connect_raw as http_connect + +PROTOCOL_NAME = "Token Authentication" + + +def _decorate_request_headers(header, value, proxy_headers, env): + proxy_headers[header] = value + env["HTTP_%s" % header] = value + +class AuthProtocol(object): + """Auth Middleware that handles authenticating client calls""" + + def _init_protocol_common(self, app, conf): + """ Common initialization code""" + print "Starting the %s component" % PROTOCOL_NAME + + self.conf = conf + self.app = app + #if app is set, then we are in a WSGI pipeline and requests get passed + # on to app. If it is not set, this component should forward requests + + # where to find the OpenStack service (if not in local WSGI chain) + # these settings are only used if this component is acting as a proxy + # and the OpenSTack service is running remotely + self.service_protocol = conf.get('service_protocol', 'http') + self.service_host = conf.get('service_host', '127.0.0.1') + self.service_port = int(conf.get('service_port', 8090)) + self.service_url = '%s://%s:%s' % (self.service_protocol, + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service (or PAPIAuth) + self.service_pass = conf.get('service_pass', 'dTpw') + + # delay_auth_decision means we still allow unauthenticated requests + # through and we let the downstream service make the final decision + self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) + + + def _init_protocol(self, app, conf): + """ Protocol specific initialization """ + + # where to find the auth service (we use this to validate tokens) + self.auth_host = conf.get('auth_ip', '127.0.0.1') + self.auth_port = int(conf.get('auth_port', 8080)) + + # Credentials used to verify this component with the Auth service since + # validating tokens is a priviledged call + self.auth_token = conf.get('auth_token', 'dTpw') + + + def __init__(self, app, conf): + """ Common initialization code """ + + #TODO: maybe we rafactor this into a superclass + self._init_protocol_common(app, conf) #Applies to all protocols + self._init_protocol(app, conf) #Specific to this protocol + + + def get_admin_auth_token(self, username, password, tenant): + """ + This function gets an admin auth token to be used by this service to + validate a user's token. Validate_token is a priviledged call so + it needs to be authenticated by a service that is calling it + """ + headers = {"Content-type": "application/json", "Accept": "text/json"} + params = {"passwordCredentials": {"username": username, + "password": password, + "tenantId": "1"}} + conn = httplib.HTTPConnection("%s:%s" \ + % (self.auth_host, self.auth_port)) + conn.request("POST", "/v1.0/token", json.dumps(params), \ + headers=headers) + response = conn.getresponse() + data = response.read() + ret = data + return ret + + def __call__(self, env, start_response): + def custom_start_response(status, headers): + if self.delay_auth_decision: + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + return start_response(status, headers) + + #Prep headers to proxy request to remote service + proxy_headers = env.copy() + user = '' + + #Look for token in request + token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) + if not token: + #No token was provided + if self.delay_auth_decision: + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", proxy_headers, env) + else: + return HTTPUnauthorized() + else: + # this request is claiming it has a valid token, let's check + # with the auth service + # Step 1: We need to auth with the keystone service, so get an + # admin token + #TODO: Need to properly implement this, where to store creds + # for now using token from ini + #auth = self.get_admin_auth_token("admin", "secrete", "1") + #admin_token = json.loads(auth)["auth"]["token"]["id"] + + # Step 2: validate the user's token with the auth service + # since this is a priviledged op,m we need to auth ourselves + # by using an admin token + headers = {"Content-type": "application/json", + "Accept": "text/json", + "X-Auth-Token": self.auth_token} + ##TODO:we need to figure out how to auth to keystone + #since validate_token is a priviledged call + #Khaled's version uses creds to get a token + # "X-Auth-Token": admin_token} + # we're using a test token from the ini file for now + conn = http_connect(self.auth_host, self.auth_port, 'GET', + '/v1.0/token/%s' % token, headers=headers) + resp = conn.getresponse() + data = resp.read() + conn.close() + + + if not str(resp.status).startswith('20'): + # Keystone rejected claim + if self.delay_auth_decision: + # Downstream service will receive call still and decide + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", proxy_headers, env) + else: + # Reject the response & send back the error (not delay_auth_decision) + return HTTPUnauthorized(headers=headers)(env, start_response) + else: + # Valid token. Get user data and put it in to the call + # so the downstream service can use iot + dict_response = json.loads(data) + user = dict_response['auth']['user']['username'] + #TODO(Ziad): add additional details we may need, like tenant and group info + _decorate_request_headers('X_AUTHORIZATION',"Proxy %s" % user, proxy_headers, env) + _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", proxy_headers, env) + + + #Token/Auth processed, headers added now decide how to pass on the call + _decorate_request_headers('AUTHORIZATION', "Basic %s" % self.service_pass, proxy_headers, env) + if self.app: + # Pass to downstream WSGI component + return self.app(env, custom_start_response) + else: + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers,\ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(proxy_headers, start_response) + + +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 + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AuthProtocol(None, conf) + +if __name__ == "__main__": + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_protocol_token.ini"), global_conf={"log_name": "echo.log"}) + wsgi.server(eventlet.listen(('', 8090)), app) -- cgit From b4bcd1d9230b469bc19af5a12ccf1524f7de694b Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Wed, 27 Apr 2011 14:06:51 -0700 Subject: pep8-ize --- echo/echo/echo.py | 11 ++--- keystone/auth_protocols/auth_protocol_basic.py | 13 +++--- keystone/auth_protocols/auth_protocol_openid.py | 15 ++++--- keystone/auth_protocols/auth_protocol_token.py | 53 +++++++++++++++---------- keystone/db/sqlalchemy/api.py | 13 +++++- keystone/db/sqlalchemy/session.py | 10 +++-- keystone/middleware/papiauth.py | 4 +- management/getgroupusers.py | 1 - management/getuser.py | 1 + management/useradd.py | 1 - management/userupdate.py | 1 + 11 files changed, 72 insertions(+), 51 deletions(-) diff --git a/echo/echo/echo.py b/echo/echo/echo.py index b0fd9c7b..c73e79b8 100644 --- a/echo/echo/echo.py +++ b/echo/echo/echo.py @@ -72,10 +72,10 @@ class EchoApp(object): def toDOM(self, environ): echo = etree.Element("{http://docs.openstack.org/echo/api/v1.0}echo", - method= environ["REQUEST_METHOD"], - pathInfo= environ["PATH_INFO"], - queryString= environ.get('QUERY_STRING', ""), - caller_identity= self.envr['HTTP_X_AUTHORIZATION']) + method=environ["REQUEST_METHOD"], + pathInfo=environ["PATH_INFO"], + queryString=environ.get('QUERY_STRING', ""), + caller_identity=self.envr['HTTP_X_AUTHORIZATION']) content = etree.Element( "{http://docs.openstack.org/echo/api/v1.0}content") content.set("type", environ["CONTENT_TYPE"]) @@ -106,7 +106,8 @@ if __name__ == "__main__": wsgi.server(eventlet.listen(('', 8100)), app) else: - print "Running all components locally. Use --remote option to run with remote auth proxy" + print "Running all components locally." + print "Use --remote option to run with remote auth proxy" app = loadapp("config:" + \ os.path.join(os.path.abspath(os.path.dirname(__file__)), "echo.ini"), global_conf={"log_name": "echo.log"}) diff --git a/keystone/auth_protocols/auth_protocol_basic.py b/keystone/auth_protocols/auth_protocol_basic.py index d529eb6e..0285fc53 100644 --- a/keystone/auth_protocols/auth_protocol_basic.py +++ b/keystone/auth_protocols/auth_protocol_basic.py @@ -34,6 +34,7 @@ This is an Auth component as per: http://wiki.openstack.org/openstack-authn PROTOCOL_NAME = "Basic Authentication" + class AuthProtocol(object): """Auth Middleware that handles authenticating client calls""" @@ -52,16 +53,14 @@ class AuthProtocol(object): self.service_host = conf.get('service_host', '127.0.0.1') self.service_port = int(conf.get('service_port', 8090)) self.service_url = '%s://%s:%s' % (self.service_protocol, - self.service_host, - self.service_port) - # used to verify this component with the OpenStack service (or PAPIAuth) + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service or PAPIAuth self.service_pass = conf.get('service_pass', 'dTpw') # delay_auth_decision means we still allow unauthenticated requests # through and we let the downstream service make the final decision self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) - - def __call__(self, env, start_response): def custom_start_response(status, headers): @@ -101,6 +100,7 @@ def filter_factory(global_conf, **local_conf): return AuthProtocol(app, conf) return auth_filter + def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) @@ -109,5 +109,6 @@ def app_factory(global_conf, **local_conf): if __name__ == "__main__": app = loadapp("config:" + \ os.path.join(os.path.abspath(os.path.dirname(__file__)), - "auth_protocol_basic.ini"), global_conf={"log_name": "auth_protocol_basic.log"}) + "auth_protocol_basic.ini"), + global_conf={"log_name": "auth_protocol_basic.log"}) wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_openid.py b/keystone/auth_protocols/auth_protocol_openid.py index 0901533a..8a333cf4 100644 --- a/keystone/auth_protocols/auth_protocol_openid.py +++ b/keystone/auth_protocols/auth_protocol_openid.py @@ -28,13 +28,12 @@ This WSGI component should perform multiple jobs: such as user name, groups, etc... This is an Auth component as per: http://wiki.openstack.org/openstack-authn - - """ PROTOCOL_NAME = "OpenID Authentication" + class AuthProtocol(object): """Auth Middleware that handles authenticating client calls""" @@ -53,16 +52,14 @@ class AuthProtocol(object): self.service_host = conf.get('service_host', '127.0.0.1') self.service_port = int(conf.get('service_port', 8090)) self.service_url = '%s://%s:%s' % (self.service_protocol, - self.service_host, - self.service_port) - # used to verify this component with the OpenStack service (or PAPIAuth) + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service or PAPIAuth self.service_pass = conf.get('service_pass', 'dTpw') # delay_auth_decision means we still allow unauthenticated requests # through and we let the downstream service make the final decision self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) - - def __call__(self, env, start_response): def custom_start_response(status, headers): @@ -102,6 +99,7 @@ def filter_factory(global_conf, **local_conf): return AuthProtocol(app, conf) return auth_filter + def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) @@ -110,5 +108,6 @@ def app_factory(global_conf, **local_conf): if __name__ == "__main__": app = loadapp("config:" + \ os.path.join(os.path.abspath(os.path.dirname(__file__)), - "auth_protocol_openid.ini"), global_conf={"log_name": "auth_protocol_openid.log"}) + "auth_protocol_openid.ini"), + global_conf={"log_name": "auth_protocol_openid.log"}) wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_token.py b/keystone/auth_protocols/auth_protocol_token.py index 1f3637e3..8bfc1a05 100644 --- a/keystone/auth_protocols/auth_protocol_token.py +++ b/keystone/auth_protocols/auth_protocol_token.py @@ -70,6 +70,7 @@ def _decorate_request_headers(header, value, proxy_headers, env): proxy_headers[header] = value env["HTTP_%s" % header] = value + class AuthProtocol(object): """Auth Middleware that handles authenticating client calls""" @@ -89,16 +90,15 @@ class AuthProtocol(object): self.service_host = conf.get('service_host', '127.0.0.1') self.service_port = int(conf.get('service_port', 8090)) self.service_url = '%s://%s:%s' % (self.service_protocol, - self.service_host, - self.service_port) - # used to verify this component with the OpenStack service (or PAPIAuth) + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service or PAPIAuth self.service_pass = conf.get('service_pass', 'dTpw') # delay_auth_decision means we still allow unauthenticated requests # through and we let the downstream service make the final decision self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) - def _init_protocol(self, app, conf): """ Protocol specific initialization """ @@ -110,20 +110,18 @@ class AuthProtocol(object): # validating tokens is a priviledged call self.auth_token = conf.get('auth_token', 'dTpw') - def __init__(self, app, conf): """ Common initialization code """ #TODO: maybe we rafactor this into a superclass - self._init_protocol_common(app, conf) #Applies to all protocols - self._init_protocol(app, conf) #Specific to this protocol - + self._init_protocol_common(app, conf) # Applies to all protocols + self._init_protocol(app, conf) # Specific to this protocol def get_admin_auth_token(self, username, password, tenant): """ - This function gets an admin auth token to be used by this service to - validate a user's token. Validate_token is a priviledged call so - it needs to be authenticated by a service that is calling it + This function gets an admin auth token to be used by this service to + validate a user's token. Validate_token is a priviledged call so + it needs to be authenticated by a service that is calling it """ headers = {"Content-type": "application/json", "Accept": "text/json"} params = {"passwordCredentials": {"username": username, @@ -153,7 +151,9 @@ class AuthProtocol(object): if not token: #No token was provided if self.delay_auth_decision: - _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", proxy_headers, env) + _decorate_request_headers("X_IDENTITY_STATUS", + "Invalid", + proxy_headers, env) else: return HTTPUnauthorized() else: @@ -183,27 +183,34 @@ class AuthProtocol(object): data = resp.read() conn.close() - if not str(resp.status).startswith('20'): # Keystone rejected claim if self.delay_auth_decision: # Downstream service will receive call still and decide - _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", proxy_headers, env) + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", + proxy_headers, env) else: - # Reject the response & send back the error (not delay_auth_decision) - return HTTPUnauthorized(headers=headers)(env, start_response) + # Reject the response & send back the error + # (not delay_auth_decision) + return HTTPUnauthorized(headers=headers)(env, + start_response) else: # Valid token. Get user data and put it in to the call # so the downstream service can use iot dict_response = json.loads(data) user = dict_response['auth']['user']['username'] - #TODO(Ziad): add additional details we may need, like tenant and group info - _decorate_request_headers('X_AUTHORIZATION',"Proxy %s" % user, proxy_headers, env) - _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", proxy_headers, env) - + # TODO(Ziad): add additional details we may need, + # like tenant and group info + _decorate_request_headers('X_AUTHORIZATION', "Proxy %s" % user, + proxy_headers, env) + _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", + proxy_headers, env) #Token/Auth processed, headers added now decide how to pass on the call - _decorate_request_headers('AUTHORIZATION', "Basic %s" % self.service_pass, proxy_headers, env) + _decorate_request_headers('AUTHORIZATION', + "Basic %s" % self.service_pass, + proxy_headers, + env) if self.app: # Pass to downstream WSGI component return self.app(env, custom_start_response) @@ -219,7 +226,8 @@ class AuthProtocol(object): data = resp.read() #TODO: use a more sophisticated proxy # we are rewriting the headers now - return Response(status=resp.status, body=data)(proxy_headers, start_response) + return Response(status=resp.status, body=data)(proxy_headers, + start_response) def filter_factory(global_conf, **local_conf): @@ -231,6 +239,7 @@ def filter_factory(global_conf, **local_conf): return AuthProtocol(app, conf) return auth_filter + def app_factory(global_conf, **local_conf): conf = global_conf.copy() conf.update(local_conf) diff --git a/keystone/db/sqlalchemy/api.py b/keystone/db/sqlalchemy/api.py index a8551b9d..a0910778 100644 --- a/keystone/db/sqlalchemy/api.py +++ b/keystone/db/sqlalchemy/api.py @@ -85,6 +85,7 @@ def user_get(id, session=None): joinedload('tenants')).filter_by(id=id).first() return result + def user_get_by_tenant(tenant_id, session=None): if not session: session = get_session() @@ -92,6 +93,7 @@ def user_get_by_tenant(tenant_id, session=None): tenant_id=tenant_id) return result + def user_groups(id, session=None): if not session: session = get_session() @@ -99,6 +101,7 @@ def user_groups(id, session=None): user_id=id) return result + def user_update(id, values, session=None): if not session: session = get_session() @@ -107,19 +110,22 @@ def user_update(id, values, session=None): user_ref.update(values) user_ref.save(session=session) + def user_delete(id, session=None): if not session: session = get_session() with session.begin(): user_ref = user_get(id, session) session.delete(user_ref) - + + def group_get(id, session=None): if not session: session = get_session() result = session.query(models.Group).filter_by(id=id).first() return result + def group_users(id, session=None): if not session: session = get_session() @@ -127,19 +133,22 @@ def group_users(id, session=None): group_id=id) return result + def group_get_all(session=None): if not session: session = get_session() result = session.query(models.Group) return result + def group_delete(id, session=None): if not session: session = get_session() with session.begin(): group_ref = group_get(id, session) session.delete(group_ref) - + + def token_create(values): token_ref = models.Token() token_ref.update(values) diff --git a/keystone/db/sqlalchemy/session.py b/keystone/db/sqlalchemy/session.py index 836a0e96..10e727a7 100644 --- a/keystone/db/sqlalchemy/session.py +++ b/keystone/db/sqlalchemy/session.py @@ -29,17 +29,19 @@ from sqlalchemy.orm import sessionmaker _ENGINE = None _MAKER = None + def get_connection_string(): path = os.path.realpath(__file__) dbpath = os.path.normpath(os.path.join(path, - os.pardir, #sqlalchemy - os.pardir, #db - os.pardir #keystone + os.pardir, # sqlalchemy + os.pardir, # db + os.pardir # keystone )) connection_string = "sqlite:///%s/keystone.db" % dbpath logging.debug('SQL ALchemy connection string: %s', connection_string) return connection_string + def get_session(autocommit=True, expire_on_commit=False): """Helper method to grab session""" global _ENGINE @@ -47,7 +49,7 @@ def get_session(autocommit=True, expire_on_commit=False): if not _MAKER: if not _ENGINE: kwargs = {'pool_recycle': 30, 'echo': False} - kwargs['poolclass'] = pool.NullPool #for SQLite3 + kwargs['poolclass'] = pool.NullPool # for SQLite3 _ENGINE = create_engine(get_connection_string(), **kwargs) _MAKER = (sessionmaker(bind=_ENGINE, autocommit=autocommit, diff --git a/keystone/middleware/papiauth.py b/keystone/middleware/papiauth.py index ee2645ad..0af14bb9 100644 --- a/keystone/middleware/papiauth.py +++ b/keystone/middleware/papiauth.py @@ -23,8 +23,8 @@ Auth Middleware that handles auth for a service This module can be installed as a filter in front of your service to validate that requests are coming from a trusted component that has handled authenticating the call. If a call comes from an untrusted source, it will -redirect it back to be properly authenticated. This is done by sending our a 305 -proxy redirect response with the URL for the auth service. +redirect it back to be properly authenticated. This is done by sending our a +305 proxy redirect response with the URL for the auth service. The auth service settings are specified in the INI file (keystone.ini). The ini file is passed in as the WSGI config file when starting the service. For this diff --git a/management/getgroupusers.py b/management/getgroupusers.py index 86fc700d..0fc7d231 100644 --- a/management/getgroupusers.py +++ b/management/getgroupusers.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - import optparse import keystone.db.sqlalchemy.api as db_api diff --git a/management/getuser.py b/management/getuser.py index 68ccb454..036f10f4 100644 --- a/management/getuser.py +++ b/management/getuser.py @@ -16,6 +16,7 @@ import optparse import keystone.db.sqlalchemy.api as db_api + def main(): usage = "usage: %prog username" parser = optparse.OptionParser(usage) diff --git a/management/useradd.py b/management/useradd.py index 969a8a7e..fc3fc8b6 100644 --- a/management/useradd.py +++ b/management/useradd.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - import optparse import keystone.db.sqlalchemy.api as db_api from keystone.db.sqlalchemy import models diff --git a/management/userupdate.py b/management/userupdate.py index 8af517cc..89174d04 100644 --- a/management/userupdate.py +++ b/management/userupdate.py @@ -16,6 +16,7 @@ import optparse import keystone.db.sqlalchemy.api as db_api + def main(): usage = "usage: %prog username email" parser = optparse.OptionParser(usage) -- cgit From 4c3936177169868038497688098a867e3a1f1348 Mon Sep 17 00:00:00 2001 From: Jesse Andrews Date: Wed, 27 Apr 2011 14:28:08 -0700 Subject: simple json cleanups for tests --- test/unit/test_identity.py | 84 ++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 44 deletions(-) diff --git a/test/unit/test_identity.py b/test/unit/test_identity.py index dfd4ba26..95a91afc 100644 --- a/test/unit/test_identity.py +++ b/test/unit/test_identity.py @@ -1,25 +1,22 @@ -import unittest -from webtest import TestApp import httplib2 +import json from lxml import etree +import unittest +from webtest import TestApp -try: - import simplejson as json -except ImportError: - import json URL = 'http://localhost:8080/v1.0/' -def get_token(user, pswd, type=''): +def get_token(user, pswd, kind=''): h = httplib2.Http(".cache") url = '%stoken' % URL - body = '{"passwordCredentials" : { "username" : "%s", "password" :\ - "%s"} }' % (user, pswd) - resp, content = h.request(url, "POST", body=body,\ - headers={"Content-Type": "application/json"}) + body = {"passwordCredentials": {"username": user, + "password": pswd}} + resp, content = h.request(url, "POST", body=json.dumps(body), + headers={"Content-Type": "application/json"}) content = json.loads(content) token = str(content['auth']['token']['id']) - if type == 'token': + if kind == 'token': return token else: return (resp, content) @@ -69,13 +66,12 @@ def create_tenant(tenantid, auth_token): h = httplib2.Http(".cache") url = '%stenants' % (URL) - body = '{"tenant": { "id": "%s", \ - "description": "A description ...", "enabled"\ - :true } }' % tenantid - resp, content = h.request(url, "POST", body=body,\ - headers={"Content-Type": "application/json",\ - "X-Auth-Token": auth_token}) - + body = {"tenant": {"id": tenantid, + "description": "A description ...", + "enabled": True}} + resp, content = h.request(url, "POST", body=json.dumps(body), + headers={"Content-Type": "application/json", + "X-Auth-Token": auth_token}) return (resp, content) @@ -145,15 +141,15 @@ class identity_test(unittest.TestCase): def test_a_get_version(self): h = httplib2.Http(".cache") url = URL - resp, content = h.request(url, "GET", body="",\ - headers={"Content-Type": "application/json"}) + resp, content = h.request(url, "GET", body="", + headers={"Content-Type": "application/json"}) self.assertEqual(200, int(resp['status'])) self.assertEqual('application/json', resp['content-type']) def test_a_get_version(self): h = httplib2.Http(".cache") url = URL - resp, content = h.request(url, "GET", body="",\ + resp, content = h.request(url, "GET", body="", headers={"Content-Type": "application/xml", "ACCEPT": "application/xml"}) self.assertEqual(200, int(resp['status'])) @@ -187,9 +183,9 @@ class authorize_test(identity_test): def test_a_authorize_user_disaabled(self): h = httplib2.Http(".cache") url = '%stoken' % URL - body = '{"passwordCredentials" : { "username": "disabled", "password":\ - "secrete"} }' - resp, content = h.request(url, "POST", body=body,\ + body = {"passwordCredentials": {"username": "disabled", + "password": "secrete"}} + resp, content = h.request(url, "POST", body=json.dumps(body), headers={"Content-Type": "application/json"}) content = json.loads(content) if int(resp['status']) == 500: @@ -208,8 +204,8 @@ class authorize_test(identity_test): password="secrete" username="disabled" \ />' resp, content = h.request(url, "POST", body=body,\ - headers={"Content-Type": "application/xml", - "ACCEPT": "application/xml"}) + headers={"Content-Type": "application/xml", + "ACCEPT": "application/xml"}) content = etree.fromstring(content) if int(resp['status']) == 500: self.fail('IDM fault') @@ -220,9 +216,9 @@ class authorize_test(identity_test): def test_a_authorize_user_wrong(self): h = httplib2.Http(".cache") url = '%stoken' % URL - body = '{"passwordCredentials" : { "username-w" : "disabled", \ - "password" : "secrete"} }' - resp, content = h.request(url, "POST", body=body,\ + body = {"passwordCredentials": {"username-w": "disabled", + "password": "secrete"}} + resp, content = h.request(url, "POST", body=json.dumps(body), headers={"Content-Type": "application/json"}) content = json.loads(content) if int(resp['status']) == 500: @@ -422,11 +418,11 @@ class create_tenant_test(tenant_test): self.tenant = content['tenant']['id'] url = '%stenants' % (URL) - body = '{"tenant": { "id": "%s", \ - "description": "A description ...", "enabled"\ - :true } }' % self.tenant - resp, content = h.request(url, "POST", body=body,\ - headers={"Content-Type": "application/json",\ + body = {"tenant": {"id": self.tenant, + "description": "A description ...", + "enabled": True}} + resp, content = h.request(url, "POST", body=json.dumps(body), + headers={"Content-Type": "application/json", "X-Auth-Token": self.token}) if int(resp['status']) == 500: @@ -466,11 +462,11 @@ class create_tenant_test(tenant_test): self.tenant = content['tenant']['id'] url = '%stenants' % (URL) - body = '{"tenant": { "id": "%s", \ - "description": "A description ...", "enabled"\ - :true } }' % self.tenant - resp, content = h.request(url, "POST", body=body,\ - headers={"Content-Type": "application/json",\ + body = {"tenant": {"id": self.tenant, + "description": "A description ...", + "enabled": True}} + resp, content = h.request(url, "POST", body=json.dumps(body), + headers={"Content-Type": "application/json", "X-Auth-Token": self.exp_auth_token}) if int(resp['status']) == 500: @@ -511,10 +507,10 @@ class create_tenant_test(tenant_test): self.tenant = content['tenant']['id'] url = '%stenants' % (URL) - body = '{"tenant": { "id": "%s", \ - "description": "A description ...", "enabled"\ - :true } }' % self.tenant - resp, content = h.request(url, "POST", body=body,\ + body = {"tenant": {"id": self.tenant, + "description": "A description ...", + "enabled": True}} + resp, content = h.request(url, "POST", body=json.dumps(body), headers={"Content-Type": "application/json"}) if int(resp['status']) == 500: -- cgit From 055aa283d55888a9eafc174c5a7f2a427531fd55 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Wed, 27 Apr 2011 18:20:29 -0700 Subject: Renamed protocol modules to auth_[type] Renamed PAPIAuth to RemoteAuth - better documented it and added redirect to auth_token (to stop using this) Cleaned up ini files and ini file handling (removed hard-coded defaults) --- docs/guide/src/docbkx/idm.wadl | 371 ++---------------------- echo/echo/echo.ini | 3 +- echo/echo/echo.py | 8 +- echo/echo/echo_remote.ini | 13 +- keystone/__init__.py | 12 +- keystone/auth_protocols/auth_basic.py | 114 ++++++++ keystone/auth_protocols/auth_openid.py | 113 ++++++++ keystone/auth_protocols/auth_protocol_basic.py | 114 -------- keystone/auth_protocols/auth_protocol_openid.py | 113 -------- keystone/auth_protocols/auth_protocol_token.ini | 15 - keystone/auth_protocols/auth_protocol_token.py | 252 ---------------- keystone/auth_protocols/auth_token.ini | 18 ++ keystone/auth_protocols/auth_token.py | 265 +++++++++++++++++ 13 files changed, 551 insertions(+), 860 deletions(-) create mode 100644 keystone/auth_protocols/auth_basic.py create mode 100644 keystone/auth_protocols/auth_openid.py delete mode 100644 keystone/auth_protocols/auth_protocol_basic.py delete mode 100644 keystone/auth_protocols/auth_protocol_openid.py delete mode 100644 keystone/auth_protocols/auth_protocol_token.ini delete mode 100644 keystone/auth_protocols/auth_protocol_token.py create mode 100644 keystone/auth_protocols/auth_token.ini create mode 100644 keystone/auth_protocols/auth_token.py diff --git a/docs/guide/src/docbkx/idm.wadl b/docs/guide/src/docbkx/idm.wadl index 39135b71..4e2e2d37 100644 --- a/docs/guide/src/docbkx/idm.wadl +++ b/docs/guide/src/docbkx/idm.wadl @@ -1,355 +1,20 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/echo/echo/echo.ini b/echo/echo/echo.ini index e4ac690f..b81174d0 100644 --- a/echo/echo/echo.ini +++ b/echo/echo/echo.ini @@ -24,9 +24,10 @@ paste.filter_factory = keystone:tokenauth_factory ;where to find the token auth service auth_host = 127.0.0.1 auth_port = 8080 +auth_protocol = http ;how to authenticate to the auth service for priviledged operations ;like validate token -auth_token = 999888777666 +admin_token = 999888777666 [filter:basicauth] paste.filter_factory = keystone:basicauth_factory diff --git a/echo/echo/echo.py b/echo/echo/echo.py index c73e79b8..ee950b37 100644 --- a/echo/echo/echo.py +++ b/echo/echo/echo.py @@ -54,7 +54,13 @@ class EchoApp(object): def __iter__(self): if 'HTTP_X_AUTHORIZATION' not in self.envr: - return HTTPUnauthorized(env, start_response) + return HTTPUnauthorized(self.envr, start_response) + + print ' Received:' + if 'HTTP_X_IDENTITY_STATUS' in self.envr: print ' Auth Status:', self.envr['HTTP_X_IDENTITY_STATUS'] + if 'HTTP_X_AUTHORIZATION' in self.envr: print ' Identity :', self.envr['HTTP_X_AUTHORIZATION'] + if 'HTTP_X_TENANT' in self.envr: print ' Tenant :', self.envr['HTTP_X_TENANT'] + if 'HTTP_X_GROUP' in self.envr: print ' Group :', self.envr['HTTP_X_GROUP'] accept = self.envr.get("HTTP_ACCEPT", "application/json") if accept == "application/xml": diff --git a/echo/echo/echo_remote.ini b/echo/echo/echo_remote.ini index 8f6bc89b..c27b2365 100644 --- a/echo/echo/echo_remote.ini +++ b/echo/echo/echo_remote.ini @@ -5,14 +5,15 @@ paste.app_factory = echo:app_factory [pipeline:main] pipeline = - papiauth + remoteauth echo -[filter:papiauth] -paste.filter_factory = keystone:papiauth_factory -;password which will tell us caller is trusted (otherwise we redirect them) -auth_pass = dTpw +[filter:remoteauth] +paste.filter_factory = keystone:remoteauth_factory +;password which will tell us call is coming from a trusted auth proxy +; (otherwise we redirect call) +remote_auth_pass = dTpw ;where to redirect untrusted calls to -proxy_location = http://127.0.0.1:8080/ +auth_location = http://127.0.0.1:8080/ diff --git a/keystone/__init__.py b/keystone/__init__.py index 44c15ef3..aec9d44f 100644 --- a/keystone/__init__.py +++ b/keystone/__init__.py @@ -1,13 +1,15 @@ -from middleware.papiauth import filter_factory as papiauth_factory - #TOKEN AUTH -from auth_protocols.auth_protocol_token \ +from auth_protocols.auth_token \ import filter_factory as tokenauth_factory #BASIC AUTH -from auth_protocols.auth_protocol_basic \ +from auth_protocols.auth_basic \ import filter_factory as basicauth_factory #OPENID AUTH -from auth_protocols.auth_protocol_openid \ +from auth_protocols.auth_openid \ import filter_factory as openidauth_factory + +#Remote Auth handler +from middleware.remoteauth \ + import filter_factory as remoteauth_factory diff --git a/keystone/auth_protocols/auth_basic.py b/keystone/auth_protocols/auth_basic.py new file mode 100644 index 00000000..2bc967ff --- /dev/null +++ b/keystone/auth_protocols/auth_basic.py @@ -0,0 +1,114 @@ +#!/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 + + +""" +BASIC AUTH MIDDLEWARE - STUB + +This WSGI component should perform multiple jobs: +- validate incoming basic claims +- perform all basic auth interactions with clients +- collect and forward identity information from the authentication process + such as user name, groups, etc... + +This is an Auth component as per: http://wiki.openstack.org/openstack-authn + +""" + + +PROTOCOL_NAME = "Basic Authentication" + + +class AuthProtocol(object): + """Auth Middleware that handles authenticating client calls""" + + def __init__(self, app, conf): + print "Starting the %s component" % PROTOCOL_NAME + + self.conf = conf + self.app = app + #if app is set, then we are in a WSGI pipeline and requests get passed + # on to app. If it is not set, this component should forward requests + + # where to find the OpenStack service (if not in local WSGI chain) + # these settings are only used if this component is acting as a proxy + # and the OpenSTack service is running remotely + self.service_protocol = conf.get('service_protocol', 'https') + self.service_host = conf.get('service_host') + self.service_port = int(conf.get('service_port')) + self.service_url = '%s://%s:%s' % (self.service_protocol, + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service or PAPIAuth + self.service_pass = conf.get('service_pass') + + # delay_auth_decision means we still allow unauthenticated requests + # through and we let the downstream service make the final decision + self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) + + def __call__(self, env, start_response): + def custom_start_response(status, headers): + if self.delay_auth_decision: + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + return start_response(status, headers) + + #TODO(Ziad): PERFORM BASIC AUTH + + #Auth processed, headers added now decide how to pass on the call + if self.app: + # Pass to downstream WSGI component + env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass + return self.app(env, custom_start_response) + + proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers,\ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(env, start_response) + + +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 + + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AuthProtocol(None, conf) + +if __name__ == "__main__": + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_basic.ini"), + global_conf={"log_name": "auth_basic.log"}) + wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_openid.py b/keystone/auth_protocols/auth_openid.py new file mode 100644 index 00000000..ac9121f7 --- /dev/null +++ b/keystone/auth_protocols/auth_openid.py @@ -0,0 +1,113 @@ +#!/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 + + +""" +OPENID AUTH MIDDLEWARE - STUB + +This WSGI component should perform multiple jobs: +- validate incoming openid claims +- perform all openid interactions with clients +- collect and forward identity information from the openid authentication + such as user name, groups, etc... + +This is an Auth component as per: http://wiki.openstack.org/openstack-authn +""" + + +PROTOCOL_NAME = "OpenID Authentication" + + +class AuthProtocol(object): + """Auth Middleware that handles authenticating client calls""" + + def __init__(self, app, conf): + print "Starting the %s component" % PROTOCOL_NAME + + self.conf = conf + self.app = app + #if app is set, then we are in a WSGI pipeline and requests get passed + # on to app. If it is not set, this component should forward requests + + # where to find the OpenStack service (if not in local WSGI chain) + # these settings are only used if this component is acting as a proxy + # and the OpenSTack service is running remotely + self.service_protocol = conf.get('service_protocol', 'http') + self.service_host = conf.get('service_host', '127.0.0.1') + self.service_port = int(conf.get('service_port', 8090)) + self.service_url = '%s://%s:%s' % (self.service_protocol, + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service or PAPIAuth + self.service_pass = conf.get('service_pass', 'dTpw') + + # delay_auth_decision means we still allow unauthenticated requests + # through and we let the downstream service make the final decision + self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) + + def __call__(self, env, start_response): + def custom_start_response(status, headers): + if self.delay_auth_decision: + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + return start_response(status, headers) + + #TODO(Rasib): PERFORM OPENID AUTH + + #Auth processed, headers added now decide how to pass on the call + if self.app: + # Pass to downstream WSGI component + env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass + return self.app(env, custom_start_response) + + proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers,\ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(env, start_response) + + +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 + + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AuthProtocol(None, conf) + +if __name__ == "__main__": + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_openid.ini"), + global_conf={"log_name": "auth_openid.log"}) + wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_basic.py b/keystone/auth_protocols/auth_protocol_basic.py deleted file mode 100644 index 0285fc53..00000000 --- a/keystone/auth_protocols/auth_protocol_basic.py +++ /dev/null @@ -1,114 +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 - - -""" -BASIC AUTH MIDDLEWARE - STUB - -This WSGI component should perform multiple jobs: -- validate incoming basic claims -- perform all basic auth interactions with clients -- collect and forward identity information from the authentication process - such as user name, groups, etc... - -This is an Auth component as per: http://wiki.openstack.org/openstack-authn - -""" - - -PROTOCOL_NAME = "Basic Authentication" - - -class AuthProtocol(object): - """Auth Middleware that handles authenticating client calls""" - - def __init__(self, app, conf): - print "Starting the %s component" % PROTOCOL_NAME - - self.conf = conf - self.app = app - #if app is set, then we are in a WSGI pipeline and requests get passed - # on to app. If it is not set, this component should forward requests - - # where to find the OpenStack service (if not in local WSGI chain) - # these settings are only used if this component is acting as a proxy - # and the OpenSTack service is running remotely - self.service_protocol = conf.get('service_protocol', 'http') - self.service_host = conf.get('service_host', '127.0.0.1') - self.service_port = int(conf.get('service_port', 8090)) - self.service_url = '%s://%s:%s' % (self.service_protocol, - self.service_host, - self.service_port) - # used to verify this component with the OpenStack service or PAPIAuth - self.service_pass = conf.get('service_pass', 'dTpw') - - # delay_auth_decision means we still allow unauthenticated requests - # through and we let the downstream service make the final decision - self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) - - def __call__(self, env, start_response): - def custom_start_response(status, headers): - if self.delay_auth_decision: - headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) - return start_response(status, headers) - - #TODO(Ziad): PERFORM BASIC AUTH - - #Auth processed, headers added now decide how to pass on the call - if self.app: - # Pass to downstream WSGI component - env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass - return self.app(env, custom_start_response) - - proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass - # We are forwarding to a remote service (no downstream WSGI app) - req = Request(proxy_headers) - parsed = urlparse(req.url) - conn = http_connect(self.service_host, self.service_port, \ - req.method, parsed.path, \ - proxy_headers,\ - ssl=(self.service_protocol == 'https')) - resp = conn.getresponse() - data = resp.read() - #TODO: use a more sophisticated proxy - # we are rewriting the headers now - return Response(status=resp.status, body=data)(env, start_response) - - -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 - - -def app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return AuthProtocol(None, conf) - -if __name__ == "__main__": - app = loadapp("config:" + \ - os.path.join(os.path.abspath(os.path.dirname(__file__)), - "auth_protocol_basic.ini"), - global_conf={"log_name": "auth_protocol_basic.log"}) - wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_openid.py b/keystone/auth_protocols/auth_protocol_openid.py deleted file mode 100644 index 8a333cf4..00000000 --- a/keystone/auth_protocols/auth_protocol_openid.py +++ /dev/null @@ -1,113 +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 - - -""" -OPENID AUTH MIDDLEWARE - STUB - -This WSGI component should perform multiple jobs: -- validate incoming openid claims -- perform all openid interactions with clients -- collect and forward identity information from the openid authentication - such as user name, groups, etc... - -This is an Auth component as per: http://wiki.openstack.org/openstack-authn -""" - - -PROTOCOL_NAME = "OpenID Authentication" - - -class AuthProtocol(object): - """Auth Middleware that handles authenticating client calls""" - - def __init__(self, app, conf): - print "Starting the %s component" % PROTOCOL_NAME - - self.conf = conf - self.app = app - #if app is set, then we are in a WSGI pipeline and requests get passed - # on to app. If it is not set, this component should forward requests - - # where to find the OpenStack service (if not in local WSGI chain) - # these settings are only used if this component is acting as a proxy - # and the OpenSTack service is running remotely - self.service_protocol = conf.get('service_protocol', 'http') - self.service_host = conf.get('service_host', '127.0.0.1') - self.service_port = int(conf.get('service_port', 8090)) - self.service_url = '%s://%s:%s' % (self.service_protocol, - self.service_host, - self.service_port) - # used to verify this component with the OpenStack service or PAPIAuth - self.service_pass = conf.get('service_pass', 'dTpw') - - # delay_auth_decision means we still allow unauthenticated requests - # through and we let the downstream service make the final decision - self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) - - def __call__(self, env, start_response): - def custom_start_response(status, headers): - if self.delay_auth_decision: - headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) - return start_response(status, headers) - - #TODO(Rasib): PERFORM OPENID AUTH - - #Auth processed, headers added now decide how to pass on the call - if self.app: - # Pass to downstream WSGI component - env['HTTP_AUTHORIZATION'] = "Basic %s" % self.service_pass - return self.app(env, custom_start_response) - - proxy_headers['AUTHORIZATION'] = "Basic %s" % self.service_pass - # We are forwarding to a remote service (no downstream WSGI app) - req = Request(proxy_headers) - parsed = urlparse(req.url) - conn = http_connect(self.service_host, self.service_port, \ - req.method, parsed.path, \ - proxy_headers,\ - ssl=(self.service_protocol == 'https')) - resp = conn.getresponse() - data = resp.read() - #TODO: use a more sophisticated proxy - # we are rewriting the headers now - return Response(status=resp.status, body=data)(env, start_response) - - -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 - - -def app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return AuthProtocol(None, conf) - -if __name__ == "__main__": - app = loadapp("config:" + \ - os.path.join(os.path.abspath(os.path.dirname(__file__)), - "auth_protocol_openid.ini"), - global_conf={"log_name": "auth_protocol_openid.log"}) - wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_protocol_token.ini b/keystone/auth_protocols/auth_protocol_token.ini deleted file mode 100644 index 094c3aa2..00000000 --- a/keystone/auth_protocols/auth_protocol_token.ini +++ /dev/null @@ -1,15 +0,0 @@ -[DEFAULT] - -[app:main] -paste.app_factory = auth_protocol_token:app_factory -auth_host = 127.0.0.1 -auth_port = 8080 -auth_token = 999888777666 -delay_auth_decision = 0 - -service_protocol = http -service_host = 127.0.0.1 -service_port = 8100 -service_pass = dTpw - - diff --git a/keystone/auth_protocols/auth_protocol_token.py b/keystone/auth_protocols/auth_protocol_token.py deleted file mode 100644 index 8bfc1a05..00000000 --- a/keystone/auth_protocols/auth_protocol_token.py +++ /dev/null @@ -1,252 +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 - -This WSGI component performs multiple jobs: -- it verifies that incoming client requests have valid tokens by verifying - tokens with the auth service. -- it will reject unauthenticated requests UNLESS it is in 'delay_auth_decision' - mode, which means the final decision is delegated to the downstream WSGI - component (usually the OpenStack service) -- it will collect and forward identity information from a valid token - such as user name, groups, etc... - -Refer to: http://wiki.openstack.org/openstack-authn - - -HEADERS -------- -Headers starting with HTTP_ is a standard http header -Headers starting with HTTP_X is an extended http header - -> Coming in from initial call from client or customer -HTTP_X_AUTH_TOKEN : the client token being passed in -HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) - to support cloud files -> Used for communication between components -www-authenticate : only used if this component is being used remotely -HTTP_AUTHORIZATION : basic auth password used to validate the connection - -> What we add to the request for use by the OpenStack service -HTTP_X_AUTHORIZATION: the client identity being passed in - -""" - -import eventlet -from eventlet import wsgi -import json -import os -from paste.deploy import loadapp -from urlparse import urlparse -from webob.exc import HTTPUnauthorized -from webob.exc import Request, Response -import httplib - -from keystone.common.bufferedhttp import http_connect_raw as http_connect - -PROTOCOL_NAME = "Token Authentication" - - -def _decorate_request_headers(header, value, proxy_headers, env): - proxy_headers[header] = value - env["HTTP_%s" % header] = value - - -class AuthProtocol(object): - """Auth Middleware that handles authenticating client calls""" - - def _init_protocol_common(self, app, conf): - """ Common initialization code""" - print "Starting the %s component" % PROTOCOL_NAME - - self.conf = conf - self.app = app - #if app is set, then we are in a WSGI pipeline and requests get passed - # on to app. If it is not set, this component should forward requests - - # where to find the OpenStack service (if not in local WSGI chain) - # these settings are only used if this component is acting as a proxy - # and the OpenSTack service is running remotely - self.service_protocol = conf.get('service_protocol', 'http') - self.service_host = conf.get('service_host', '127.0.0.1') - self.service_port = int(conf.get('service_port', 8090)) - self.service_url = '%s://%s:%s' % (self.service_protocol, - self.service_host, - self.service_port) - # used to verify this component with the OpenStack service or PAPIAuth - self.service_pass = conf.get('service_pass', 'dTpw') - - # delay_auth_decision means we still allow unauthenticated requests - # through and we let the downstream service make the final decision - self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) - - def _init_protocol(self, app, conf): - """ Protocol specific initialization """ - - # where to find the auth service (we use this to validate tokens) - self.auth_host = conf.get('auth_ip', '127.0.0.1') - self.auth_port = int(conf.get('auth_port', 8080)) - - # Credentials used to verify this component with the Auth service since - # validating tokens is a priviledged call - self.auth_token = conf.get('auth_token', 'dTpw') - - def __init__(self, app, conf): - """ Common initialization code """ - - #TODO: maybe we rafactor this into a superclass - self._init_protocol_common(app, conf) # Applies to all protocols - self._init_protocol(app, conf) # Specific to this protocol - - def get_admin_auth_token(self, username, password, tenant): - """ - This function gets an admin auth token to be used by this service to - validate a user's token. Validate_token is a priviledged call so - it needs to be authenticated by a service that is calling it - """ - headers = {"Content-type": "application/json", "Accept": "text/json"} - params = {"passwordCredentials": {"username": username, - "password": password, - "tenantId": "1"}} - conn = httplib.HTTPConnection("%s:%s" \ - % (self.auth_host, self.auth_port)) - conn.request("POST", "/v1.0/token", json.dumps(params), \ - headers=headers) - response = conn.getresponse() - data = response.read() - ret = data - return ret - - def __call__(self, env, start_response): - def custom_start_response(status, headers): - if self.delay_auth_decision: - headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) - return start_response(status, headers) - - #Prep headers to proxy request to remote service - proxy_headers = env.copy() - user = '' - - #Look for token in request - token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) - if not token: - #No token was provided - if self.delay_auth_decision: - _decorate_request_headers("X_IDENTITY_STATUS", - "Invalid", - proxy_headers, env) - else: - return HTTPUnauthorized() - else: - # this request is claiming it has a valid token, let's check - # with the auth service - # Step 1: We need to auth with the keystone service, so get an - # admin token - #TODO: Need to properly implement this, where to store creds - # for now using token from ini - #auth = self.get_admin_auth_token("admin", "secrete", "1") - #admin_token = json.loads(auth)["auth"]["token"]["id"] - - # Step 2: validate the user's token with the auth service - # since this is a priviledged op,m we need to auth ourselves - # by using an admin token - headers = {"Content-type": "application/json", - "Accept": "text/json", - "X-Auth-Token": self.auth_token} - ##TODO:we need to figure out how to auth to keystone - #since validate_token is a priviledged call - #Khaled's version uses creds to get a token - # "X-Auth-Token": admin_token} - # we're using a test token from the ini file for now - conn = http_connect(self.auth_host, self.auth_port, 'GET', - '/v1.0/token/%s' % token, headers=headers) - resp = conn.getresponse() - data = resp.read() - conn.close() - - if not str(resp.status).startswith('20'): - # Keystone rejected claim - if self.delay_auth_decision: - # Downstream service will receive call still and decide - _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", - proxy_headers, env) - else: - # Reject the response & send back the error - # (not delay_auth_decision) - return HTTPUnauthorized(headers=headers)(env, - start_response) - else: - # Valid token. Get user data and put it in to the call - # so the downstream service can use iot - dict_response = json.loads(data) - user = dict_response['auth']['user']['username'] - # TODO(Ziad): add additional details we may need, - # like tenant and group info - _decorate_request_headers('X_AUTHORIZATION', "Proxy %s" % user, - proxy_headers, env) - _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", - proxy_headers, env) - - #Token/Auth processed, headers added now decide how to pass on the call - _decorate_request_headers('AUTHORIZATION', - "Basic %s" % self.service_pass, - proxy_headers, - env) - if self.app: - # Pass to downstream WSGI component - return self.app(env, custom_start_response) - else: - # We are forwarding to a remote service (no downstream WSGI app) - req = Request(proxy_headers) - parsed = urlparse(req.url) - conn = http_connect(self.service_host, self.service_port, \ - req.method, parsed.path, \ - proxy_headers,\ - ssl=(self.service_protocol == 'https')) - resp = conn.getresponse() - data = resp.read() - #TODO: use a more sophisticated proxy - # we are rewriting the headers now - return Response(status=resp.status, body=data)(proxy_headers, - start_response) - - -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 - - -def app_factory(global_conf, **local_conf): - conf = global_conf.copy() - conf.update(local_conf) - return AuthProtocol(None, conf) - -if __name__ == "__main__": - app = loadapp("config:" + \ - os.path.join(os.path.abspath(os.path.dirname(__file__)), - "auth_protocol_token.ini"), global_conf={"log_name": "echo.log"}) - wsgi.server(eventlet.listen(('', 8090)), app) diff --git a/keystone/auth_protocols/auth_token.ini b/keystone/auth_protocols/auth_token.ini new file mode 100644 index 00000000..3154fc69 --- /dev/null +++ b/keystone/auth_protocols/auth_token.ini @@ -0,0 +1,18 @@ +[DEFAULT] + +[app:main] +paste.app_factory = auth_token:app_factory + +auth_protocol = http +auth_host = 127.0.0.1 +auth_port = 8080 +admin_token = 999888777666 + +delay_auth_decision = 0 + +service_protocol = http +service_host = 127.0.0.1 +service_port = 8100 +service_pass = dTpw + + diff --git a/keystone/auth_protocols/auth_token.py b/keystone/auth_protocols/auth_token.py new file mode 100644 index 00000000..cee8e84f --- /dev/null +++ b/keystone/auth_protocols/auth_token.py @@ -0,0 +1,265 @@ +#!/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 + +This WSGI component performs multiple jobs: +- it verifies that incoming client requests have valid tokens by verifying + tokens with the auth service. +- it will reject unauthenticated requests UNLESS it is in 'delay_auth_decision' + mode, which means the final decision is delegated to the downstream WSGI + component (usually the OpenStack service) +- it will collect and forward identity information from a valid token + such as user name, groups, etc... + +Refer to: http://wiki.openstack.org/openstack-authn + + +HEADERS +------- +Headers starting with HTTP_ is a standard http header +Headers starting with HTTP_X is an extended http header + +> Coming in from initial call from client or customer +HTTP_X_AUTH_TOKEN : the client token being passed in +HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) + to support cloud files +> Used for communication between components +www-authenticate : only used if this component is being used remotely +HTTP_AUTHORIZATION : basic auth password used to validate the connection + +> What we add to the request for use by the OpenStack service +HTTP_X_AUTHORIZATION: the client identity being passed in + +""" + +import eventlet +from eventlet import wsgi +import json +import os +from paste.deploy import loadapp +from urlparse import urlparse +from webob.exc import HTTPUnauthorized +from webob.exc import Request, Response +import httplib + +from keystone.common.bufferedhttp import http_connect_raw as http_connect + +PROTOCOL_NAME = "Token Authentication" + + +def _decorate_request_headers(header, value, proxy_headers, env): + proxy_headers[header] = value + env["HTTP_%s" % header] = value + + +class AuthProtocol(object): + """Auth Middleware that handles authenticating client calls""" + + def _init_protocol_common(self, app, conf): + """ Common initialization code""" + print "Starting the %s component" % PROTOCOL_NAME + + self.conf = conf + self.app = app + #if app is set, then we are in a WSGI pipeline and requests get passed + # on to app. If it is not set, this component should forward requests + + # where to find the OpenStack service (if not in local WSGI chain) + # these settings are only used if this component is acting as a proxy + # and the OpenSTack service is running remotely + self.service_protocol = conf.get('service_protocol', 'https') + self.service_host = conf.get('service_host') + self.service_port = int(conf.get('service_port')) + self.service_url = '%s://%s:%s' % (self.service_protocol, + self.service_host, + self.service_port) + # used to verify this component with the OpenStack service or PAPIAuth + self.service_pass = conf.get('service_pass') + + # delay_auth_decision means we still allow unauthenticated requests + # through and we let the downstream service make the final decision + self.delay_auth_decision = int(conf.get('delay_auth_decision', 0)) + + def _init_protocol(self, app, conf): + """ Protocol specific initialization """ + + # where to find the auth service (we use this to validate tokens) + self.auth_host = conf.get('auth_host') + self.auth_port = int(conf.get('auth_port')) + self.auth_protocol = conf.get('auth_protocol', 'https') + self.auth_location = "%s://%s:%s" % (self.auth_protocol, self.auth_host, + self.auth_port) + + # Credentials used to verify this component with the Auth service since + # validating tokens is a priviledged call + self.admin_token = conf.get('admin_token') + + def __init__(self, app, conf): + """ Common initialization code """ + + #TODO: maybe we rafactor this into a superclass + self._init_protocol_common(app, conf) # Applies to all protocols + self._init_protocol(app, conf) # Specific to this protocol + + def get_admin_auth_token(self, username, password, tenant): + """ + This function gets an admin auth token to be used by this service to + validate a user's token. Validate_token is a priviledged call so + it needs to be authenticated by a service that is calling it + """ + headers = {"Content-type": "application/json", "Accept": "text/json"} + params = {"passwordCredentials": {"username": username, + "password": password, + "tenantId": "1"}} + conn = httplib.HTTPConnection("%s:%s" \ + % (self.auth_host, self.auth_port)) + conn.request("POST", "/v1.0/token", json.dumps(params), \ + headers=headers) + response = conn.getresponse() + data = response.read() + ret = data + return ret + + def __call__(self, env, start_response): + def custom_start_response(status, headers): + if self.delay_auth_decision: + headers.append(('WWW-Authenticate', "Basic realm='API Realm'")) + return start_response(status, headers) + + #Prep headers to proxy request to remote service + proxy_headers = env.copy() + user = '' + + #Look for token in request + token = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN')) + if not token: + #No token was provided + if self.delay_auth_decision: + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", + proxy_headers, env) + else: + # Redirect client to auth server + return HTTPUseProxy(location=self.auth_location)(env, + start_response) + else: + # this request is claiming it has a valid token, let's check + # with the auth service + # Step 1: We need to auth with the keystone service, so get an + # admin token + #TODO: Need to properly implement this, where to store creds + # for now using token from ini + #auth = self.get_admin_auth_token("admin", "secrete", "1") + #admin_token = json.loads(auth)["auth"]["token"]["id"] + + # Step 2: validate the user's token with the auth service + # since this is a priviledged op,m we need to auth ourselves + # by using an admin token + headers = {"Content-type": "application/json", + "Accept": "text/json", + "X-Auth-Token": self.admin_token} + ##TODO:we need to figure out how to auth to keystone + #since validate_token is a priviledged call + #Khaled's version uses creds to get a token + # "X-Auth-Token": admin_token} + # we're using a test token from the ini file for now + conn = http_connect(self.auth_host, self.auth_port, 'GET', + '/v1.0/token/%s' % token, headers=headers) + resp = conn.getresponse() + data = resp.read() + conn.close() + + if not str(resp.status).startswith('20'): + # Keystone rejected claim + if self.delay_auth_decision: + # Downstream service will receive call still and decide + _decorate_request_headers("X_IDENTITY_STATUS", "Invalid", + proxy_headers, env) + else: + # Reject the response & send back the error + # (not delay_auth_decision) + return HTTPUnauthorized(headers=headers)(env, + start_response) + else: + # Valid token. Get user data and put it in to the call + # so the downstream service can use iot + dict_response = json.loads(data) + #TODO(Ziad): make this more robust + user = dict_response['auth']['user']['username'] + tenant = dict_response['auth']['user']['tenantId'] + group = '%s/%s' % (dict_response['auth']['user']['groups']['group'][0]['id'], + dict_response['auth']['user']['groups']['group'][0]['tenantId']) + + # TODO(Ziad): add additional details we may need, + # like tenant and group info + _decorate_request_headers('X_AUTHORIZATION', "Proxy %s" % user, + proxy_headers, env) + _decorate_request_headers("X_IDENTITY_STATUS", "Confirmed", + proxy_headers, env) + _decorate_request_headers('X_TENANT', tenant, + proxy_headers, env) + _decorate_request_headers('X_GROUP', group, + proxy_headers, env) + + #Token/Auth processed, headers added now decide how to pass on the call + _decorate_request_headers('AUTHORIZATION', + "Basic %s" % self.service_pass, + proxy_headers, + env) + if self.app: + # Pass to downstream WSGI component + return self.app(env, custom_start_response) + else: + # We are forwarding to a remote service (no downstream WSGI app) + req = Request(proxy_headers) + parsed = urlparse(req.url) + conn = http_connect(self.service_host, self.service_port, \ + req.method, parsed.path, \ + proxy_headers,\ + ssl=(self.service_protocol == 'https')) + resp = conn.getresponse() + data = resp.read() + #TODO: use a more sophisticated proxy + # we are rewriting the headers now + return Response(status=resp.status, body=data)(proxy_headers, + start_response) + + +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 + + +def app_factory(global_conf, **local_conf): + conf = global_conf.copy() + conf.update(local_conf) + return AuthProtocol(None, conf) + +if __name__ == "__main__": + app = loadapp("config:" + \ + os.path.join(os.path.abspath(os.path.dirname(__file__)), + "auth_token.ini"), global_conf={"log_name": "auth_token.log"}) + wsgi.server(eventlet.listen(('', 8090)), app) -- cgit From 2a77f0a3d9ae638100624971c3e279d0fc1e263c Mon Sep 17 00:00:00 2001 From: "Jorge L. Williams" Date: Thu, 28 Apr 2011 00:30:11 -0500 Subject: Reverted accidental(?) WADL deletion >:-( --- docs/guide/src/docbkx/idm.wadl | 371 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 353 insertions(+), 18 deletions(-) diff --git a/docs/guide/src/docbkx/idm.wadl b/docs/guide/src/docbkx/idm.wadl index 4e2e2d37..39135b71 100644 --- a/docs/guide/src/docbkx/idm.wadl +++ b/docs/guide/src/docbkx/idm.wadl @@ -1,20 +1,355 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit From fb12e9a70fef20cb8f0df4220bfb259ca49ca763 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Wed, 27 Apr 2011 23:13:40 -0700 Subject: Restored remoteauth --- keystone/middleware/papiauth.py | 105 -------------------------------------- keystone/middleware/remoteauth.py | 105 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 105 deletions(-) delete mode 100644 keystone/middleware/papiauth.py create mode 100644 keystone/middleware/remoteauth.py diff --git a/keystone/middleware/papiauth.py b/keystone/middleware/papiauth.py deleted file mode 100644 index 0af14bb9..00000000 --- a/keystone/middleware/papiauth.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python -# vim: tabstop=4 shiftwidth=4 softtabstop=4 -# -# 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. - - -""" -Auth Middleware that handles auth for a service - -This module can be installed as a filter in front of your service to validate -that requests are coming from a trusted component that has handled -authenticating the call. If a call comes from an untrusted source, it will -redirect it back to be properly authenticated. This is done by sending our a -305 proxy redirect response with the URL for the auth service. - -The auth service settings are specified in the INI file (keystone.ini). The ini -file is passed in as the WSGI config file when starting the service. For this -proof of concept, the ini file is in echo/echo/echo.ini. - -In the current implementation use a basic auth password to verify that the -request is coming from a valid auth component or service - -Refer to: http://wiki.openstack.org/openstack-authn - - -HEADERS -------- -HTTP_ is a standard http header -HTTP_X is an extended http header - -> Coming in from initial call -HTTP_X_AUTH_TOKEN : the client token being passed in -HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) - to support cloud files -> Used for communication between components -www-authenticate : only used if this component is being used remotely -HTTP_AUTHORIZATION : basic auth password used to validate the connection - -> What we add to the request for use by the OpenStack service -HTTP_X_AUTHORIZATION: the client identity being passed in - - -""" - -from webob.exc import HTTPUseProxy, HTTPUnauthorized - - -class PAPIAuth(object): - - # app is the downstream WSGI component, usually the OpenStack service - # - # if app is not provided, the assumption is this filter is being run - # from a separate server. - - def __init__(self, app, conf): - # app is the next app in WSGI chain - eventually the OpenStack service - self.app = app - self.conf = conf - # where to redirect untrusted requests to go and auth - self.proxy_location = conf.get('proxy_location') - # secret that will tell us a request is coming from a trusted auth - # component - self.auth_pass = conf.get('auth_pass', 'dTpw') - print 'Starting PAPI Auth middleware' - - def __call__(self, env, start_response): - # Validate the request is trusted - # Authenticate the Auth component itself. - headers = [('www-authenticate', 'Basic realm="API Auth"')] - if 'HTTP_AUTHORIZATION' not in env: - return HTTPUnauthorized(headers=headers)(env, start_response) - else: - auth_type, encoded_creds = env['HTTP_AUTHORIZATION'].split(None, 1) - if encoded_creds != self.auth_pass: - return HTTPUnauthorized(headers=headers)(env, start_response) - - # Make sure that the user has been authenticated by the Auth Service - if 'HTTP_X_AUTHORIZATION' not in env: - return HTTPUseProxy(location=self.proxy_location)(env, - start_response) - - return self.app(env, start_response) - - -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 PAPIAuth(app, conf) - return auth_filter diff --git a/keystone/middleware/remoteauth.py b/keystone/middleware/remoteauth.py new file mode 100644 index 00000000..5919abd6 --- /dev/null +++ b/keystone/middleware/remoteauth.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# 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. + + +""" +Auth Middleware that handles auth for a service + +This module can be installed as a filter in front of your service to validate +that requests are coming from a trusted component that has handled +authenticating the call. If a call comes from an untrusted source, it will +redirect it back to be properly authenticated. This is done by sending our a +305 proxy redirect response with the URL for the auth service. + +The auth service settings are specified in the INI file (keystone.ini). The ini +file is passed in as the WSGI config file when starting the service. For this +proof of concept, the ini file is in echo/echo/echo.ini. + +In the current implementation use a basic auth password to verify that the +request is coming from a valid auth component or service + +Refer to: http://wiki.openstack.org/openstack-authn + + +HEADERS +------- +HTTP_ is a standard http header +HTTP_X is an extended http header + +> Coming in from initial call +HTTP_X_AUTH_TOKEN : the client token being passed in +HTTP_X_STORAGE_TOKEN: the client token being passed in (legacy Rackspace use) + to support cloud files +> Used for communication between components +www-authenticate : only used if this component is being used remotely +HTTP_AUTHORIZATION : basic auth password used to validate the connection + +> What we add to the request for use by the OpenStack service +HTTP_X_AUTHORIZATION: the client identity being passed in + + +""" + +from webob.exc import HTTPUseProxy, HTTPUnauthorized + + +class RemoteAuth(object): + + # app is the downstream WSGI component, usually the OpenStack service + # + # if app is not provided, the assumption is this filter is being run + # from a separate server. + + def __init__(self, app, conf): + # app is the next app in WSGI chain - eventually the OpenStack service + self.app = app + self.conf = conf + # where to redirect untrusted requests to go and auth + self.auth_location = conf.get('auth_location') + # secret that will tell us a request is coming from a trusted auth + # component + self.remote_auth_pass = conf.get('remote_auth_pass') + print 'Starting Remote Auth middleware' + + def __call__(self, env, start_response): + # Validate the request is trusted + # Authenticate the Auth component itself. + headers = [('www-authenticate', 'Basic realm="API Auth"')] + if 'HTTP_AUTHORIZATION' not in env: + return HTTPUnauthorized(headers=headers)(env, start_response) + else: + auth_type, encoded_creds = env['HTTP_AUTHORIZATION'].split(None, 1) + if encoded_creds != self.remote_auth_pass: + return HTTPUnauthorized(headers=headers)(env, start_response) + + # Make sure that the user has been authenticated by the Auth Service + if 'HTTP_X_AUTHORIZATION' not in env: + return HTTPUseProxy(location=self.auth_location)(env, + start_response) + + return self.app(env, start_response) + + +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 RemoteAuth(app, conf) + return auth_filter -- cgit From b42d40a726868be543e877706958a0921d4dcd93 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Thu, 28 Apr 2011 01:19:15 -0700 Subject: Minor changes + call using WSGI instead of bottle --- README.md | 22 ++++++++++++---------- keystone/identity.py | 7 ++++--- keystone/keystone.ini | 13 ++++++++++++- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 2126bb87..3753b402 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,15 @@ This initial proof of concept aims to address the current use cases in Swift and SERVICES: --------- -* Keystone - authentication service -* Auth_Protocal_Token - WSGI middleware that can be used to handle token auth protocol (WSGI or remote proxy) -* Echo - A sample service that responds by returning call details +* Keystone - authentication service +* Auth_Token - WSGI middleware that can be used to handle token auth protocol (WSGI or remote proxy) +* Echo - A sample service that responds by returning call details Also included: -* Auth_Protocal_Basic - Stub for WSGI middleware that will be used to handle basic auth -* Auth_Protocal_OpenID - Stub for WSGI middleware that will be used to handle openid auth protocol +* Auth_Basic - Stub for WSGI middleware that will be used to handle basic auth +* Auth_OpenID - Stub for WSGI middleware that will be used to handle openid auth protocol +* RemoteAuth - WSGI middleware that can be used in services (like Swift, Nova, and Glance) when Auth middleware is running remotely + DEPENDENCIES: ------------- @@ -64,17 +66,17 @@ RUNNING KEYSTONE: RUNNING TEST SERVICE: --------------------- -Standalone stack (with PAPIAuth and Auth_Protocol_Token) + Standalone stack (with Auth_Token) $ cd echo/echo $ python echo.py -Distributed stack (with PAPIAuth local and Auth_Protocol_Token remote) + Distributed stack (with RemoteAuth local and Auth_Token remote) $ cd echo/echo $ python echo.py --remote in separate session - $ cd keystone/auth_protocol - $ python auth_protocol_token.py --remote + $ cd keystone/auth_protocols + $ python auth_token.py --remote DEMO CLIENT: --------------------- @@ -145,7 +147,7 @@ Once the Identity service is running, go to unit test/unit directory For more on unit testing please refer -python test_identity --help + python test_identity --help diff --git a/keystone/identity.py b/keystone/identity.py index 2e2184d8..8bb6a4f7 100644 --- a/keystone/identity.py +++ b/keystone/identity.py @@ -16,7 +16,7 @@ """ -Service that stores identoties and issues and manages tokens +Service that stores identities and issues and manages tokens HEADERS ------- @@ -40,6 +40,8 @@ import functools import logging import os import sys +import eventlet +from eventlet import wsgi import bottle from bottle import request @@ -222,7 +224,6 @@ def delete_token(token_id): return send_result(204, service.revoke_token(get_auth_token(), token_id)) - ## ## Tenant Operations ## @@ -298,4 +299,4 @@ def get_extension(ext_alias): if __name__ == "__main__": - bottle.run(host='localhost', port=8080) + wsgi.server(eventlet.listen(('', 8080)), bottle.default_app()) diff --git a/keystone/keystone.ini b/keystone/keystone.ini index 3fed30cf..626ca1ed 100644 --- a/keystone/keystone.ini +++ b/keystone/keystone.ini @@ -1,5 +1,16 @@ +; +; This file not used +; [DEFAULT] -[app:main] +[composite:main] +use = egg:Paste#urlmap +/ = home +/tenants = admin + +[app:home] +use = egg:keystone + +[app:admin] use = egg:keystone -- cgit