SignalResponder store access/secret in resource data

We need to stop relying on the logic in heat_keystoneclient which
assumes there is only one keypair per user, as this assumption
may not remain true in future.

So instead, store the access/secret key in the DB as resource data,
which is probably better from a performance perspective anyway, and
will also allow SignalResponder subclasses to potentially override
the default credentials implementation more easily.

Change-Id: I7952181f30c94848acf358ab34cf17b6281d43ac
Partial-Bug: #1089261
blueprint: instance-users
This commit is contained in:
Steven Hardy 2013-12-16 17:17:13 +00:00
parent 151bfa1d11
commit ba9c290212
3 changed files with 47 additions and 14 deletions

View File

@ -53,6 +53,11 @@ class SignalResponder(resource.Resource):
if not kp:
raise exception.Error(_("Error creating ec2 keypair for user %s") %
user_id)
else:
db_api.resource_data_set(self, 'access_key', kp.access,
redact=True)
db_api.resource_data_set(self, 'secret_key', kp.secret,
redact=True)
def handle_delete(self):
if self.resource_id is None:
@ -61,10 +66,11 @@ class SignalResponder(resource.Resource):
self.keystone().delete_stack_user(self.resource_id)
except clients.hkc.kc.exceptions.NotFound:
pass
try:
db_api.resource_data_delete(self, 'ec2_signed_url')
except exception.NotFound:
pass
for data_key in ('ec2_signed_url', 'access_key', 'secret_key'):
try:
db_api.resource_data_delete(self, data_key)
except exception.NotFound:
pass
def _get_signed_url(self, signal_type=SIGNAL):
"""Create properly formatted and pre-signed URL.
@ -87,7 +93,8 @@ class SignalResponder(resource.Resource):
host_url = urlutils.urlparse(signal_url)
path = self.identifier().arn_url_path()
credentials = self.keystone().get_ec2_keypair(self.resource_id)
access_key = db_api.resource_data_get(self, 'access_key')
secret_key = db_api.resource_data_get(self, 'secret_key')
# Note the WSGI spec apparently means that the webob request we end up
# prcessing in the CFN API (ec2token.py) has an unquoted path, so we
@ -99,12 +106,12 @@ class SignalResponder(resource.Resource):
'path': unquoted_path,
'params': {'SignatureMethod': 'HmacSHA256',
'SignatureVersion': '2',
'AWSAccessKeyId': credentials.access,
'AWSAccessKeyId': access_key,
'Timestamp':
self.created_time.strftime("%Y-%m-%dT%H:%M:%SZ")
}}
# Sign the requested
signer = ec2_utils.Ec2Signer(credentials.secret)
# Sign the request
signer = ec2_utils.Ec2Signer(secret_key)
request['params']['Signature'] = signer.generate(request)
qs = urlutils.urlencode(request['params'])

View File

@ -1385,8 +1385,7 @@ class AutoScalingTest(HeatTestCase):
self._stub_create(1)
self.m.StubOutWithMock(asc.ScalingPolicy, 'keystone')
asc.ScalingPolicy.keystone().MultipleTimes().AndReturn(
self.fc)
asc.ScalingPolicy.keystone().MultipleTimes().AndReturn(self.fc)
self.m.ReplayAll()
rsrc = self.create_scaling_group(t, stack, 'WebServerGroup')
@ -1402,10 +1401,6 @@ class AutoScalingTest(HeatTestCase):
self._stub_meta_expected(now, 'ChangeInCapacity : 1', 2)
self._stub_create(1)
self.m.StubOutWithMock(asc.ScalingPolicy, 'keystone')
asc.ScalingPolicy.keystone().MultipleTimes().AndReturn(
self.fc)
self.m.ReplayAll()
# Trigger alarm

View File

@ -24,9 +24,12 @@ from heat.tests import utils
from heat.common import exception
from heat.common import template_format
from heat.db import api as db_api
from heat.engine import clients
from heat.engine import parser
from heat.engine import resource
from heat.engine import scheduler
from heat.engine import signal_responder as sr
from keystoneclient import exceptions as kc_exceptions
@ -154,6 +157,34 @@ class SignalTest(HeatTestCase):
self.assertIn('Error creating ec2 keypair', rsrc.status_reason)
self.assertEqual('123xyz', rsrc.resource_id, '123xyz')
@utils.stack_delete_after
def test_resource_data(self):
self.stack = self.create_stack(stack_name='resource_data_test',
stub=False)
self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
clients.OpenStackClients.keystone().MultipleTimes().AndReturn(
fakes.FakeKeystoneClient(
access='anaccesskey', secret='verysecret'))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['signal_handler']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
# Ensure the resource data has been stored correctly
rs_data = db_api.resource_data_get_all(rsrc)
self.assertEqual('anaccesskey', rs_data.get('access_key'))
self.assertEqual('verysecret', rs_data.get('secret_key'))
self.assertEqual(2, len(rs_data.keys()))
# And that we remove it on delete
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
rs_data = db_api.resource_data_get_all(rsrc)
self.assertEqual(0, len(rs_data.keys()))
@utils.stack_delete_after
def test_FnGetAtt_Alarm_Url(self):
self.stack = self.create_stack()