Migrating ec2 credentials to credential.
Merging ec2 credentials into the credentials table to simplify management of ec2 credentials. blueprint migrate-ec2-credentials Change-Id: I8f83c007a44857ca41d7ef23f70cb9718d83ca5d
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import sqlalchemy
|
import sqlalchemy
|
||||||
@@ -21,8 +22,9 @@ from sqlalchemy import exc
|
|||||||
|
|
||||||
|
|
||||||
from keystone.assignment.backends import sql as assignment_sql
|
from keystone.assignment.backends import sql as assignment_sql
|
||||||
|
from keystone.common import utils
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone.contrib.ec2.backends import sql as ec2_sql
|
from keystone.credential.backends import sql as ec2_sql
|
||||||
from keystone.identity.backends import sql as identity_sql
|
from keystone.identity.backends import sql as identity_sql
|
||||||
from keystone.openstack.common import log as logging
|
from keystone.openstack.common import log as logging
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ class LegacyMigration(object):
|
|||||||
self.identity_driver.db_sync()
|
self.identity_driver.db_sync()
|
||||||
self.assignment_driver.db_sync()
|
self.assignment_driver.db_sync()
|
||||||
|
|
||||||
self.ec2_driver = ec2_sql.Ec2()
|
self.ec2_driver = ec2_sql.Credential()
|
||||||
self._data = {}
|
self._data = {}
|
||||||
self._user_map = {}
|
self._user_map = {}
|
||||||
self._project_map = {}
|
self._project_map = {}
|
||||||
@@ -178,11 +180,18 @@ class LegacyMigration(object):
|
|||||||
|
|
||||||
def _migrate_ec2(self):
|
def _migrate_ec2(self):
|
||||||
for x in self._data['credentials']:
|
for x in self._data['credentials']:
|
||||||
new_dict = {'user_id': x['user_id'],
|
blob = {
|
||||||
'tenant_id': x['tenant_id'],
|
|
||||||
'access': x['key'],
|
'access': x['key'],
|
||||||
'secret': x['secret']}
|
'secret': x['secret']
|
||||||
|
}
|
||||||
|
credential_id = utils.hash_access_key(blob['access'])
|
||||||
|
new_dict = {'user_id': x['user_id'],
|
||||||
|
'blob': json.dumps(blob),
|
||||||
|
'project_id': x['tenant_id'],
|
||||||
|
'id': credential_id,
|
||||||
|
'type': 'ec2'}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.ec2_driver.create_credential(None, new_dict)
|
self.ec2_driver.create_credential(credential_id, new_dict)
|
||||||
except exc.IntegrityError:
|
except exc.IntegrityError:
|
||||||
LOG.exception(_('Cannot migrate EC2 credential: %s') % x)
|
LOG.exception(_('Cannot migrate EC2 credential: %s') % x)
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 OpenStack LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import sqlalchemy as sql
|
||||||
|
|
||||||
|
from keystone.common import utils
|
||||||
|
from keystone import exception
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(migrate_engine):
|
||||||
|
meta = sql.MetaData()
|
||||||
|
meta.bind = migrate_engine
|
||||||
|
|
||||||
|
credential_table = sql.Table('credential',
|
||||||
|
meta,
|
||||||
|
autoload=True)
|
||||||
|
|
||||||
|
ec2_cred_table = sql.Table('ec2_credential',
|
||||||
|
meta,
|
||||||
|
autoload=True)
|
||||||
|
|
||||||
|
session = sql.orm.sessionmaker(bind=migrate_engine)()
|
||||||
|
insert = credential_table.insert()
|
||||||
|
for ec2credential in session.query(ec2_cred_table):
|
||||||
|
cred_exist = check_credential_exists(ec2credential,
|
||||||
|
credential_table, session)
|
||||||
|
|
||||||
|
if not cred_exist:
|
||||||
|
credential = utils.convert_ec2_to_v3_credential(ec2credential)
|
||||||
|
insert.execute(credential)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
|
|
||||||
|
ec2_cred_table.drop()
|
||||||
|
|
||||||
|
|
||||||
|
def check_credential_exists(ec2credential, credential_table, session):
|
||||||
|
credential = session.query(credential_table).filter_by(
|
||||||
|
id=utils.hash_access_key(ec2credential.access)).first()
|
||||||
|
if credential is None:
|
||||||
|
return False
|
||||||
|
blob = utils.get_blob_from_credential(credential)
|
||||||
|
# check if credential with same access key but different
|
||||||
|
# secret key already exists in credential table.
|
||||||
|
# If exists raise an exception
|
||||||
|
if blob['secret'] != ec2credential.secret:
|
||||||
|
msg = _('Credential %(access)s already exists with different secret'
|
||||||
|
' in %(table)s table')
|
||||||
|
message = msg % {'access': ec2credential.access,
|
||||||
|
'table': credential_table.name}
|
||||||
|
raise exception.Conflict(type='credential', details=message)
|
||||||
|
# check if credential with same access and secret key but
|
||||||
|
# associated with a different project exists. If exists raise
|
||||||
|
# an exception
|
||||||
|
elif credential.project_id is not None and (
|
||||||
|
credential.project_id != ec2credential.tenant_id):
|
||||||
|
msg = _('Credential %(access)s already exists with different project'
|
||||||
|
' in %(table)s table')
|
||||||
|
message = msg % {'access': ec2credential.access,
|
||||||
|
'table': credential_table.name}
|
||||||
|
raise exception.Conflict(type='credential', details=message)
|
||||||
|
# if credential with same access and secret key and not associated
|
||||||
|
# with any projects already exists in the credential table, then
|
||||||
|
# return true.
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(migrate_engine):
|
||||||
|
meta = sql.MetaData()
|
||||||
|
meta.bind = migrate_engine
|
||||||
|
|
||||||
|
session = sql.orm.sessionmaker(bind=migrate_engine)()
|
||||||
|
|
||||||
|
ec2_credential_table = sql.Table(
|
||||||
|
'ec2_credential',
|
||||||
|
meta,
|
||||||
|
sql.Column('access', sql.String(64), primary_key=True),
|
||||||
|
sql.Column('secret', sql.String(64)),
|
||||||
|
sql.Column('user_id', sql.String(64)),
|
||||||
|
sql.Column('tenant_id', sql.String(64)),
|
||||||
|
mysql_engine='InnoDB',
|
||||||
|
mysql_charset='utf8')
|
||||||
|
|
||||||
|
ec2_credential_table.create(migrate_engine, checkfirst=True)
|
||||||
|
credential_table = sql.Table('credential',
|
||||||
|
meta,
|
||||||
|
autoload=True)
|
||||||
|
insert = ec2_credential_table.insert()
|
||||||
|
for credential in session.query(credential_table).filter(
|
||||||
|
sql.and_(credential_table.c.type == 'ec2',
|
||||||
|
credential_table.c.project_id is not None)).all():
|
||||||
|
ec2_credential = utils.convert_v3_to_ec2_credential(credential)
|
||||||
|
insert.execute(ec2_credential)
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
session.close()
|
||||||
@@ -16,11 +16,13 @@
|
|||||||
|
|
||||||
"""Export data from Nova database and import through Identity Service."""
|
"""Export data from Nova database and import through Identity Service."""
|
||||||
|
|
||||||
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from keystone import assignment
|
from keystone import assignment
|
||||||
|
from keystone.common import utils
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone.contrib.ec2.backends import sql as ec2_sql
|
from keystone.credential.backends import sql as ec2_sql
|
||||||
from keystone import identity
|
from keystone import identity
|
||||||
from keystone.openstack.common import log as logging
|
from keystone.openstack.common import log as logging
|
||||||
|
|
||||||
@@ -42,7 +44,7 @@ def import_auth(data):
|
|||||||
_assign_roles(assignment_api, data['role_user_tenant_list'],
|
_assign_roles(assignment_api, data['role_user_tenant_list'],
|
||||||
role_map, user_map, tenant_map)
|
role_map, user_map, tenant_map)
|
||||||
|
|
||||||
ec2_api = ec2_sql.Ec2()
|
ec2_api = ec2_sql.Credential()
|
||||||
ec2_creds = data['ec2_credentials']
|
ec2_creds = data['ec2_credentials']
|
||||||
_create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map)
|
_create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map)
|
||||||
|
|
||||||
@@ -127,14 +129,20 @@ def _create_ec2_creds(ec2_api, assignment_api, ec2_creds, user_map):
|
|||||||
for ec2_cred in ec2_creds:
|
for ec2_cred in ec2_creds:
|
||||||
user_id = user_map[ec2_cred['user_id']]
|
user_id = user_map[ec2_cred['user_id']]
|
||||||
for tenant_id in assignment_api.get_projects_for_user(user_id):
|
for tenant_id in assignment_api.get_projects_for_user(user_id):
|
||||||
cred_dict = {
|
blob = {
|
||||||
'access': '%s:%s' % (tenant_id, ec2_cred['access_key']),
|
'access': '%s:%s' % (tenant_id, ec2_cred['access_key']),
|
||||||
'secret': ec2_cred['secret_key'],
|
'secret': ec2_cred['secret_key'],
|
||||||
|
}
|
||||||
|
credential_id = utils.hash_access_key(blob['access'])
|
||||||
|
cred_dict = {
|
||||||
'user_id': user_id,
|
'user_id': user_id,
|
||||||
'tenant_id': tenant_id,
|
'blob': json.dumps(blob),
|
||||||
|
'project_id': tenant_id,
|
||||||
|
'id': credential_id,
|
||||||
|
'type': 'ec2',
|
||||||
}
|
}
|
||||||
LOG.debug(_(
|
LOG.debug(_(
|
||||||
'Creating ec2 cred for user %(user_id)s and tenant '
|
'Creating ec2 cred for user %(user_id)s and tenant '
|
||||||
'%(tenant_id)s') % {
|
'%(tenant_id)s') % {
|
||||||
'user_id': user_id, 'tenant_id': tenant_id})
|
'user_id': user_id, 'tenant_id': tenant_id})
|
||||||
ec2_api.create_credential(None, cred_dict)
|
ec2_api.create_credential(credential_id, cred_dict)
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ def trunc_password(password):
|
|||||||
raise exception.ValidationError(attribute='string', target='password')
|
raise exception.ValidationError(attribute='string', target='password')
|
||||||
|
|
||||||
|
|
||||||
|
def hash_access_key(access):
|
||||||
|
hash_ = hashlib.sha256()
|
||||||
|
hash_.update(access)
|
||||||
|
return hash_.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def hash_user_password(user):
|
def hash_user_password(user):
|
||||||
"""Hash a user dict's password without modifying the passed-in dict."""
|
"""Hash a user dict's password without modifying the passed-in dict."""
|
||||||
try:
|
try:
|
||||||
@@ -170,6 +176,38 @@ def check_output(*popenargs, **kwargs):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
def get_blob_from_credential(credential):
|
||||||
|
try:
|
||||||
|
blob = json.loads(credential.blob)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
raise exception.ValidationError(
|
||||||
|
message=_('Invalid blob in credential'))
|
||||||
|
if not blob or not isinstance(blob, dict):
|
||||||
|
raise exception.ValidationError(attribute='blob',
|
||||||
|
target='credential')
|
||||||
|
return blob
|
||||||
|
|
||||||
|
|
||||||
|
def convert_ec2_to_v3_credential(ec2credential):
|
||||||
|
blob = {'access': ec2credential.access,
|
||||||
|
'secret': ec2credential.secret}
|
||||||
|
return {'id': hash_access_key(ec2credential.access),
|
||||||
|
'user_id': ec2credential.user_id,
|
||||||
|
'project_id': ec2credential.tenant_id,
|
||||||
|
'blob': json.dumps(blob),
|
||||||
|
'type': 'ec2',
|
||||||
|
'extra': json.dumps({})}
|
||||||
|
|
||||||
|
|
||||||
|
def convert_v3_to_ec2_credential(credential):
|
||||||
|
blob = get_blob_from_credential(credential)
|
||||||
|
return {'access': blob.get('access'),
|
||||||
|
'secret': blob.get('secret'),
|
||||||
|
'user_id': credential.user_id,
|
||||||
|
'tenant_id': credential.project_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def git(*args):
|
def git(*args):
|
||||||
return check_output(['git'] + list(args))
|
return check_output(['git'] + list(args))
|
||||||
|
|
||||||
|
|||||||
@@ -15,4 +15,6 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from keystone.contrib.ec2 import controllers
|
||||||
from keystone.contrib.ec2.core import *
|
from keystone.contrib.ec2.core import *
|
||||||
|
from keystone.contrib.ec2.routers import Ec2Extension
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012 OpenStack LLC
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from keystone.common import kvs
|
|
||||||
|
|
||||||
|
|
||||||
class Ec2(kvs.Base):
|
|
||||||
# Public interface
|
|
||||||
def get_credential(self, credential_id):
|
|
||||||
credential_ref = self.db.get('credential-%s' % credential_id)
|
|
||||||
return credential_ref
|
|
||||||
|
|
||||||
def list_credentials(self, user_id):
|
|
||||||
credential_ids = self.db.get('credential_list', [])
|
|
||||||
rv = [self.get_credential(x) for x in credential_ids]
|
|
||||||
return [x for x in rv if x['user_id'] == user_id]
|
|
||||||
|
|
||||||
# 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):
|
|
||||||
# This will ensure credential-%s is here before deleting
|
|
||||||
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
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
||||||
|
|
||||||
# Copyright 2012 OpenStack LLC
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
||||||
# not use this file except in compliance with the License. You may obtain
|
|
||||||
# a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
||||||
# License for the specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
|
|
||||||
from keystone.common import sql
|
|
||||||
|
|
||||||
|
|
||||||
class Ec2Credential(sql.ModelBase, sql.DictBase):
|
|
||||||
__tablename__ = 'ec2_credential'
|
|
||||||
access = sql.Column(sql.String(64), primary_key=True)
|
|
||||||
secret = sql.Column(sql.String(64))
|
|
||||||
user_id = sql.Column(sql.String(64))
|
|
||||||
tenant_id = sql.Column(sql.String(64))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_dict(cls, user_dict):
|
|
||||||
return cls(**user_dict)
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return dict(self.iteritems())
|
|
||||||
|
|
||||||
|
|
||||||
class Ec2(sql.Base):
|
|
||||||
def get_credential(self, credential_id):
|
|
||||||
session = self.get_session()
|
|
||||||
query = session.query(Ec2Credential)
|
|
||||||
query = query.filter_by(access=credential_id)
|
|
||||||
credential_ref = query.first()
|
|
||||||
if not credential_ref:
|
|
||||||
return
|
|
||||||
return credential_ref.to_dict()
|
|
||||||
|
|
||||||
def list_credentials(self, user_id):
|
|
||||||
session = self.get_session()
|
|
||||||
query = session.query(Ec2Credential)
|
|
||||||
credential_refs = query.filter_by(user_id=user_id)
|
|
||||||
return [x.to_dict() for x in credential_refs]
|
|
||||||
|
|
||||||
# CRUD
|
|
||||||
def create_credential(self, credential_id, credential):
|
|
||||||
session = self.get_session()
|
|
||||||
with session.begin():
|
|
||||||
credential_ref = Ec2Credential.from_dict(credential)
|
|
||||||
session.add(credential_ref)
|
|
||||||
session.flush()
|
|
||||||
return credential_ref.to_dict()
|
|
||||||
|
|
||||||
def delete_credential(self, credential_id):
|
|
||||||
session = self.get_session()
|
|
||||||
query = session.query(Ec2Credential)
|
|
||||||
query = query.filter_by(access=credential_id)
|
|
||||||
credential_ref = query.first()
|
|
||||||
with session.begin():
|
|
||||||
session.delete(credential_ref)
|
|
||||||
session.flush()
|
|
||||||
290
keystone/contrib/ec2/controllers.py
Normal file
290
keystone/contrib/ec2/controllers.py
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 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.
|
||||||
|
|
||||||
|
"""Main entry point into the EC2 Credentials service.
|
||||||
|
|
||||||
|
This service allows the creation of access/secret credentials used for
|
||||||
|
the ec2 interop layer of OpenStack.
|
||||||
|
|
||||||
|
A user can create as many access/secret pairs, each of which map to a
|
||||||
|
specific project. This is required because OpenStack supports a user
|
||||||
|
belonging to multiple projects, whereas the signatures created on ec2-style
|
||||||
|
requests don't allow specification of which project the user wishes to act
|
||||||
|
upon.
|
||||||
|
|
||||||
|
To complete the cycle, we provide a method that OpenStack services can
|
||||||
|
use to validate a signature and get a corresponding OpenStack token. This
|
||||||
|
token allows method calls to other services within the context the
|
||||||
|
access/secret was created. As an example, Nova requests Keystone to validate
|
||||||
|
the signature of a request, receives a token, and then makes a request to
|
||||||
|
Glance to list images needed to perform the requested task.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
||||||
|
|
||||||
|
from keystone.common import controller
|
||||||
|
from keystone.common import dependency
|
||||||
|
from keystone.common import utils
|
||||||
|
from keystone import exception
|
||||||
|
from keystone import token
|
||||||
|
|
||||||
|
|
||||||
|
@dependency.requires('catalog_api', 'credential_api', 'token_provider_api')
|
||||||
|
class Ec2Controller(controller.V2Controller):
|
||||||
|
def check_signature(self, creds_ref, credentials):
|
||||||
|
signer = ec2_utils.Ec2Signer(creds_ref['secret'])
|
||||||
|
signature = signer.generate(credentials)
|
||||||
|
if utils.auth_str_equal(credentials['signature'], signature):
|
||||||
|
return
|
||||||
|
# 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 not utils.auth_str_equal(credentials.signature, signature):
|
||||||
|
raise exception.Unauthorized(message='Invalid EC2 signature.')
|
||||||
|
else:
|
||||||
|
raise exception.Unauthorized(message='EC2 signature not supplied.')
|
||||||
|
|
||||||
|
def authenticate(self, context, credentials=None, ec2Credentials=None):
|
||||||
|
"""Validate a signed EC2 request and provide a token.
|
||||||
|
|
||||||
|
Other services (such as Nova) use this **admin** call to determine
|
||||||
|
if a request they signed received is from a valid user.
|
||||||
|
|
||||||
|
If it is a valid signature, an OpenStack token that maps
|
||||||
|
to the user/tenant is returned to the caller, along with
|
||||||
|
all the other details returned from a normal token validation
|
||||||
|
call.
|
||||||
|
|
||||||
|
The returned token is useful for making calls to other
|
||||||
|
OpenStack services within the context of the request.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param credentials: dict of ec2 signature
|
||||||
|
:param ec2Credentials: DEPRECATED dict of ec2 signature
|
||||||
|
:returns: token: OpenStack token equivalent to access key along
|
||||||
|
with the corresponding service catalog and roles
|
||||||
|
"""
|
||||||
|
|
||||||
|
# FIXME(ja): validate that a service token was used!
|
||||||
|
|
||||||
|
# NOTE(termie): backwards compat hack
|
||||||
|
if not credentials and ec2Credentials:
|
||||||
|
credentials = ec2Credentials
|
||||||
|
|
||||||
|
if 'access' not in credentials:
|
||||||
|
raise exception.Unauthorized(message='EC2 signature not supplied.')
|
||||||
|
|
||||||
|
creds_ref = self._get_credentials(credentials['access'])
|
||||||
|
self.check_signature(creds_ref, credentials)
|
||||||
|
|
||||||
|
# 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_project(creds_ref['tenant_id'])
|
||||||
|
user_ref = self.identity_api.get_user(creds_ref['user_id'])
|
||||||
|
metadata_ref = {}
|
||||||
|
metadata_ref['roles'] = (
|
||||||
|
self.identity_api.get_roles_for_user_and_project(
|
||||||
|
user_ref['id'], tenant_ref['id']))
|
||||||
|
|
||||||
|
# Validate that the auth info is valid and nothing is disabled
|
||||||
|
token.validate_auth_info(self, user_ref, tenant_ref)
|
||||||
|
|
||||||
|
roles = metadata_ref.get('roles', [])
|
||||||
|
if not roles:
|
||||||
|
raise exception.Unauthorized(message='User not valid for tenant.')
|
||||||
|
roles_ref = [self.identity_api.get_role(role_id)
|
||||||
|
for role_id in roles]
|
||||||
|
|
||||||
|
catalog_ref = self.catalog_api.get_catalog(
|
||||||
|
user_ref['id'], tenant_ref['id'], metadata_ref)
|
||||||
|
|
||||||
|
auth_token_data = dict(user=user_ref,
|
||||||
|
tenant=tenant_ref,
|
||||||
|
metadata=metadata_ref,
|
||||||
|
id='placeholder')
|
||||||
|
(token_id, token_data) = self.token_provider_api.issue_v2_token(
|
||||||
|
auth_token_data, roles_ref, catalog_ref)
|
||||||
|
return token_data
|
||||||
|
|
||||||
|
def create_credential(self, context, user_id, tenant_id):
|
||||||
|
"""Create a secret/access pair for use with ec2 style auth.
|
||||||
|
|
||||||
|
Generates a new set of credentials that map the user/tenant
|
||||||
|
pair.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param user_id: id of user
|
||||||
|
:param tenant_id: id of tenant
|
||||||
|
:returns: credential: dict of ec2 credential
|
||||||
|
"""
|
||||||
|
if not self._is_admin(context):
|
||||||
|
self._assert_identity(context, user_id)
|
||||||
|
|
||||||
|
self._assert_valid_user_id(user_id)
|
||||||
|
self._assert_valid_project_id(tenant_id)
|
||||||
|
blob = {'access': uuid.uuid4().hex,
|
||||||
|
'secret': uuid.uuid4().hex}
|
||||||
|
credential_id = utils.hash_access_key(blob['access'])
|
||||||
|
cred_ref = {'user_id': user_id,
|
||||||
|
'project_id': tenant_id,
|
||||||
|
'blob': blob,
|
||||||
|
'id': credential_id,
|
||||||
|
'type': 'ec2'}
|
||||||
|
self.credential_api.create_credential(credential_id, cred_ref)
|
||||||
|
return {'credential': self._convert_v3_to_ec2_credential(cred_ref)}
|
||||||
|
|
||||||
|
def get_credentials(self, context, user_id):
|
||||||
|
"""List all credentials for a user.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param user_id: id of user
|
||||||
|
:returns: credentials: list of ec2 credential dicts
|
||||||
|
"""
|
||||||
|
if not self._is_admin(context):
|
||||||
|
self._assert_identity(context, user_id)
|
||||||
|
self._assert_valid_user_id(user_id)
|
||||||
|
credential_refs = self.credential_api.list_credentials(
|
||||||
|
user_id=user_id)
|
||||||
|
return {'credentials':
|
||||||
|
[self._convert_v3_to_ec2_credential(credential)
|
||||||
|
for credential in credential_refs]}
|
||||||
|
|
||||||
|
def get_credential(self, context, user_id, credential_id):
|
||||||
|
"""Retrieve a user's access/secret pair by the access key.
|
||||||
|
|
||||||
|
Grab the full access/secret pair for a given access key.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param user_id: id of user
|
||||||
|
:param credential_id: access key for credentials
|
||||||
|
:returns: credential: dict of ec2 credential
|
||||||
|
"""
|
||||||
|
if not self._is_admin(context):
|
||||||
|
self._assert_identity(context, user_id)
|
||||||
|
self._assert_valid_user_id(user_id)
|
||||||
|
return {'credential': self._get_credentials(credential_id)}
|
||||||
|
|
||||||
|
def delete_credential(self, context, user_id, credential_id):
|
||||||
|
"""Delete a user's access/secret pair.
|
||||||
|
|
||||||
|
Used to revoke a user's access/secret pair
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param user_id: id of user
|
||||||
|
:param credential_id: access key for credentials
|
||||||
|
:returns: bool: success
|
||||||
|
"""
|
||||||
|
if not self._is_admin(context):
|
||||||
|
self._assert_identity(context, user_id)
|
||||||
|
self._assert_owner(user_id, credential_id)
|
||||||
|
|
||||||
|
self._assert_valid_user_id(user_id)
|
||||||
|
self._get_credentials(credential_id)
|
||||||
|
ec2_credential_id = utils.hash_access_key(credential_id)
|
||||||
|
return self.credential_api.delete_credential(ec2_credential_id)
|
||||||
|
|
||||||
|
def _convert_v3_to_ec2_credential(self, credential):
|
||||||
|
|
||||||
|
blob = credential['blob']
|
||||||
|
return {'user_id': credential.get('user_id'),
|
||||||
|
'tenant_id': credential.get('project_id'),
|
||||||
|
'access': blob.get('access'),
|
||||||
|
'secret': blob.get('secret')}
|
||||||
|
|
||||||
|
def _get_credentials(self, credential_id):
|
||||||
|
"""Return credentials from an ID.
|
||||||
|
|
||||||
|
:param credential_id: id of credential
|
||||||
|
:raises exception.Unauthorized: when credential id is invalid
|
||||||
|
:returns: credential: dict of ec2 credential.
|
||||||
|
"""
|
||||||
|
ec2_credential_id = utils.hash_access_key(credential_id)
|
||||||
|
creds = self.credential_api.get_credential(ec2_credential_id)
|
||||||
|
if not creds:
|
||||||
|
raise exception.Unauthorized(message='EC2 access key not found.')
|
||||||
|
return self._convert_v3_to_ec2_credential(creds)
|
||||||
|
|
||||||
|
def _assert_identity(self, context, user_id):
|
||||||
|
"""Check that the provided token belongs to the user.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param user_id: id of user
|
||||||
|
:raises exception.Forbidden: when token is invalid
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
token_ref = self.token_api.get_token(context['token_id'])
|
||||||
|
except exception.TokenNotFound as e:
|
||||||
|
raise exception.Unauthorized(e)
|
||||||
|
|
||||||
|
if token_ref['user'].get('id') != user_id:
|
||||||
|
raise exception.Forbidden(_('Token belongs to another user'))
|
||||||
|
|
||||||
|
def _is_admin(self, context):
|
||||||
|
"""Wrap admin assertion error return statement.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:returns: bool: success
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.assert_admin(context)
|
||||||
|
return True
|
||||||
|
except exception.Forbidden:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _assert_owner(self, user_id, credential_id):
|
||||||
|
"""Ensure the provided user owns the credential.
|
||||||
|
|
||||||
|
:param user_id: expected credential owner
|
||||||
|
:param credential_id: id of credential object
|
||||||
|
:raises exception.Forbidden: on failure
|
||||||
|
|
||||||
|
"""
|
||||||
|
cred_ref = self.credential_api.get_credential(credential_id)
|
||||||
|
if user_id != cred_ref['user_id']:
|
||||||
|
raise exception.Forbidden(_('Credential belongs to another user'))
|
||||||
|
|
||||||
|
def _assert_valid_user_id(self, user_id):
|
||||||
|
"""Ensure a valid user id.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param user_id: expected credential owner
|
||||||
|
:raises exception.UserNotFound: on failure
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_ref = self.identity_api.get_user(user_id)
|
||||||
|
if not user_ref:
|
||||||
|
raise exception.UserNotFound(user_id=user_id)
|
||||||
|
|
||||||
|
def _assert_valid_project_id(self, project_id):
|
||||||
|
"""Ensure a valid project id.
|
||||||
|
|
||||||
|
:param context: standard context
|
||||||
|
:param project_id: expected project
|
||||||
|
:raises exception.ProjectNotFound: on failure
|
||||||
|
|
||||||
|
"""
|
||||||
|
project_ref = self.identity_api.get_project(project_id)
|
||||||
|
if not project_ref:
|
||||||
|
raise exception.ProjectNotFound(project_id=project_id)
|
||||||
@@ -14,40 +14,8 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
"""Main entry point into the EC2 Credentials service.
|
|
||||||
|
|
||||||
This service allows the creation of access/secret credentials used for
|
|
||||||
the ec2 interop layer of OpenStack.
|
|
||||||
|
|
||||||
A user can create as many access/secret pairs, each of which map to a
|
|
||||||
specific tenant. This is required because OpenStack supports a user
|
|
||||||
belonging to multiple tenants, whereas the signatures created on ec2-style
|
|
||||||
requests don't allow specification of which tenant the user wishs to act
|
|
||||||
upon.
|
|
||||||
|
|
||||||
To complete the cycle, we provide a method that OpenStack services can
|
|
||||||
use to validate a signature and get a corresponding openstack token. This
|
|
||||||
token allows method calls to other services within the context the
|
|
||||||
access/secret was created. As an example, nova requests keystone to validate
|
|
||||||
the signature of a request, receives a token, and then makes a request to
|
|
||||||
glance to list images needed to perform the requested task.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
|
||||||
|
|
||||||
from keystone.common import controller
|
|
||||||
from keystone.common import dependency
|
|
||||||
from keystone.common import extension
|
from keystone.common import extension
|
||||||
from keystone.common import manager
|
|
||||||
from keystone.common import utils
|
|
||||||
from keystone.common import wsgi
|
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone import exception
|
|
||||||
from keystone import token
|
|
||||||
|
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
|
||||||
@@ -69,279 +37,3 @@ EXTENSION_DATA = {
|
|||||||
]}
|
]}
|
||||||
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||||
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA)
|
||||||
|
|
||||||
|
|
||||||
@dependency.provider('ec2_api')
|
|
||||||
class Manager(manager.Manager):
|
|
||||||
"""Default pivot point for the EC2 Credentials backend.
|
|
||||||
|
|
||||||
See :mod:`keystone.common.manager.Manager` for more details on how this
|
|
||||||
dynamically calls the backend.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(Manager, self).__init__(CONF.ec2.driver)
|
|
||||||
|
|
||||||
|
|
||||||
class Ec2Extension(wsgi.ExtensionRouter):
|
|
||||||
def add_routes(self, mapper):
|
|
||||||
ec2_controller = Ec2Controller()
|
|
||||||
# validation
|
|
||||||
mapper.connect(
|
|
||||||
'/ec2tokens',
|
|
||||||
controller=ec2_controller,
|
|
||||||
action='authenticate',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
|
|
||||||
# crud
|
|
||||||
mapper.connect(
|
|
||||||
'/users/{user_id}/credentials/OS-EC2',
|
|
||||||
controller=ec2_controller,
|
|
||||||
action='create_credential',
|
|
||||||
conditions=dict(method=['POST']))
|
|
||||||
mapper.connect(
|
|
||||||
'/users/{user_id}/credentials/OS-EC2',
|
|
||||||
controller=ec2_controller,
|
|
||||||
action='get_credentials',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
mapper.connect(
|
|
||||||
'/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
|
||||||
controller=ec2_controller,
|
|
||||||
action='get_credential',
|
|
||||||
conditions=dict(method=['GET']))
|
|
||||||
mapper.connect(
|
|
||||||
'/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
|
||||||
controller=ec2_controller,
|
|
||||||
action='delete_credential',
|
|
||||||
conditions=dict(method=['DELETE']))
|
|
||||||
|
|
||||||
|
|
||||||
@dependency.requires('catalog_api', 'ec2_api', 'token_provider_api')
|
|
||||||
class Ec2Controller(controller.V2Controller):
|
|
||||||
def check_signature(self, creds_ref, credentials):
|
|
||||||
signer = ec2_utils.Ec2Signer(creds_ref['secret'])
|
|
||||||
signature = signer.generate(credentials)
|
|
||||||
if utils.auth_str_equal(credentials['signature'], signature):
|
|
||||||
return
|
|
||||||
# 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 not utils.auth_str_equal(credentials.signature, signature):
|
|
||||||
raise exception.Unauthorized(message='Invalid EC2 signature.')
|
|
||||||
else:
|
|
||||||
raise exception.Unauthorized(message='EC2 signature not supplied.')
|
|
||||||
|
|
||||||
def authenticate(self, context, credentials=None, ec2Credentials=None):
|
|
||||||
"""Validate a signed EC2 request and provide a token.
|
|
||||||
|
|
||||||
Other services (such as Nova) use this **admin** call to determine
|
|
||||||
if a request they signed received is from a valid user.
|
|
||||||
|
|
||||||
If it is a valid signature, an openstack token that maps
|
|
||||||
to the user/tenant is returned to the caller, along with
|
|
||||||
all the other details returned from a normal token validation
|
|
||||||
call.
|
|
||||||
|
|
||||||
The returned token is useful for making calls to other
|
|
||||||
OpenStack services within the context of the request.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param credentials: dict of ec2 signature
|
|
||||||
:param ec2Credentials: DEPRECATED dict of ec2 signature
|
|
||||||
:returns: token: openstack token equivalent to access key along
|
|
||||||
with the corresponding service catalog and roles
|
|
||||||
"""
|
|
||||||
|
|
||||||
# FIXME(ja): validate that a service token was used!
|
|
||||||
|
|
||||||
# NOTE(termie): backwards compat hack
|
|
||||||
if not credentials and ec2Credentials:
|
|
||||||
credentials = ec2Credentials
|
|
||||||
|
|
||||||
if 'access' not in credentials:
|
|
||||||
raise exception.Unauthorized(message='EC2 signature not supplied.')
|
|
||||||
|
|
||||||
creds_ref = self._get_credentials(credentials['access'])
|
|
||||||
self.check_signature(creds_ref, credentials)
|
|
||||||
|
|
||||||
# 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_project(creds_ref['tenant_id'])
|
|
||||||
user_ref = self.identity_api.get_user(creds_ref['user_id'])
|
|
||||||
metadata_ref = {}
|
|
||||||
metadata_ref['roles'] = (
|
|
||||||
self.identity_api.get_roles_for_user_and_project(
|
|
||||||
user_ref['id'], tenant_ref['id']))
|
|
||||||
|
|
||||||
# Validate that the auth info is valid and nothing is disabled
|
|
||||||
token.validate_auth_info(self, user_ref, tenant_ref)
|
|
||||||
|
|
||||||
roles = metadata_ref.get('roles', [])
|
|
||||||
if not roles:
|
|
||||||
raise exception.Unauthorized(message='User not valid for tenant.')
|
|
||||||
roles_ref = [self.identity_api.get_role(role_id)
|
|
||||||
for role_id in roles]
|
|
||||||
|
|
||||||
catalog_ref = self.catalog_api.get_catalog(
|
|
||||||
user_ref['id'], tenant_ref['id'], metadata_ref)
|
|
||||||
|
|
||||||
auth_token_data = dict(user=user_ref,
|
|
||||||
tenant=tenant_ref,
|
|
||||||
metadata=metadata_ref,
|
|
||||||
id='placeholder')
|
|
||||||
|
|
||||||
(token_id, token_data) = self.token_provider_api.issue_v2_token(
|
|
||||||
auth_token_data, roles_ref, catalog_ref)
|
|
||||||
return token_data
|
|
||||||
|
|
||||||
def create_credential(self, context, user_id, tenant_id):
|
|
||||||
"""Create a secret/access pair for use with ec2 style auth.
|
|
||||||
|
|
||||||
Generates a new set of credentials that map the the user/tenant
|
|
||||||
pair.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param user_id: id of user
|
|
||||||
:param tenant_id: id of tenant
|
|
||||||
:returns: credential: dict of ec2 credential
|
|
||||||
"""
|
|
||||||
if not self._is_admin(context):
|
|
||||||
self._assert_identity(context, user_id)
|
|
||||||
|
|
||||||
self._assert_valid_user_id(user_id)
|
|
||||||
self._assert_valid_project_id(tenant_id)
|
|
||||||
|
|
||||||
cred_ref = {'user_id': user_id,
|
|
||||||
'tenant_id': tenant_id,
|
|
||||||
'access': uuid.uuid4().hex,
|
|
||||||
'secret': uuid.uuid4().hex}
|
|
||||||
self.ec2_api.create_credential(cred_ref['access'], cred_ref)
|
|
||||||
return {'credential': cred_ref}
|
|
||||||
|
|
||||||
def get_credentials(self, context, user_id):
|
|
||||||
"""List all credentials for a user.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param user_id: id of user
|
|
||||||
:returns: credentials: list of ec2 credential dicts
|
|
||||||
"""
|
|
||||||
if not self._is_admin(context):
|
|
||||||
self._assert_identity(context, user_id)
|
|
||||||
self._assert_valid_user_id(user_id)
|
|
||||||
return {'credentials': self.ec2_api.list_credentials(user_id)}
|
|
||||||
|
|
||||||
def get_credential(self, context, user_id, credential_id):
|
|
||||||
"""Retrieve a user's access/secret pair by the access key.
|
|
||||||
|
|
||||||
Grab the full access/secret pair for a given access key.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param user_id: id of user
|
|
||||||
:param credential_id: access key for credentials
|
|
||||||
:returns: credential: dict of ec2 credential
|
|
||||||
"""
|
|
||||||
if not self._is_admin(context):
|
|
||||||
self._assert_identity(context, user_id)
|
|
||||||
self._assert_valid_user_id(user_id)
|
|
||||||
creds = self._get_credentials(credential_id)
|
|
||||||
return {'credential': creds}
|
|
||||||
|
|
||||||
def delete_credential(self, context, user_id, credential_id):
|
|
||||||
"""Delete a user's access/secret pair.
|
|
||||||
|
|
||||||
Used to revoke a user's access/secret pair
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param user_id: id of user
|
|
||||||
:param credential_id: access key for credentials
|
|
||||||
:returns: bool: success
|
|
||||||
"""
|
|
||||||
if not self._is_admin(context):
|
|
||||||
self._assert_identity(context, user_id)
|
|
||||||
self._assert_owner(user_id, credential_id)
|
|
||||||
|
|
||||||
self._assert_valid_user_id(user_id)
|
|
||||||
self._get_credentials(credential_id)
|
|
||||||
return self.ec2_api.delete_credential(credential_id)
|
|
||||||
|
|
||||||
def _get_credentials(self, credential_id):
|
|
||||||
"""Return credentials from an ID.
|
|
||||||
|
|
||||||
:param credential_id: id of credential
|
|
||||||
:raises exception.Unauthorized: when credential id is invalid
|
|
||||||
:returns: credential: dict of ec2 credential.
|
|
||||||
"""
|
|
||||||
creds = self.ec2_api.get_credential(credential_id)
|
|
||||||
if not creds:
|
|
||||||
raise exception.Unauthorized(message='EC2 access key not found.')
|
|
||||||
return creds
|
|
||||||
|
|
||||||
def _assert_identity(self, context, user_id):
|
|
||||||
"""Check that the provided token belongs to the user.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param user_id: id of user
|
|
||||||
:raises exception.Forbidden: when token is invalid
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
token_ref = self.token_api.get_token(context['token_id'])
|
|
||||||
except exception.TokenNotFound as e:
|
|
||||||
raise exception.Unauthorized(e)
|
|
||||||
|
|
||||||
if token_ref['user'].get('id') != user_id:
|
|
||||||
raise exception.Forbidden('Token belongs to another user')
|
|
||||||
|
|
||||||
def _is_admin(self, context):
|
|
||||||
"""Wrap admin assertion error return statement.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:returns: bool: success
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
self.assert_admin(context)
|
|
||||||
return True
|
|
||||||
except exception.Forbidden:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _assert_owner(self, user_id, credential_id):
|
|
||||||
"""Ensure the provided user owns the credential.
|
|
||||||
|
|
||||||
:param user_id: expected credential owner
|
|
||||||
:param credential_id: id of credential object
|
|
||||||
:raises exception.Forbidden: on failure
|
|
||||||
|
|
||||||
"""
|
|
||||||
cred_ref = self.ec2_api.get_credential(credential_id)
|
|
||||||
if not user_id == cred_ref['user_id']:
|
|
||||||
raise exception.Forbidden('Credential belongs to another user')
|
|
||||||
|
|
||||||
def _assert_valid_user_id(self, user_id):
|
|
||||||
"""Ensure a valid user id.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param user_id: expected credential owner
|
|
||||||
:raises exception.UserNotFound: on failure
|
|
||||||
|
|
||||||
"""
|
|
||||||
user_ref = self.identity_api.get_user(user_id)
|
|
||||||
if not user_ref:
|
|
||||||
raise exception.UserNotFound(user_id=user_id)
|
|
||||||
|
|
||||||
def _assert_valid_project_id(self, tenant_id):
|
|
||||||
"""Ensure a valid tenant id.
|
|
||||||
|
|
||||||
:param context: standard context
|
|
||||||
:param tenant_id: expected tenant
|
|
||||||
:raises exception.ProjectNotFound: on failure
|
|
||||||
|
|
||||||
"""
|
|
||||||
tenant_ref = self.identity_api.get_project(tenant_id)
|
|
||||||
if not tenant_ref:
|
|
||||||
raise exception.ProjectNotFound(project_id=tenant_id)
|
|
||||||
|
|||||||
51
keystone/contrib/ec2/routers.py
Normal file
51
keystone/contrib/ec2/routers.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2013 OpenStack LLC
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from keystone.common import wsgi
|
||||||
|
from keystone.contrib.ec2 import controllers
|
||||||
|
|
||||||
|
|
||||||
|
class Ec2Extension(wsgi.ExtensionRouter):
|
||||||
|
def add_routes(self, mapper):
|
||||||
|
ec2_controller = controllers.Ec2Controller()
|
||||||
|
# validation
|
||||||
|
mapper.connect(
|
||||||
|
'/ec2tokens',
|
||||||
|
controller=ec2_controller,
|
||||||
|
action='authenticate',
|
||||||
|
conditions=dict(method=['POST']))
|
||||||
|
|
||||||
|
# crud
|
||||||
|
mapper.connect(
|
||||||
|
'/users/{user_id}/credentials/OS-EC2',
|
||||||
|
controller=ec2_controller,
|
||||||
|
action='create_credential',
|
||||||
|
conditions=dict(method=['POST']))
|
||||||
|
mapper.connect(
|
||||||
|
'/users/{user_id}/credentials/OS-EC2',
|
||||||
|
controller=ec2_controller,
|
||||||
|
action='get_credentials',
|
||||||
|
conditions=dict(method=['GET']))
|
||||||
|
mapper.connect(
|
||||||
|
'/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
||||||
|
controller=ec2_controller,
|
||||||
|
action='get_credential',
|
||||||
|
conditions=dict(method=['GET']))
|
||||||
|
mapper.connect(
|
||||||
|
'/users/{user_id}/credentials/OS-EC2/{credential_id}',
|
||||||
|
controller=ec2_controller,
|
||||||
|
action='delete_credential',
|
||||||
|
conditions=dict(method=['DELETE']))
|
||||||
@@ -31,7 +31,7 @@ from keystone.common import extension
|
|||||||
from keystone.common import utils
|
from keystone.common import utils
|
||||||
from keystone.common import wsgi
|
from keystone.common import wsgi
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone.contrib import ec2
|
from keystone.contrib.ec2 import controllers
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
@@ -64,7 +64,7 @@ class S3Extension(wsgi.ExtensionRouter):
|
|||||||
conditions=dict(method=['POST']))
|
conditions=dict(method=['POST']))
|
||||||
|
|
||||||
|
|
||||||
class S3Controller(ec2.Ec2Controller):
|
class S3Controller(controllers.Ec2Controller):
|
||||||
def check_signature(self, creds_ref, credentials):
|
def check_signature(self, creds_ref, credentials):
|
||||||
msg = base64.urlsafe_b64decode(str(credentials['token']))
|
msg = base64.urlsafe_b64decode(str(credentials['token']))
|
||||||
key = str(creds_ref['secret'])
|
key = str(creds_ref['secret'])
|
||||||
|
|||||||
@@ -48,9 +48,12 @@ class Credential(sql.Base, credential.Driver):
|
|||||||
session.flush()
|
session.flush()
|
||||||
return ref.to_dict()
|
return ref.to_dict()
|
||||||
|
|
||||||
def list_credentials(self):
|
def list_credentials(self, **filters):
|
||||||
session = self.get_session()
|
session = self.get_session()
|
||||||
refs = session.query(CredentialModel).all()
|
query = session.query(CredentialModel)
|
||||||
|
if 'user_id' in filters:
|
||||||
|
query = query.filter_by(user_id=filters.get('user_id'))
|
||||||
|
refs = query.all()
|
||||||
return [ref.to_dict() for ref in refs]
|
return [ref.to_dict() for ref in refs]
|
||||||
|
|
||||||
def _get_credential(self, session, credential_id):
|
def _get_credential(self, session, credential_id):
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class Driver(object):
|
|||||||
"""
|
"""
|
||||||
raise exception.NotImplemented()
|
raise exception.NotImplemented()
|
||||||
|
|
||||||
def list_credentials(self):
|
def list_credentials(self, **filters):
|
||||||
"""List all credentials in the system.
|
"""List all credentials in the system applying filters.
|
||||||
|
|
||||||
:returns: a list of credential_refs or an empty list.
|
:returns: a list of credential_refs or an empty list.
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ from keystone import catalog
|
|||||||
from keystone.common import dependency
|
from keystone.common import dependency
|
||||||
from keystone.common import wsgi
|
from keystone.common import wsgi
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone.contrib import ec2
|
|
||||||
from keystone.contrib import oauth1
|
from keystone.contrib import oauth1
|
||||||
from keystone import controllers
|
from keystone import controllers
|
||||||
from keystone import credential
|
from keystone import credential
|
||||||
@@ -48,7 +47,6 @@ DRIVERS = dict(
|
|||||||
assignment_api=assignment.Manager(),
|
assignment_api=assignment.Manager(),
|
||||||
catalog_api=catalog.Manager(),
|
catalog_api=catalog.Manager(),
|
||||||
credentials_api=credential.Manager(),
|
credentials_api=credential.Manager(),
|
||||||
ec2_api=ec2.Manager(),
|
|
||||||
identity_api=_IDENTITY_API,
|
identity_api=_IDENTITY_API,
|
||||||
oauth1_api=oauth1.Manager(),
|
oauth1_api=oauth1.Manager(),
|
||||||
policy_api=policy.Manager(),
|
policy_api=policy.Manager(),
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ from keystone.common import sql
|
|||||||
from keystone.common import utils
|
from keystone.common import utils
|
||||||
from keystone.common import wsgi
|
from keystone.common import wsgi
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone.contrib import ec2
|
|
||||||
from keystone.contrib import oauth1
|
from keystone.contrib import oauth1
|
||||||
from keystone import credential
|
from keystone import credential
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
@@ -268,7 +267,7 @@ class TestCase(NoModule, unittest.TestCase):
|
|||||||
# identity driver is available to the assignment manager because the
|
# identity driver is available to the assignment manager because the
|
||||||
# assignment manager gets the default assignment driver from the
|
# assignment manager gets the default assignment driver from the
|
||||||
# identity driver.
|
# identity driver.
|
||||||
for manager in [identity, assignment, catalog, credential, ec2, policy,
|
for manager in [identity, assignment, catalog, credential, policy,
|
||||||
token, token_provider, trust, oauth1]:
|
token, token_provider, trust, oauth1]:
|
||||||
# manager.__name__ is like keystone.xxx[.yyy],
|
# manager.__name__ is like keystone.xxx[.yyy],
|
||||||
# converted to xxx[_yyy]
|
# converted to xxx[_yyy]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import uuid
|
|||||||
|
|
||||||
from keystone.tests import core as test
|
from keystone.tests import core as test
|
||||||
|
|
||||||
from keystone.contrib import ec2
|
|
||||||
from keystone.contrib import s3
|
from keystone.contrib import s3
|
||||||
|
|
||||||
from keystone import exception
|
from keystone import exception
|
||||||
@@ -30,7 +29,6 @@ class S3ContribCore(test.TestCase):
|
|||||||
|
|
||||||
self.load_backends()
|
self.load_backends()
|
||||||
|
|
||||||
self.ec2_api = ec2.Manager()
|
|
||||||
self.controller = s3.S3Controller()
|
self.controller = s3.S3Controller()
|
||||||
|
|
||||||
def test_good_signature(self):
|
def test_good_signature(self):
|
||||||
|
|||||||
@@ -688,91 +688,6 @@ class KeystoneClientTests(object):
|
|||||||
# TODO(devcamcar): This assert should be more specific.
|
# TODO(devcamcar): This assert should be more specific.
|
||||||
self.assertTrue(len(roles) > 0)
|
self.assertTrue(len(roles) > 0)
|
||||||
|
|
||||||
def test_ec2_credential_crud(self):
|
|
||||||
client = self.get_client()
|
|
||||||
creds = client.ec2.list(user_id=self.user_foo['id'])
|
|
||||||
self.assertEquals(creds, [])
|
|
||||||
|
|
||||||
cred = client.ec2.create(user_id=self.user_foo['id'],
|
|
||||||
tenant_id=self.tenant_bar['id'])
|
|
||||||
creds = client.ec2.list(user_id=self.user_foo['id'])
|
|
||||||
self.assertEquals(creds, [cred])
|
|
||||||
|
|
||||||
got = client.ec2.get(user_id=self.user_foo['id'], access=cred.access)
|
|
||||||
self.assertEquals(cred, got)
|
|
||||||
|
|
||||||
client.ec2.delete(user_id=self.user_foo['id'], access=cred.access)
|
|
||||||
creds = client.ec2.list(user_id=self.user_foo['id'])
|
|
||||||
self.assertEquals(creds, [])
|
|
||||||
|
|
||||||
def test_ec2_credentials_create_404(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
client = self.get_client()
|
|
||||||
self.assertRaises(client_exceptions.NotFound,
|
|
||||||
client.ec2.create,
|
|
||||||
user_id=uuid.uuid4().hex,
|
|
||||||
tenant_id=self.tenant_bar['id'])
|
|
||||||
self.assertRaises(client_exceptions.NotFound,
|
|
||||||
client.ec2.create,
|
|
||||||
user_id=self.user_foo['id'],
|
|
||||||
tenant_id=uuid.uuid4().hex)
|
|
||||||
|
|
||||||
def test_ec2_credentials_delete_404(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
client = self.get_client()
|
|
||||||
self.assertRaises(client_exceptions.NotFound,
|
|
||||||
client.ec2.delete,
|
|
||||||
user_id=uuid.uuid4().hex,
|
|
||||||
access=uuid.uuid4().hex)
|
|
||||||
|
|
||||||
def test_ec2_credentials_get_404(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
client = self.get_client()
|
|
||||||
self.assertRaises(client_exceptions.NotFound,
|
|
||||||
client.ec2.get,
|
|
||||||
user_id=uuid.uuid4().hex,
|
|
||||||
access=uuid.uuid4().hex)
|
|
||||||
|
|
||||||
def test_ec2_credentials_list_404(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
client = self.get_client()
|
|
||||||
self.assertRaises(client_exceptions.NotFound,
|
|
||||||
client.ec2.list,
|
|
||||||
user_id=uuid.uuid4().hex)
|
|
||||||
|
|
||||||
def test_ec2_credentials_list_user_forbidden(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
|
|
||||||
two = self.get_client(self.user_two)
|
|
||||||
self.assertRaises(client_exceptions.Forbidden, two.ec2.list,
|
|
||||||
user_id=self.user_foo['id'])
|
|
||||||
|
|
||||||
def test_ec2_credentials_get_user_forbidden(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
|
|
||||||
foo = self.get_client()
|
|
||||||
cred = foo.ec2.create(user_id=self.user_foo['id'],
|
|
||||||
tenant_id=self.tenant_bar['id'])
|
|
||||||
|
|
||||||
two = self.get_client(self.user_two)
|
|
||||||
self.assertRaises(client_exceptions.Forbidden, two.ec2.get,
|
|
||||||
user_id=self.user_foo['id'], access=cred.access)
|
|
||||||
|
|
||||||
foo.ec2.delete(user_id=self.user_foo['id'], access=cred.access)
|
|
||||||
|
|
||||||
def test_ec2_credentials_delete_user_forbidden(self):
|
|
||||||
from keystoneclient import exceptions as client_exceptions
|
|
||||||
|
|
||||||
foo = self.get_client()
|
|
||||||
cred = foo.ec2.create(user_id=self.user_foo['id'],
|
|
||||||
tenant_id=self.tenant_bar['id'])
|
|
||||||
|
|
||||||
two = self.get_client(self.user_two)
|
|
||||||
self.assertRaises(client_exceptions.Forbidden, two.ec2.delete,
|
|
||||||
user_id=self.user_foo['id'], access=cred.access)
|
|
||||||
|
|
||||||
foo.ec2.delete(user_id=self.user_foo['id'], access=cred.access)
|
|
||||||
|
|
||||||
def test_service_crud(self):
|
def test_service_crud(self):
|
||||||
from keystoneclient import exceptions as client_exceptions
|
from keystoneclient import exceptions as client_exceptions
|
||||||
client = self.get_client(admin=True)
|
client = self.get_client(admin=True)
|
||||||
@@ -888,28 +803,6 @@ class KcMasterTestCase(CompatTestCase, KeystoneClientTests):
|
|||||||
def get_checkout(self):
|
def get_checkout(self):
|
||||||
return KEYSTONECLIENT_REPO, 'master'
|
return KEYSTONECLIENT_REPO, 'master'
|
||||||
|
|
||||||
def test_ec2_auth(self):
|
|
||||||
client = self.get_client()
|
|
||||||
cred = client.ec2.create(user_id=self.user_foo['id'],
|
|
||||||
tenant_id=self.tenant_bar['id'])
|
|
||||||
|
|
||||||
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
|
||||||
signer = ec2_utils.Ec2Signer(cred.secret)
|
|
||||||
credentials = {'params': {'SignatureVersion': '2'},
|
|
||||||
'access': cred.access,
|
|
||||||
'verb': 'GET',
|
|
||||||
'host': 'localhost',
|
|
||||||
'path': '/thisisgoingtowork'}
|
|
||||||
signature = signer.generate(credentials)
|
|
||||||
credentials['signature'] = signature
|
|
||||||
url = '%s/ec2tokens' % (client.auth_url)
|
|
||||||
(resp, token) = client.request(url=url,
|
|
||||||
method='POST',
|
|
||||||
body={'credentials': credentials})
|
|
||||||
# make sure we have a v2 token
|
|
||||||
self.assertEqual(resp.status_code, 200)
|
|
||||||
self.assertIn('access', token)
|
|
||||||
|
|
||||||
def test_tenant_add_and_remove_user(self):
|
def test_tenant_add_and_remove_user(self):
|
||||||
client = self.get_client(admin=True)
|
client = self.get_client(admin=True)
|
||||||
client.roles.add_user_role(tenant=self.tenant_bar['id'],
|
client.roles.add_user_role(tenant=self.tenant_bar['id'],
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from keystoneclient.contrib.ec2 import utils as ec2_utils
|
||||||
|
|
||||||
from keystone.common import sql
|
from keystone.common import sql
|
||||||
from keystone import config
|
from keystone import config
|
||||||
from keystone.tests import core as test
|
from keystone.tests import core as test
|
||||||
@@ -37,6 +39,10 @@ class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase, sql.Base):
|
|||||||
self.engine = self.get_engine()
|
self.engine = self.get_engine()
|
||||||
sql.ModelBase.metadata.create_all(bind=self.engine)
|
sql.ModelBase.metadata.create_all(bind=self.engine)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(KcMasterSqlTestCase, self).setUp()
|
||||||
|
self.default_client = self.get_client()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
sql.ModelBase.metadata.drop_all(bind=self.engine)
|
sql.ModelBase.metadata.drop_all(bind=self.engine)
|
||||||
self.engine.dispose()
|
self.engine.dispose()
|
||||||
@@ -83,6 +89,146 @@ class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase, sql.Base):
|
|||||||
self.assertRaises(client_exceptions.NotFound, client.endpoints.delete,
|
self.assertRaises(client_exceptions.NotFound, client.endpoints.delete,
|
||||||
id=endpoint.id)
|
id=endpoint.id)
|
||||||
|
|
||||||
|
def _send_ec2_auth_request(self, credentials):
|
||||||
|
url = '%s/ec2tokens' % self.default_client.auth_url
|
||||||
|
(resp, token) = self.default_client.request(
|
||||||
|
url=url, method='POST',
|
||||||
|
body={'credentials': credentials})
|
||||||
|
return resp, token
|
||||||
|
|
||||||
|
def _generate_default_user_ec2_credentials(self):
|
||||||
|
cred = self. default_client.ec2.create(
|
||||||
|
user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
signer = ec2_utils.Ec2Signer(cred.secret)
|
||||||
|
credentials = {'params': {'SignatureVersion': '2'},
|
||||||
|
'access': cred.access,
|
||||||
|
'verb': 'GET',
|
||||||
|
'host': 'localhost',
|
||||||
|
'path': '/service/cloud'}
|
||||||
|
signature = signer.generate(credentials)
|
||||||
|
return credentials, signature
|
||||||
|
|
||||||
|
def test_ec2_auth_success(self):
|
||||||
|
credentials, signature = self._generate_default_user_ec2_credentials()
|
||||||
|
credentials['signature'] = signature
|
||||||
|
resp, token = self._send_ec2_auth_request(credentials)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn('access', token)
|
||||||
|
|
||||||
|
def test_ec2_auth_failure(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
credentials, signature = self._generate_default_user_ec2_credentials()
|
||||||
|
credentials['signature'] = uuid.uuid4().hex
|
||||||
|
self.assertRaises(client_exceptions.Unauthorized,
|
||||||
|
self._send_ec2_auth_request,
|
||||||
|
credentials)
|
||||||
|
|
||||||
|
def test_ec2_credential_crud(self):
|
||||||
|
creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
|
||||||
|
self.assertEquals(creds, [])
|
||||||
|
|
||||||
|
cred = self.default_client.ec2.create(user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
|
||||||
|
self.assertEquals(creds, [cred])
|
||||||
|
got = self.default_client.ec2.get(user_id=self.user_foo['id'],
|
||||||
|
access=cred.access)
|
||||||
|
self.assertEquals(cred, got)
|
||||||
|
|
||||||
|
self.default_client.ec2.delete(user_id=self.user_foo['id'],
|
||||||
|
access=cred.access)
|
||||||
|
creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
|
||||||
|
self.assertEquals(creds, [])
|
||||||
|
|
||||||
|
def test_ec2_list_credentials(self):
|
||||||
|
cred_1 = self.default_client.ec2.create(
|
||||||
|
user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
cred_2 = self.default_client.ec2.create(
|
||||||
|
user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_service['id'])
|
||||||
|
cred_3 = self.default_client.ec2.create(
|
||||||
|
user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_mtu['id'])
|
||||||
|
two = self.get_client(self.user_two)
|
||||||
|
cred_4 = two.ec2.create(user_id=self.user_two['id'],
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
creds = self.default_client.ec2.list(user_id=self.user_foo['id'])
|
||||||
|
self.assertEquals(len(creds), 3)
|
||||||
|
self.assertEquals(sorted([cred_1, cred_2, cred_3],
|
||||||
|
key=lambda x: x.access),
|
||||||
|
sorted(creds, key=lambda x: x.access))
|
||||||
|
self.assertNotIn(cred_4, creds)
|
||||||
|
|
||||||
|
def test_ec2_credentials_create_404(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
self.assertRaises(client_exceptions.NotFound,
|
||||||
|
self.default_client.ec2.create,
|
||||||
|
user_id=uuid.uuid4().hex,
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
self.assertRaises(client_exceptions.NotFound,
|
||||||
|
self.default_client.ec2.create,
|
||||||
|
user_id=self.user_foo['id'],
|
||||||
|
tenant_id=uuid.uuid4().hex)
|
||||||
|
|
||||||
|
def test_ec2_credentials_delete_404(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
self.assertRaises(client_exceptions.NotFound,
|
||||||
|
self.default_client.ec2.delete,
|
||||||
|
user_id=uuid.uuid4().hex,
|
||||||
|
access=uuid.uuid4().hex)
|
||||||
|
|
||||||
|
def test_ec2_credentials_get_404(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
self.assertRaises(client_exceptions.NotFound,
|
||||||
|
self.default_client.ec2.get,
|
||||||
|
user_id=uuid.uuid4().hex,
|
||||||
|
access=uuid.uuid4().hex)
|
||||||
|
|
||||||
|
def test_ec2_credentials_list_404(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
self.assertRaises(client_exceptions.NotFound,
|
||||||
|
self.default_client.ec2.list,
|
||||||
|
user_id=uuid.uuid4().hex)
|
||||||
|
|
||||||
|
def test_ec2_credentials_list_user_forbidden(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
two = self.get_client(self.user_two)
|
||||||
|
self.assertRaises(client_exceptions.Forbidden, two.ec2.list,
|
||||||
|
user_id=self.user_foo['id'])
|
||||||
|
|
||||||
|
def test_ec2_credentials_get_user_forbidden(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
cred = self.default_client.ec2.create(user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
|
||||||
|
two = self.get_client(self.user_two)
|
||||||
|
self.assertRaises(client_exceptions.Forbidden, two.ec2.get,
|
||||||
|
user_id=self.user_foo['id'], access=cred.access)
|
||||||
|
|
||||||
|
self.default_client.ec2.delete(user_id=self.user_foo['id'],
|
||||||
|
access=cred.access)
|
||||||
|
|
||||||
|
def test_ec2_credentials_delete_user_forbidden(self):
|
||||||
|
from keystoneclient import exceptions as client_exceptions
|
||||||
|
|
||||||
|
cred = self.default_client.ec2.create(user_id=self.user_foo['id'],
|
||||||
|
tenant_id=self.tenant_bar['id'])
|
||||||
|
|
||||||
|
two = self.get_client(self.user_two)
|
||||||
|
self.assertRaises(client_exceptions.Forbidden, two.ec2.delete,
|
||||||
|
user_id=self.user_foo['id'], access=cred.access)
|
||||||
|
|
||||||
|
self.default_client.ec2.delete(user_id=self.user_foo['id'],
|
||||||
|
access=cred.access)
|
||||||
|
|
||||||
def test_endpoint_create_404(self):
|
def test_endpoint_create_404(self):
|
||||||
from keystoneclient import exceptions as client_exceptions
|
from keystoneclient import exceptions as client_exceptions
|
||||||
client = self.get_client(admin=True)
|
client = self.get_client(admin=True)
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
import ast
|
import ast
|
||||||
|
|
||||||
from keystone.contrib.admin_crud import core as admin_crud_core
|
from keystone.contrib.admin_crud import core as admin_crud_core
|
||||||
from keystone.contrib.ec2 import core as ec2_core
|
|
||||||
from keystone.contrib.s3 import core as s3_core
|
from keystone.contrib.s3 import core as s3_core
|
||||||
from keystone.contrib.stats import core as stats_core
|
from keystone.contrib.stats import core as stats_core
|
||||||
from keystone.contrib.user_crud import core as user_crud_core
|
from keystone.contrib.user_crud import core as user_crud_core
|
||||||
@@ -28,7 +27,7 @@ from keystone import service
|
|||||||
class TestSingularPlural(object):
|
class TestSingularPlural(object):
|
||||||
def test_keyword_arg_condition_or_methods(self):
|
def test_keyword_arg_condition_or_methods(self):
|
||||||
"""Raise if we see a keyword arg called 'condition' or 'methods'."""
|
"""Raise if we see a keyword arg called 'condition' or 'methods'."""
|
||||||
modules = [admin_crud_core, ec2_core, s3_core, stats_core,
|
modules = [admin_crud_core, s3_core, stats_core,
|
||||||
user_crud_core, identity_core, service]
|
user_crud_core, identity_core, service]
|
||||||
for module in modules:
|
for module in modules:
|
||||||
filename = module.__file__
|
filename = module.__file__
|
||||||
|
|||||||
@@ -36,7 +36,10 @@ from keystone.tests import core as test
|
|||||||
|
|
||||||
from keystone.common import sql
|
from keystone.common import sql
|
||||||
from keystone.common.sql import migration
|
from keystone.common.sql import migration
|
||||||
|
from keystone.common import utils
|
||||||
from keystone import config
|
from keystone import config
|
||||||
|
from keystone import credential
|
||||||
|
from keystone import exception
|
||||||
|
|
||||||
import default_fixtures
|
import default_fixtures
|
||||||
|
|
||||||
@@ -69,6 +72,7 @@ class SqlMigrateBase(test.TestCase):
|
|||||||
|
|
||||||
# create and share a single sqlalchemy engine for testing
|
# create and share a single sqlalchemy engine for testing
|
||||||
self.engine = self.base.get_engine(allow_global_engine=False)
|
self.engine = self.base.get_engine(allow_global_engine=False)
|
||||||
|
sql.core.set_global_engine(self.engine)
|
||||||
self.Session = self.base.get_sessionmaker(engine=self.engine,
|
self.Session = self.base.get_sessionmaker(engine=self.engine,
|
||||||
autocommit=False)
|
autocommit=False)
|
||||||
|
|
||||||
@@ -87,6 +91,7 @@ class SqlMigrateBase(test.TestCase):
|
|||||||
autoload=True)
|
autoload=True)
|
||||||
self.downgrade(0)
|
self.downgrade(0)
|
||||||
table.drop(self.engine, checkfirst=True)
|
table.drop(self.engine, checkfirst=True)
|
||||||
|
sql.core.set_global_engine(None)
|
||||||
super(SqlMigrateBase, self).tearDown()
|
super(SqlMigrateBase, self).tearDown()
|
||||||
|
|
||||||
def select_table(self, name):
|
def select_table(self, name):
|
||||||
@@ -1292,6 +1297,208 @@ class SqlUpgradeTests(SqlMigrateBase):
|
|||||||
else:
|
else:
|
||||||
self.assertEqual(len(index_data), 0)
|
self.assertEqual(len(index_data), 0)
|
||||||
|
|
||||||
|
def test_migrate_ec2_credential(self):
|
||||||
|
user = {
|
||||||
|
'id': 'foo',
|
||||||
|
'name': 'FOO',
|
||||||
|
'password': 'foo2',
|
||||||
|
'enabled': True,
|
||||||
|
'email': 'foo@bar.com',
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project = {
|
||||||
|
'id': 'bar',
|
||||||
|
'name': 'BAR',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
ec2_credential = {
|
||||||
|
'access': uuid.uuid4().hex,
|
||||||
|
'secret': uuid.uuid4().hex,
|
||||||
|
'user_id': user['id'],
|
||||||
|
'tenant_id': project['id'],
|
||||||
|
}
|
||||||
|
session = self.Session()
|
||||||
|
self.upgrade(7)
|
||||||
|
self.insert_dict(session, 'ec2_credential', ec2_credential)
|
||||||
|
self.insert_dict(session, 'user', user)
|
||||||
|
self.insert_dict(session, 'tenant', project)
|
||||||
|
self.upgrade(33)
|
||||||
|
self.assertTableDoesNotExist('ec2_credential')
|
||||||
|
cred_table = sqlalchemy.Table('credential',
|
||||||
|
self.metadata,
|
||||||
|
autoload=True)
|
||||||
|
expected_credential_id = utils.hash_access_key(
|
||||||
|
ec2_credential['access'])
|
||||||
|
cred = session.query(cred_table).filter_by(
|
||||||
|
id=expected_credential_id).one()
|
||||||
|
self.assertEqual(cred.user_id, ec2_credential['user_id'])
|
||||||
|
self.assertEqual(cred.project_id, ec2_credential['tenant_id'])
|
||||||
|
# test list credential using credential manager.
|
||||||
|
credential_api = credential.Manager()
|
||||||
|
self.assertNotEmpty(credential_api.
|
||||||
|
list_credentials(
|
||||||
|
user_id=ec2_credential['user_id']))
|
||||||
|
self.downgrade(32)
|
||||||
|
session.commit()
|
||||||
|
self.assertTableExists('ec2_credential')
|
||||||
|
ec2_cred_table = sqlalchemy.Table('ec2_credential',
|
||||||
|
self.metadata,
|
||||||
|
autoload=True)
|
||||||
|
ec2_cred = session.query(ec2_cred_table).filter_by(
|
||||||
|
access=ec2_credential['access']).one()
|
||||||
|
self.assertEqual(ec2_cred.user_id, ec2_credential['user_id'])
|
||||||
|
|
||||||
|
def test_migrate_ec2_credential_with_conflict_project(self):
|
||||||
|
user = {
|
||||||
|
'id': 'foo',
|
||||||
|
'name': 'FOO',
|
||||||
|
'password': 'foo2',
|
||||||
|
'enabled': True,
|
||||||
|
'email': 'foo@bar.com',
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project_1 = {
|
||||||
|
'id': 'bar',
|
||||||
|
'name': 'BAR',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project_2 = {
|
||||||
|
'id': 'baz',
|
||||||
|
'name': 'BAZ',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
ec2_credential = {
|
||||||
|
'access': uuid.uuid4().hex,
|
||||||
|
'secret': uuid.uuid4().hex,
|
||||||
|
'user_id': user['id'],
|
||||||
|
'tenant_id': project_1['id'],
|
||||||
|
}
|
||||||
|
blob = {'access': ec2_credential['access'],
|
||||||
|
'secret': ec2_credential['secret']}
|
||||||
|
v3_credential = {
|
||||||
|
'id': utils.hash_access_key(ec2_credential['access']),
|
||||||
|
'user_id': user['id'],
|
||||||
|
# set the project id to simulate a conflict
|
||||||
|
'project_id': project_2['id'],
|
||||||
|
'blob': json.dumps(blob),
|
||||||
|
'type': 'ec2',
|
||||||
|
'extra': json.dumps({})
|
||||||
|
}
|
||||||
|
session = self.Session()
|
||||||
|
self.upgrade(7)
|
||||||
|
self.insert_dict(session, 'ec2_credential', ec2_credential)
|
||||||
|
self.insert_dict(session, 'user', user)
|
||||||
|
self.insert_dict(session, 'tenant', project_1)
|
||||||
|
self.insert_dict(session, 'tenant', project_2)
|
||||||
|
self.upgrade(32)
|
||||||
|
self.insert_dict(session, 'credential', v3_credential)
|
||||||
|
self.assertRaises(exception.Conflict, self.upgrade, 33)
|
||||||
|
|
||||||
|
def test_migrate_ec2_credential_with_conflict_secret(self):
|
||||||
|
user = {
|
||||||
|
'id': 'foo',
|
||||||
|
'name': 'FOO',
|
||||||
|
'password': 'foo2',
|
||||||
|
'enabled': True,
|
||||||
|
'email': 'foo@bar.com',
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project_1 = {
|
||||||
|
'id': 'bar',
|
||||||
|
'name': 'BAR',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project_2 = {
|
||||||
|
'id': 'baz',
|
||||||
|
'name': 'BAZ',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
ec2_credential = {
|
||||||
|
'access': uuid.uuid4().hex,
|
||||||
|
'secret': uuid.uuid4().hex,
|
||||||
|
'user_id': user['id'],
|
||||||
|
'tenant_id': project_1['id'],
|
||||||
|
}
|
||||||
|
blob = {'access': ec2_credential['access'],
|
||||||
|
'secret': 'different secret'}
|
||||||
|
v3_cred_different_secret = {
|
||||||
|
'id': utils.hash_access_key(ec2_credential['access']),
|
||||||
|
'user_id': user['id'],
|
||||||
|
'project_id': project_1['id'],
|
||||||
|
'blob': json.dumps(blob),
|
||||||
|
'type': 'ec2',
|
||||||
|
'extra': json.dumps({})
|
||||||
|
}
|
||||||
|
|
||||||
|
session = self.Session()
|
||||||
|
self.upgrade(7)
|
||||||
|
self.insert_dict(session, 'ec2_credential', ec2_credential)
|
||||||
|
self.insert_dict(session, 'user', user)
|
||||||
|
self.insert_dict(session, 'tenant', project_1)
|
||||||
|
self.insert_dict(session, 'tenant', project_2)
|
||||||
|
self.upgrade(32)
|
||||||
|
self.insert_dict(session, 'credential', v3_cred_different_secret)
|
||||||
|
self.assertRaises(exception.Conflict, self.upgrade, 33)
|
||||||
|
|
||||||
|
def test_migrate_ec2_credential_with_invalid_blob(self):
|
||||||
|
user = {
|
||||||
|
'id': 'foo',
|
||||||
|
'name': 'FOO',
|
||||||
|
'password': 'foo2',
|
||||||
|
'enabled': True,
|
||||||
|
'email': 'foo@bar.com',
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project_1 = {
|
||||||
|
'id': 'bar',
|
||||||
|
'name': 'BAR',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
project_2 = {
|
||||||
|
'id': 'baz',
|
||||||
|
'name': 'BAZ',
|
||||||
|
'description': 'description',
|
||||||
|
'enabled': True,
|
||||||
|
'extra': json.dumps({'enabled': True})
|
||||||
|
}
|
||||||
|
ec2_credential = {
|
||||||
|
'access': uuid.uuid4().hex,
|
||||||
|
'secret': uuid.uuid4().hex,
|
||||||
|
'user_id': user['id'],
|
||||||
|
'tenant_id': project_1['id'],
|
||||||
|
}
|
||||||
|
blob = '{"abc":"def"d}'
|
||||||
|
v3_cred_invalid_blob = {
|
||||||
|
'id': utils.hash_access_key(ec2_credential['access']),
|
||||||
|
'user_id': user['id'],
|
||||||
|
'project_id': project_1['id'],
|
||||||
|
'blob': json.dumps(blob),
|
||||||
|
'type': 'ec2',
|
||||||
|
'extra': json.dumps({})
|
||||||
|
}
|
||||||
|
|
||||||
|
session = self.Session()
|
||||||
|
self.upgrade(7)
|
||||||
|
self.insert_dict(session, 'ec2_credential', ec2_credential)
|
||||||
|
self.insert_dict(session, 'user', user)
|
||||||
|
self.insert_dict(session, 'tenant', project_1)
|
||||||
|
self.insert_dict(session, 'tenant', project_2)
|
||||||
|
self.upgrade(32)
|
||||||
|
self.insert_dict(session, 'credential', v3_cred_invalid_blob)
|
||||||
|
self.assertRaises(exception.ValidationError, self.upgrade, 33)
|
||||||
|
|
||||||
def populate_user_table(self, with_pass_enab=False,
|
def populate_user_table(self, with_pass_enab=False,
|
||||||
with_pass_enab_domain=False):
|
with_pass_enab_domain=False):
|
||||||
# Populate the appropriate fields in the user
|
# Populate the appropriate fields in the user
|
||||||
|
|||||||
Reference in New Issue
Block a user