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:
parent
ba2503c2ff
commit
7214c146f1
|
@ -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'
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue