Merge "Encrypt properties data"

This commit is contained in:
Jenkins 2015-06-04 09:51:51 +00:00 committed by Gerrit Code Review
commit e72aa7a23f
6 changed files with 148 additions and 2 deletions

View File

@ -0,0 +1,25 @@
#
# 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
def upgrade(migrate_engine):
meta = sqlalchemy.MetaData()
meta.bind = migrate_engine
resource = sqlalchemy.Table('resource', meta, autoload=True)
properties_data_encrypted = sqlalchemy.Column('properties_data_encrypted',
sqlalchemy.Boolean,
default=False)
properties_data_encrypted.create(resource)

View File

@ -298,6 +298,8 @@ class Resource(BASE, HeatBase, StateAware):
# created/modified. (bug #1193269)
updated_at = sqlalchemy.Column(sqlalchemy.DateTime)
properties_data = sqlalchemy.Column('properties_data', types.Json)
properties_data_encrypted = sqlalchemy.Column('properties_data_encrypted',
sqlalchemy.Boolean)
engine_id = sqlalchemy.Column(sqlalchemy.String(36))
atomic_key = sqlalchemy.Column(sqlalchemy.Integer)

View File

@ -1010,6 +1010,10 @@ class Resource(object):
def _store(self, metadata=None):
'''Create the resource in the database.'''
properties_data_encrypted, properties_data = \
resource_objects.Resource.encrypt_properties_data(
self._stored_properties_data)
try:
rs = {'action': self.action,
'status': self.status,
@ -1018,7 +1022,8 @@ class Resource(object):
'nova_instance': self.resource_id,
'name': self.name,
'rsrc_metadata': metadata,
'properties_data': self._stored_properties_data,
'properties_data': properties_data,
'properties_data_encrypted': properties_data_encrypted,
'needed_by': self.needed_by,
'requires': self.requires,
'replaces': self.replaces,
@ -1048,13 +1053,17 @@ class Resource(object):
self.status = status
self.status_reason = reason
properties_data_encrypted, properties_data = \
resource_objects.Resource.encrypt_properties_data(
self._stored_properties_data)
data = {
'action': self.action,
'status': self.status,
'status_reason': reason,
'stack_id': self.stack.id,
'updated_at': self.updated_time,
'properties_data': self._stored_properties_data,
'properties_data': properties_data,
'properties_data_encrypted': properties_data_encrypted,
'needed_by': self.needed_by,
'requires': self.requires,
'replaces': self.replaces,

View File

@ -18,15 +18,21 @@ Resource object
"""
from oslo_config import cfg
from oslo_serialization import jsonutils
from oslo_utils import encodeutils
from oslo_versionedobjects import base
from oslo_versionedobjects import fields
import six
from heat.common import crypt
from heat.db import api as db_api
from heat.objects import fields as heat_fields
from heat.objects import resource_data
from heat.objects import stack
cfg.CONF.import_opt('encrypt_parameters_and_properties', 'heat.common.config')
class Resource(
base.VersionedObject,
@ -46,6 +52,7 @@ class Resource(
'action': fields.StringField(nullable=True),
'rsrc_metadata': heat_fields.JsonField(nullable=True),
'properties_data': heat_fields.JsonField(nullable=True),
'properties_data_encrypted': fields.BooleanField(default=False),
'data': fields.ListOfObjectsField(
resource_data.ResourceData,
nullable=True
@ -74,6 +81,17 @@ class Resource(
)
else:
resource[field] = db_resource[field]
if resource.properties_data_encrypted and resource.properties_data:
properties_data = {}
for prop_name, prop_value in resource.properties_data.items():
decrypt_function_name = prop_value[0]
decrypt_function = getattr(crypt, decrypt_function_name, None)
decrypted_value = decrypt_function(prop_value[1])
prop_string = jsonutils.loads(decrypted_value)
properties_data[prop_name] = prop_string
resource.properties_data = properties_data
resource._context = context
resource.obj_reset_changes()
return resource
@ -157,3 +175,15 @@ class Resource(
resource_db = db_api.resource_get(self._context, self.id)
resource_db.refresh(attrs=attrs)
return self._refresh()
@staticmethod
def encrypt_properties_data(data):
if cfg.CONF.encrypt_parameters_and_properties and data:
result = {}
for prop_name, prop_value in data.items():
prop_string = jsonutils.dumps(prop_value)
encoded_value = encodeutils.safe_encode(prop_string)
encrypted_value = crypt.encrypt(encoded_value)
result[prop_name] = encrypted_value
return (True, result)
return (False, data)

View File

@ -609,6 +609,10 @@ class HeatMigrationsCheckers(test_migrations.WalkVersionsMixin,
def _check_062(self, engine, data):
self.assertColumnExists(engine, 'stack', 'parent_resource_name')
def _check_063(self, engine, data):
self.assertColumnExists(engine, 'resource',
'properties_data_encrypted')
class TestHeatMigrationsMySQL(HeatMigrationsCheckers,
test_base.MySQLOpportunisticTestCase):

View File

@ -25,6 +25,7 @@ from heat.common import exception
from heat.common.i18n import _
from heat.common import short_id
from heat.common import timeutils
from heat.db import api as db_api
from heat.engine import attributes
from heat.engine.cfn import functions as cfn_funcs
from heat.engine import constraints
@ -1317,6 +1318,81 @@ class ResourceTest(common.HeatTestCase):
res.FnGetAtt('attr2')
self.assertIn("Attribute attr2 is not of type Map", self.LOG.output)
def test_properties_data_stored_encrypted_decrypted_on_load(self):
cfg.CONF.set_override('encrypt_parameters_and_properties', True)
tmpl = rsrc_defn.ResourceDefinition('test_resource', 'Foo')
stored_properties_data = {'prop1': 'string',
'prop2': {'a': 'dict'},
'prop3': 1,
'prop4': ['a', 'list'],
'prop5': True}
# The db data should be encrypted when _store_or_update() is called
res = generic_rsrc.GenericResource('test_res_enc', tmpl, self.stack)
res._stored_properties_data = stored_properties_data
res._store_or_update(res.CREATE, res.IN_PROGRESS, 'test_store')
db_res = db_api.resource_get(res.context, res.id)
self.assertNotEqual('string',
db_res.properties_data['prop1'])
# The db data should be encrypted when _store() is called
res = generic_rsrc.GenericResource('test_res_enc', tmpl, self.stack)
res._stored_properties_data = stored_properties_data
res._store()
db_res = db_api.resource_get(res.context, res.id)
self.assertNotEqual('string',
db_res.properties_data['prop1'])
# The properties data should be decrypted when the object is
# loaded using get_obj
res_obj = resource_objects.Resource.get_obj(res.context, res.id)
self.assertEqual('string', res_obj.properties_data['prop1'])
# The properties data should be decrypted when the object is
# loaded using get_all_by_stack
res_objs = resource_objects.Resource.get_all_by_stack(res.context,
self.stack.id)
res_obj = res_objs['test_res_enc']
self.assertEqual('string', res_obj.properties_data['prop1'])
def test_properties_data_no_encryption(self):
cfg.CONF.set_override('encrypt_parameters_and_properties', False)
tmpl = rsrc_defn.ResourceDefinition('test_resource', 'Foo')
stored_properties_data = {'prop1': 'string',
'prop2': {'a': 'dict'},
'prop3': 1,
'prop4': ['a', 'list'],
'prop5': True}
# The db data should not be encrypted when _store_or_update()
# is called
res = generic_rsrc.GenericResource('test_res_enc', tmpl, self.stack)
res._stored_properties_data = stored_properties_data
res._store_or_update(res.CREATE, res.IN_PROGRESS, 'test_store')
db_res = db_api.resource_get(res.context, res.id)
self.assertEqual('string', db_res.properties_data['prop1'])
# The db data should not be encrypted when _store() is called
res = generic_rsrc.GenericResource('test_res_enc', tmpl, self.stack)
res._stored_properties_data = stored_properties_data
res._store()
db_res = db_api.resource_get(res.context, res.id)
self.assertEqual('string', db_res.properties_data['prop1'])
# The properties data should not be modified when the object
# is loaded using get_obj
res_obj = resource_objects.Resource.get_obj(res.context, res.id)
self.assertEqual('string', res_obj.properties_data['prop1'])
# The properties data should not be modified when the object
# is loaded using get_all_by_stack
res_objs = resource_objects.Resource.get_all_by_stack(res.context,
self.stack.id)
res_obj = res_objs['test_res_enc']
self.assertEqual('string', res_obj.properties_data['prop1'])
class ResourceAdoptTest(common.HeatTestCase):
def setUp(self):