summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZiad Sawalha <github@highbridgellc.com>2011-05-24 11:16:19 -0500
committerZiad Sawalha <github@highbridgellc.com>2011-05-24 11:16:19 -0500
commit7bee4601197fbfbac5d0aa8804301e62cec2de21 (patch)
treeff7d820cdcab7e70c91104c335ecebfe71b4121a
parent90624dd6a136fd443651bb7e5ba41984d8e261a6 (diff)
parentc58de5c6347e8620d4a295d83098e2bcbd86143d (diff)
Merge branch 'master' of https://github.com/khussein/keystone
-rw-r--r--README.md2
-rw-r--r--docs/nova-api-paste.ini2
-rw-r--r--keystone/auth_protocols/nova_auth_token.py302
3 files changed, 7 insertions, 299 deletions
diff --git a/README.md b/README.md
index 86f8f099..994f6c6a 100644
--- a/README.md
+++ b/README.md
@@ -183,7 +183,7 @@ Initial support for using keystone as nova's identity component has been started
git clone git://github.com/khussein/keystone.git
# link keystone into the nova root dir
- ln -s keystone nova/keystone
+ ln -s keystone/keystone nova/keystone
# run nova-api based on the paste config in keystone
nova/bin/nova-api --api_paste_config=keystone/docs/nova-api-paste.ini
diff --git a/docs/nova-api-paste.ini b/docs/nova-api-paste.ini
index 9786346c..cb593ce0 100644
--- a/docs/nova-api-paste.ini
+++ b/docs/nova-api-paste.ini
@@ -93,7 +93,7 @@ auth_protocol = http
admin_token = 999888777666
[filter:auth_shim]
-paste.filter_factory = keystone.auth_protocols.nova_auth_token:KeystoneAuthShim.factory
+paste.filter_factory = keystone.auth_protocols.auth_token:KeystoneAuthShim.factory
[filter:ratelimit]
paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory
diff --git a/keystone/auth_protocols/nova_auth_token.py b/keystone/auth_protocols/nova_auth_token.py
index 5d930d5d..9c72db7e 100644
--- a/keystone/auth_protocols/nova_auth_token.py
+++ b/keystone/auth_protocols/nova_auth_token.py
@@ -18,296 +18,25 @@
# Not Yet PEP8 standardized
-# FIXME(ja): fix "copy & paste ware"!
-# determine how to integrate nova, dash, keystone
-
"""
-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
+NOVA LAZY PROVISIONING AUTH MIDDLEWARE
-> What we add to the request for use by the OpenStack service
-HTTP_X_AUTHORIZATION: the client identity being passed in
+This WSGI component allows keystone act as an identity service for nova by
+lazy provisioning nova projects/users as authenticated by auth_token.
+Use by applying after auth_token in the nova paste config.
+Example: docs/nova-api-paste.ini
"""
-import eventlet
-import httplib
-import json
from nova import auth
from nova import context
from nova import flags
from nova import utils
from nova import wsgi
-import os
-from paste.deploy import loadapp
-import sys
-from urlparse import urlparse
-import webob.exc
import webob.dec
-from webob.exc import HTTPUnauthorized, HTTPUseProxy
-from webob.exc import Request, Response
-
-from keystone.common.bufferedhttp import http_connect_raw as http_connect
FLAGS = flags.FLAGS
-PROTOCOL_NAME = "Token Authentication"
-
-
-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(ziad): 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 __call__(self, env, start_response):
- """ Handle incoming request. Authenticate. And send downstream. """
-
- self.start_response = start_response
- self.env = env
-
- #Prep headers to forward request to local or remote downstream service
- self.proxy_headers = env.copy()
- for header in self.proxy_headers.iterkeys():
- if header[0:5] == 'HTTP_':
- self.proxy_headers[header[5:]] = self.proxy_headers[header]
- del self.proxy_headers[header]
-
- #Look for authentication claims
- self.claims = self._get_claims(env)
- if not self.claims:
- #No claim(s) provided
- if self.delay_auth_decision:
- #Configured to allow downstream service to make final decision.
- #So mark status as Invalid and forward the request downstream
- self._decorate_request("X_IDENTITY_STATUS", "Invalid")
- else:
- #Respond to client as appropriate for this auth protocol
- return self._reject_request()
- else:
- # this request is presenting claims. Let's validate them
- valid = self._validate_claims(self.claims)
- if not valid:
- # Keystone rejected claim
- if self.delay_auth_decision:
- # Downstream service will receive call still and decide
- self._decorate_request("X_IDENTITY_STATUS", "Invalid")
- else:
- #Respond to client as appropriate for this auth protocol
- return self._reject_claims()
- else:
- self._decorate_request("X_IDENTITY_STATUS", "Confirmed")
-
- #Collect information about valid claims
- if valid:
- claims = self._expound_claims()
- if claims:
- # TODO(Ziad): add additional details we may need,
- # like tenant and group info
- self._decorate_request('X_AUTHORIZATION',
- claims['user'])
- self._decorate_request('X_TENANT',
- claims['tenant'])
- self._decorate_request('X_GROUP',
- claims['group'])
- self.expanded = True
-
- #Send request downstream
- return self._forward_request()
-
- 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", "/v2.0/token", json.dumps(params), \
- headers=headers)
- response = conn.getresponse()
- data = response.read()
- return data
-
- def _get_claims(self, env):
- claims = env.get('HTTP_X_AUTH_TOKEN', env.get('HTTP_X_STORAGE_TOKEN'))
- return claims
-
- def _reject_request(self):
- # Redirect client to auth server
- return HTTPUseProxy(location=self.auth_location)(self.env,
- self.start_response)
-
- def _reject_claims(self):
- # Client sent bad claims
- return HTTPUnauthorized()(self.env,
- self.start_response)
-
- def _validate_claims(self, claims):
- """Validate claims, and provide identity information isf applicable """
-
- # Step 1: We need to auth with the keystone service, so get an
- # admin token
- #TODO(ziad): 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(ziad):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',
- '/v2.0/token/%s' % claims, headers=headers)
- resp = conn.getresponse()
- data = resp.read()
- conn.close()
-
- if not str(resp.status).startswith('20'):
- # Keystone rejected claim
- return False
- else:
- #TODO(Ziad): there is an optimization we can do here. We have just
- #received data from Keystone that we can use instead of making
- #another call in _expound_claims
- return True
-
- def _expound_claims(self):
- # Valid token. Get user data and put it in to the call
- # so the downstream service can use it
- headers = {"Content-type": "application/json",
- "Accept": "text/json",
- "X-Auth-Token": self.admin_token}
- ##TODO(ziad):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',
- '/v2.0/token/%s' % self.claims, headers=headers)
- resp = conn.getresponse()
- data = resp.read()
- conn.close()
-
- if not str(resp.status).startswith('20'):
- raise LookupError('Unable to locate claims: %s' % resp.status)
-
- token_info = json.loads(data)
- #TODO(Ziad): make this more robust
- verified_claims = {'user': token_info['auth']['user']['username'],
- 'tenant': token_info['auth']['user']['tenantId'],
- 'group': 'hack'}
- return verified_claims
-
- def _decorate_request(self, index, value):
- self.proxy_headers[index] = value
- self.env["HTTP_%s" % index] = value
-
- def _forward_request(self):
- #Token/Auth processed & claims added to headers
- self._decorate_request('AUTHORIZATION',
- "Basic %s" % self.service_pass)
- #now decide how to pass on the call
- if self.app:
- # Pass to downstream WSGI component
- return self.app(self.env, self.start_response)
- #.custom_start_response)
- else:
- # We are forwarding to a remote service (no downstream WSGI app)
- req = Request(self.proxy_headers)
- parsed = urlparse(req.url)
- conn = http_connect(self.service_host,
- self.service_port,
- req.method,
- parsed.path,
- self.proxy_headers,
- ssl=(self.service_protocol == 'https'))
- resp = conn.getresponse()
- data = resp.read()
- #TODO(ziad): use a more sophisticated proxy
- # we are rewriting the headers now
- return Response(status=resp.status, body=data)(self.proxy_headers,
- self.start_response)
-
class KeystoneAuthShim(wsgi.Middleware):
"""Lazy provisioning nova project/users from keystone tenant/user"""
@@ -339,24 +68,3 @@ class KeystoneAuthShim(wsgi.Middleware):
req.environ['nova.context'] = context.RequestContext(user_ref, project_ref)
return self.application
-
-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)