Add support for multiple encryption methods

We use only one encrypt() method but it returns the method to decrypt
the data. This way we can change the encryption mechanism but
always have a way to know how to decrypt whatever is stored.

Change-Id: I2315a33105a8766f69d02f0617af39a9dae19ddf
Partial-bug: #1251647
This commit is contained in:
Angus Salkeld 2013-12-16 19:59:18 +11:00
parent 37919c6b95
commit f67cd21828
5 changed files with 67 additions and 15 deletions

View File

@ -35,14 +35,14 @@ logger = logging.getLogger(__name__)
def encrypt(auth_info):
if auth_info is None:
return None
return None, None
iv = urandom(AES.block_size)
cipher = AES.new(cfg.CONF.auth_encryption_key[:32], AES.MODE_CFB, iv)
res = base64.b64encode(iv + cipher.encrypt(auth_info))
return res
return 'heat_decrypt', res
def decrypt(auth_info):
def heat_decrypt(auth_info):
if auth_info is None:
return None
auth = base64.b64decode(auth_info)

View File

@ -142,7 +142,7 @@ def resource_data_get_all(resource):
for res in result:
if res.redact:
ret[res.key] = _decrypt(res.value)
ret[res.key] = _decrypt(res.value, res.decrypt_method)
else:
ret[res.key] = res.value
@ -157,17 +157,22 @@ def resource_data_get(resource, key):
resource.id,
key)
if result.redact:
return _decrypt(result.value)
return _decrypt(result.value, result.decrypt_method)
return result.value
def _encrypt(value):
if value is not None:
return crypt.encrypt(value.encode('utf-8'))
else:
return None, None
def _decrypt(enc_value):
value = crypt.decrypt(enc_value)
def _decrypt(enc_value, method):
if method is None:
return None
decryptor = getattr(crypt, method)
value = decryptor(enc_value)
if value is not None:
return unicode(value, 'utf-8')
@ -188,7 +193,9 @@ def resource_data_get_by_key(context, resource_id, key):
def resource_data_set(resource, key, value, redact=False):
"""Save resource's key/value pair to database."""
if redact:
value = _encrypt(value)
method, value = _encrypt(value)
else:
method = ''
try:
current = resource_data_get_by_key(resource.context, resource.id, key)
except exception.NotFound:
@ -197,6 +204,7 @@ def resource_data_set(resource, key, value, redact=False):
current.resource_id = resource.id
current.redact = redact
current.value = value
current.decrypt_method = method
current.save(session=resource.context.session)
return current
@ -417,7 +425,9 @@ def user_creds_create(context):
values = context.to_dict()
user_creds_ref = models.UserCreds()
if values.get('trust_id'):
user_creds_ref.trust_id = _encrypt(values.get('trust_id'))
method, trust_id = _encrypt(values.get('trust_id'))
user_creds_ref.trust_id = trust_id
user_creds_ref.decrypt_method = method
user_creds_ref.trustor_user_id = values.get('trustor_user_id')
user_creds_ref.username = None
user_creds_ref.password = None
@ -425,7 +435,9 @@ def user_creds_create(context):
user_creds_ref.tenant_id = values.get('tenant_id')
else:
user_creds_ref.update(values)
user_creds_ref.password = _encrypt(values['password'])
method, password = _encrypt(values['password'])
user_creds_ref.password = password
user_creds_ref.decrypt_method = method
user_creds_ref.save(_session(context))
return user_creds_ref
@ -435,8 +447,9 @@ def user_creds_get(user_creds_id):
# Return a dict copy of db results, do not decrypt details into db_result
# or it can be committed back to the DB in decrypted form
result = dict(db_result)
result['password'] = _decrypt(result['password'])
result['trust_id'] = _decrypt(result['trust_id'])
del result['decrypt_method']
result['password'] = _decrypt(result['password'], db_result.decrypt_method)
result['trust_id'] = _decrypt(result['trust_id'], db_result.decrypt_method)
return result

View File

@ -0,0 +1,33 @@
# 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
for table in ('user_creds', 'resource_data'):
table = sqlalchemy.Table(table, meta, autoload=True)
method = sqlalchemy.Column('decrypt_method',
sqlalchemy.String(length=64),
default='heat_decrypt')
method.create(table)
def downgrade(migrate_engine):
meta = sqlalchemy.MetaData(bind=migrate_engine)
for table in ('user_creds', 'resource_data'):
table = sqlalchemy.Table(table, meta, autoload=True)
table.c.decrypt_method.drop()

View File

@ -166,6 +166,7 @@ class UserCreds(BASE, HeatBase):
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
username = sqlalchemy.Column(sqlalchemy.String(255))
password = sqlalchemy.Column(sqlalchemy.String(255))
decrypt_method = sqlalchemy.Column(sqlalchemy.String(64))
tenant = sqlalchemy.Column(sqlalchemy.String(1024))
auth_url = sqlalchemy.Column(sqlalchemy.String)
tenant_id = sqlalchemy.Column(sqlalchemy.String(256))
@ -207,6 +208,7 @@ class ResourceData(BASE, HeatBase):
key = sqlalchemy.Column('key', sqlalchemy.String(255))
value = sqlalchemy.Column('value', sqlalchemy.String)
redact = sqlalchemy.Column('redact', sqlalchemy.Boolean)
decrypt_method = sqlalchemy.Column(sqlalchemy.String(64))
resource_id = sqlalchemy.Column('resource_id',
sqlalchemy.String(36),
sqlalchemy.ForeignKey('resource.id'),

View File

@ -603,7 +603,9 @@ class DBAPIUserCredsTest(HeatTestCase):
user_creds = create_user_creds(self.ctx, trust_id='test_trust_id',
trustor_user_id='trustor_id')
self.assertIsNotNone(user_creds.id)
self.assertEqual('test_trust_id', db_api._decrypt(user_creds.trust_id))
self.assertEqual('test_trust_id',
db_api._decrypt(user_creds.trust_id,
user_creds.decrypt_method))
self.assertEqual('trustor_id', user_creds.trustor_user_id)
self.assertIsNone(user_creds.username)
self.assertIsNone(user_creds.password)
@ -614,12 +616,14 @@ class DBAPIUserCredsTest(HeatTestCase):
user_creds = create_user_creds(self.ctx)
self.assertIsNotNone(user_creds.id)
self.assertEqual(self.ctx.password,
db_api._decrypt(user_creds.password))
db_api._decrypt(user_creds.password,
user_creds.decrypt_method))
def test_user_creds_get(self):
user_creds = create_user_creds(self.ctx)
ret_user_creds = db_api.user_creds_get(user_creds.id)
self.assertEqual(db_api._decrypt(user_creds.password),
self.assertEqual(db_api._decrypt(user_creds.password,
user_creds.decrypt_method),
ret_user_creds['password'])