Store AccessKey secret_key in resource data

Similar to the change for SignalResponder, store the secret key
encrypted in resource data, rather than requesting it from keystone
every time the user requests the SecretKey attribute.

Unlike SignalResponder (which stores the signed URL after using
the keypair to sign the request), we need this to be backwards
compatible, to cope with upgrading heat with stacks in the DB
which contain the AccessKey resource but don't have the secret
stored.

blueprint: instance-users
Related-Bug: #1262177

Change-Id: I92826a6dc028b151d98c0a5e2f6ec27db4b744b9
This commit is contained in:
Steven Hardy 2013-12-23 20:45:13 +00:00
parent ba2503c2ff
commit 7214c146f1
2 changed files with 62 additions and 14 deletions

View File

@ -14,6 +14,7 @@
# under the License.
from heat.common import exception
from heat.db import api as db_api
from heat.engine import clients
from heat.engine import constraints
from heat.engine import properties
@ -217,6 +218,12 @@ class AccessKey(resource.Resource):
self.resource_id_set(kp.access)
self._secret = kp.secret
# Store the secret key, encrypted, in the DB so we don't have to
# re-request it from keystone every time someone requests the
# SecretAccessKey attribute
db_api.resource_data_set(self, 'secret_key', kp.secret,
redact=True)
def handle_delete(self):
self._secret = None
if self.resource_id is None:
@ -246,22 +253,25 @@ class AccessKey(resource.Resource):
'username': self.properties[self.USER_NAME],
'msg': "resource_id not yet set"})
else:
# First try to retrieve the secret from resource_data, but
# for backwards compatibility, fall back to requesting from
# keystone
try:
user_id = self._get_user().resource_id
kp = self.keystone().get_ec2_keypair(user_id)
except Exception as ex:
logger.warn(_('could not get secret for %(username)s '
'Error:%(msg)s') % {
'username': self.properties[self.USER_NAME],
'msg': str(ex)})
else:
if kp.access == self.resource_id:
self._secret = db_api.resource_data_get(self, 'secret_key')
except exception.NotFound:
try:
user_id = self._get_user().resource_id
kp = self.keystone().get_ec2_keypair(user_id)
self._secret = kp.secret
else:
msg = (_("Unexpected ec2 keypair, for %(id)s access "
"%(access)s") % {
'id': user_id, 'access': kp.access})
logger.error(msg)
# Store the key in resource_data
db_api.resource_data_set(self, 'secret_key',
kp.secret, redact=True)
except Exception as ex:
logger.warn(_('could not get secret for %(username)s '
'Error:%(msg)s') % {
'username':
self.properties[self.USER_NAME],
'msg': str(ex)})
return self._secret or '000-000-000'

View File

@ -17,6 +17,7 @@ from oslo.config import cfg
from heat.common import exception
from heat.common import template_format
from heat.db import api as db_api
from heat.engine import resource
from heat.engine import scheduler
from heat.engine.resources import user
@ -295,6 +296,11 @@ class AccessKeyTest(UserPolicyTestCase):
self.assertEqual(self.fc.secret,
rsrc._secret)
# Ensure the resource data has been stored correctly
rs_data = db_api.resource_data_get_all(rsrc)
self.assertEqual(self.fc.secret, rs_data.get('secret_key'))
self.assertEqual(1, len(rs_data.keys()))
self.assertEqual(utils.PhysName(stack.name, 'CfnUser'),
rsrc.FnGetAtt('UserName'))
rsrc._secret = None
@ -303,7 +309,39 @@ class AccessKeyTest(UserPolicyTestCase):
self.assertRaises(exception.InvalidTemplateAttribute,
rsrc.FnGetAtt, 'Foo')
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_access_key_get_from_keystone(self):
self.m.StubOutWithMock(user.AccessKey, 'keystone')
self.m.StubOutWithMock(user.User, 'keystone')
user.AccessKey.keystone().MultipleTimes().AndReturn(self.fc)
user.User.keystone().MultipleTimes().AndReturn(self.fc)
self.m.ReplayAll()
t = template_format.parse(user_accesskey_template)
stack = utils.parse_stack(t)
self.create_user(t, stack, 'CfnUser')
rsrc = self.create_access_key(t, stack, 'HostKeys')
# Delete the resource data for secret_key, to test that existing
# stacks which don't have the resource_data stored will continue
# working via retrieving the keypair from keystone
db_api.resource_data_delete(rsrc, 'secret_key')
rs_data = db_api.resource_data_get_all(rsrc)
self.assertEqual(0, len(rs_data.keys()))
rsrc._secret = None
self.assertEqual(self.fc.secret,
rsrc.FnGetAtt('SecretAccessKey'))
scheduler.TaskRunner(rsrc.delete)()
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_access_key_deleted(self):