add an ec2 extension
This commit is contained in:
parent
2ed9759ff7
commit
afd897f912
|
@ -35,6 +35,8 @@ driver = keystone.backends.kvs.KvsToken
|
|||
[policy]
|
||||
driver = keystone.backends.policy.SimpleMatch
|
||||
|
||||
[ec2]
|
||||
driver = keystone.backends.kvs.KvsEc2
|
||||
|
||||
[filter:debug]
|
||||
paste.filter_factory = keystone.wsgi:Debug.factory
|
||||
|
@ -51,6 +53,8 @@ paste.filter_factory = keystone.middleware:JsonBodyMiddleware.factory
|
|||
[filter:crud_extension]
|
||||
paste.filter_factory = keystone.service:AdminCrudExtension.factory
|
||||
|
||||
[filter:ec2_extension]
|
||||
paste.filter_factory = keystone.service:Ec2Extension.factory
|
||||
|
||||
[app:public_service]
|
||||
paste.app_factory = keystone.service:public_app_factory
|
||||
|
@ -59,7 +63,7 @@ paste.app_factory = keystone.service:public_app_factory
|
|||
paste.app_factory = keystone.service:admin_app_factory
|
||||
|
||||
[pipeline:public_api]
|
||||
pipeline = token_auth admin_token_auth json_body debug public_service
|
||||
pipeline = token_auth admin_token_auth json_body debug ec2_extension public_service
|
||||
|
||||
[pipeline:admin_api]
|
||||
pipeline = token_auth admin_token_auth json_body debug crud_extension admin_service
|
||||
|
|
|
@ -74,7 +74,7 @@ class KvsIdentity(object):
|
|||
role_ids = self.db.get('role_list', [])
|
||||
return [self.get_role(x) for x in role_ids]
|
||||
|
||||
# These should probably be part of the high-level API
|
||||
# These should probably be part of the high-level API
|
||||
def add_user_to_tenant(self, tenant_id, user_id):
|
||||
user_ref = self.get_user(user_id)
|
||||
tenants = set(user_ref.get('tenants', []))
|
||||
|
@ -265,3 +265,37 @@ class KvsPolicy(object):
|
|||
|
||||
def can_haz(self, target, action, credentials):
|
||||
pass
|
||||
|
||||
|
||||
class KvsEc2(object):
|
||||
def __init__(self, db=None):
|
||||
if db is None:
|
||||
db = INMEMDB
|
||||
elif type(db) is type({}):
|
||||
db = DictKvs(db)
|
||||
self.db = db
|
||||
|
||||
# Public interface
|
||||
def get_credential(self, credential_id):
|
||||
credential_ref = self.db.get('credential-%s' % credential_id)
|
||||
return credential_ref
|
||||
|
||||
def list_credentials(self):
|
||||
credential_ids = self.db.get('credential_list', [])
|
||||
return [self.get_credential(x) for x in credential_ids]
|
||||
|
||||
# CRUD
|
||||
def create_credential(self, credential_id, credential):
|
||||
self.db.set('credential-%s' % credential_id, credential)
|
||||
credential_list = set(self.db.get('credential_list', []))
|
||||
credential_list.add(credential_id)
|
||||
self.db.set('credential_list', list(credential_list))
|
||||
return credential
|
||||
|
||||
def delete_credential(self, credential_id):
|
||||
old_credential = self.db.get('credential-%s' % credential_id)
|
||||
self.db.delete('credential-%s' % credential_id)
|
||||
credential_list = set(self.db.get('credential_list', []))
|
||||
credential_list.remove(credential_id)
|
||||
self.db.set('credential_list', list(credential_list))
|
||||
return None
|
||||
|
|
|
@ -123,3 +123,4 @@ register_str('driver', group='catalog')
|
|||
register_str('driver', group='identity')
|
||||
register_str('driver', group='policy')
|
||||
register_str('driver', group='token')
|
||||
register_str('driver', group='ec2')
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
from keystone import config
|
||||
from keystone import manager
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class Manager(manager.Manager):
|
||||
def __init__(self):
|
||||
super(Manager, self).__init__(CONF.ec2.driver)
|
|
@ -10,6 +10,7 @@ import webob.dec
|
|||
import webob.exc
|
||||
|
||||
from keystone import catalog
|
||||
from keystone import ec2
|
||||
from keystone import identity
|
||||
from keystone import logging
|
||||
from keystone import policy
|
||||
|
@ -151,10 +152,6 @@ class PublicRouter(wsgi.Router):
|
|||
controller=auth_controller,
|
||||
action='authenticate',
|
||||
conditions=dict(method=['POST']))
|
||||
mapper.connect('/ec2tokens',
|
||||
controller=auth_controller,
|
||||
action='authenticate_ec2',
|
||||
conditions=dict(methods=['POST']))
|
||||
|
||||
# Tenant Operations
|
||||
tenant_controller = TenantController()
|
||||
|
@ -329,6 +326,135 @@ class AdminCrudExtension(wsgi.ExtensionRouter):
|
|||
application, mapper)
|
||||
|
||||
|
||||
class Ec2Extension(wsgi.ExtensionRouter):
|
||||
def __init__(self, application):
|
||||
mapper = routes.Mapper()
|
||||
ec2_controller = Ec2Controller()
|
||||
|
||||
# validation
|
||||
mapper.connect('/ec2tokens',
|
||||
controller=ec2_controller,
|
||||
action='authenticate_ec2',
|
||||
conditions=dict(methods=['POST']))
|
||||
|
||||
# crud
|
||||
mapper.connect('/users/{user_id}/credentials/OS-EC2',
|
||||
controller=ec2_controller,
|
||||
action='create_credential',
|
||||
conditions=dict(methods=['POST']))
|
||||
mapper.connect('/users/{user_id}/credentials/OS-EC2',
|
||||
controller=ec2_controller,
|
||||
action='get_credentials',
|
||||
conditions=dict(methods=['GET']))
|
||||
mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
||||
controller=ec2_controller,
|
||||
action='get_credential',
|
||||
conditions=dict(methods=['GET']))
|
||||
mapper.connect('/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
||||
controller=ec2_controller,
|
||||
action='delete_credential',
|
||||
conditions=dict(methods=['DELETE']))
|
||||
|
||||
super(Ec2Extension, self).__init__(application, mapper)
|
||||
|
||||
|
||||
class Ec2Controller(Application):
|
||||
def __init__(self):
|
||||
self.catalog_api = catalog.Manager()
|
||||
self.identity_api = identity.Manager()
|
||||
self.token_api = token.Manager()
|
||||
self.policy_api = policy.Manager()
|
||||
self.ec2_api = ec2.Manager()
|
||||
super(Ec2Controller, self).__init__()
|
||||
|
||||
def authenticate_ec2(self, context, credentials=None,
|
||||
ec2Credentials=None):
|
||||
"""Validate a signed EC2 request and provide a token."""
|
||||
# NOTE(termie): backwards compat hack
|
||||
if not ecredentials and ec2Credentials:
|
||||
credentials = ec2Credentials
|
||||
creds_ref = self.ec2_api.get_credential(context,
|
||||
credentials['access'])
|
||||
|
||||
signer = utils.Signer(creds_ref['secret'])
|
||||
signature = signer.generate(credentials)
|
||||
if signature == credentials['signature']:
|
||||
pass
|
||||
# NOTE(vish): Some libraries don't use the port when signing
|
||||
# requests, so try again without port.
|
||||
elif ':' in credentials['signature']:
|
||||
hostname, _port = credentials['host'].split(":")
|
||||
credentials['host'] = hostname
|
||||
signature = signer.generate(credentials)
|
||||
if signature != credentials.signature:
|
||||
# TODO(termie): proper exception
|
||||
raise Exception("Not Authorized")
|
||||
else:
|
||||
raise Exception("Not Authorized")
|
||||
|
||||
# TODO(termie): don't create new tokens every time
|
||||
# TODO(termie): this is copied from TokenController.authenticate
|
||||
token_id = uuid.uuid4().hex
|
||||
tenant_ref = self.identity_api.get_tenant(creds_ref['tenant_id'])
|
||||
user_ref = self.identity_api.get_user(creds_ref['user_id'])
|
||||
metadata_ref = self.identity_api.get_metadata(
|
||||
context=context,
|
||||
user_id=user_ref['id'],
|
||||
tenant_id=tenant_ref['id'])
|
||||
catalog_ref = self.catalog_api.get_catalog(
|
||||
context=context,
|
||||
user_id=user_ref['id'],
|
||||
tenant_id=tenant_ref['id'],
|
||||
metadata=metadata_ref)
|
||||
|
||||
token_ref = self.token_api.create_token(
|
||||
context, token_id, dict(expires='',
|
||||
id=token_id,
|
||||
user=user_ref,
|
||||
tenant=tenant_ref,
|
||||
metadata=metadata_ref))
|
||||
|
||||
# TODO(termie): optimize this call at some point and put it into the
|
||||
# the return for metadata
|
||||
# fill out the roles in the metadata
|
||||
roles_ref = []
|
||||
for role_id in metadata_ref.get('roles', []):
|
||||
roles_ref.append(self.identity_api.get_role(context, role_id))
|
||||
|
||||
# TODO(termie): make this a util function or something
|
||||
# TODO(termie): i don't think the ec2 middleware currently expects a
|
||||
# full return, but it contains a note saying that it
|
||||
# would be better to expect a full return
|
||||
return TokenController._format_authenticate(
|
||||
self, token_ref, roles_ref, catalog_ref)
|
||||
|
||||
def create_credential(self, context, user_id, tenant_id):
|
||||
# TODO(termie): validate that this request is valid for given user
|
||||
# tenant
|
||||
cred_ref = {'user_id': user_id,
|
||||
'tenant_id': tenant_id,
|
||||
'id': uuid.uuid4().hex,
|
||||
'secret': uuid.uuid4().hex}
|
||||
self.ec2_api.create_credential(context, cred_ref['id'], cred_ref)
|
||||
return cred_ref
|
||||
|
||||
def get_credentials(self, context, user_id):
|
||||
"""List credentials for the given user_id."""
|
||||
# TODO(termie): validate that this request is valid for given user
|
||||
# tenant
|
||||
return self.ec2_api.list_credentials(user_id)
|
||||
|
||||
def get_credential(self, context, user_id, credential_id):
|
||||
# TODO(termie): validate that this request is valid for given user
|
||||
# tenant
|
||||
return self.ec2_api.get_credential(credential_id)
|
||||
|
||||
def delete_credential(self, context, user_id, credential_id):
|
||||
# TODO(termie): validate that this request is valid for given user
|
||||
# tenant
|
||||
return self.ec2_api.delete_credential(credential_id)
|
||||
|
||||
|
||||
class NoopController(Application):
|
||||
def __init__(self):
|
||||
super(NoopController, self).__init__()
|
||||
|
@ -462,9 +588,6 @@ class TokenController(Application):
|
|||
logging.debug('TOKEN_REF %s', token_ref)
|
||||
return self._format_authenticate(token_ref, roles_ref, catalog_ref)
|
||||
|
||||
def authenticate_ec2(self, context):
|
||||
raise NotImplemented()
|
||||
|
||||
# admin only
|
||||
def validate_token(self, context, token_id, belongs_to=None):
|
||||
"""Check that a token is valid.
|
||||
|
|
|
@ -17,9 +17,13 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib
|
||||
|
||||
from keystone import logging
|
||||
|
||||
|
@ -53,6 +57,83 @@ class SmarterEncoder(json.JSONEncoder):
|
|||
return super(SmarterEncoder, self).default(obj)
|
||||
|
||||
|
||||
class Ec2Signer(object):
|
||||
"""Hacked up code from boto/connection.py"""
|
||||
|
||||
def __init__(self, secret_key):
|
||||
secret_key = secret_key.encode()
|
||||
self.hmac = hmac.new(secret_key, digestmod=hashlib.sha1)
|
||||
if hashlib.sha256:
|
||||
self.hmac_256 = hmac.new(secret_key, digestmod=hashlib.sha256)
|
||||
|
||||
def generate(self, credentials):
|
||||
"""Generate auth string according to what SignatureVersion is given."""
|
||||
if credentials.params['SignatureVersion'] == '0':
|
||||
return self._calc_signature_0(credentials.params)
|
||||
if credentials.params['SignatureVersion'] == '1':
|
||||
return self._calc_signature_1(credentials.params)
|
||||
if credentials.params['SignatureVersion'] == '2':
|
||||
return self._calc_signature_2(credentials.params,
|
||||
credentials.verb,
|
||||
credentials.host,
|
||||
credentials.path)
|
||||
raise Exception('Unknown Signature Version: %s' %
|
||||
credentials.params['SignatureVersion'])
|
||||
|
||||
@staticmethod
|
||||
def _get_utf8_value(value):
|
||||
"""Get the UTF8-encoded version of a value."""
|
||||
if not isinstance(value, str) and not isinstance(value, unicode):
|
||||
value = str(value)
|
||||
if isinstance(value, unicode):
|
||||
return value.encode('utf-8')
|
||||
else:
|
||||
return value
|
||||
|
||||
def _calc_signature_0(self, params):
|
||||
"""Generate AWS signature version 0 string."""
|
||||
s = params['Action'] + params['Timestamp']
|
||||
self.hmac.update(s)
|
||||
return base64.b64encode(self.hmac.digest())
|
||||
|
||||
def _calc_signature_1(self, params):
|
||||
"""Generate AWS signature version 1 string."""
|
||||
keys = params.keys()
|
||||
keys.sort(cmp=lambda x, y: cmp(x.lower(), y.lower()))
|
||||
for key in keys:
|
||||
self.hmac.update(key)
|
||||
val = self._get_utf8_value(params[key])
|
||||
self.hmac.update(val)
|
||||
return base64.b64encode(self.hmac.digest())
|
||||
|
||||
def _calc_signature_2(self, params, verb, server_string, path):
|
||||
"""Generate AWS signature version 2 string."""
|
||||
LOG.debug('using _calc_signature_2')
|
||||
string_to_sign = '%s\n%s\n%s\n' % (verb, server_string, path)
|
||||
if self.hmac_256:
|
||||
current_hmac = self.hmac_256
|
||||
params['SignatureMethod'] = 'HmacSHA256'
|
||||
else:
|
||||
current_hmac = self.hmac
|
||||
params['SignatureMethod'] = 'HmacSHA1'
|
||||
keys = params.keys()
|
||||
keys.sort()
|
||||
pairs = []
|
||||
for key in keys:
|
||||
val = self._get_utf8_value(params[key])
|
||||
val = urllib.quote(val, safe='-_~')
|
||||
pairs.append(urllib.quote(key, safe='') + '=' + val)
|
||||
qs = '&'.join(pairs)
|
||||
LOG.debug('query string: %s', qs)
|
||||
string_to_sign += qs
|
||||
LOG.debug('string_to_sign: %s', string_to_sign)
|
||||
current_hmac.update(string_to_sign)
|
||||
b64 = base64.b64encode(current_hmac.digest())
|
||||
LOG.debug('len(b64)=%d', len(b64))
|
||||
LOG.debug('base64 encoded digest: %s', b64)
|
||||
return b64
|
||||
|
||||
|
||||
# From python 2.7
|
||||
def check_output(*popenargs, **kwargs):
|
||||
r"""Run command with arguments and return its output as a byte string.
|
||||
|
|
Loading…
Reference in New Issue