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:
parent
37919c6b95
commit
f67cd21828
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
|
@ -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'),
|
||||
|
|
|
@ -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'])
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue