diff --git a/glance/common/config.py b/glance/common/config.py index fb8e235210..38aca69ca5 100644 --- a/glance/common/config.py +++ b/glance/common/config.py @@ -64,6 +64,9 @@ common_opts = [ cfg.IntOpt('pydev_worker_debug_port', default=5678, help=_('The port on which a pydev process is listening for ' 'connections.')), + cfg.StrOpt('metadata_encryption_key', secret=True, + help=_('Key used for encrypting sensitive metadata while ' + 'talking to the registry or database.')), ] CONF = cfg.CONF diff --git a/glance/db/__init__.py b/glance/db/__init__.py index 83794a2135..499a3c9314 100644 --- a/glance/db/__init__.py +++ b/glance/db/__init__.py @@ -19,6 +19,7 @@ from oslo.config import cfg +from glance.common import crypt from glance.common import exception import glance.domain import glance.domain.proxy @@ -34,6 +35,7 @@ sql_connection_opt = cfg.StrOpt('sql_connection', CONF = cfg.CONF CONF.register_opt(sql_connection_opt) +CONF.import_opt('metadata_encryption_key', 'glance.common.config') def add_cli_options(): @@ -99,6 +101,10 @@ class ImageRepo(object): # NOTE(markwash) db api requires us to filter deleted if not prop['deleted']: properties[prop['name']] = prop['value'] + locations = db_image['locations'] + if CONF.metadata_encryption_key: + key = CONF.metadata_encryption_key + locations = [crypt.urlsafe_decrypt(key, l) for l in locations] return glance.domain.Image( image_id=db_image['id'], name=db_image['name'], @@ -109,7 +115,7 @@ class ImageRepo(object): min_disk=db_image['min_disk'], min_ram=db_image['min_ram'], protected=db_image['protected'], - locations=db_image['locations'], + locations=locations, checksum=db_image['checksum'], owner=db_image['owner'], disk_format=db_image['disk_format'], @@ -120,6 +126,10 @@ class ImageRepo(object): ) def _format_image_to_db(self, image): + locations = image.locations + if CONF.metadata_encryption_key: + key = CONF.metadata_encryption_key + locations = [crypt.urlsafe_encrypt(key, l) for l in locations] return { 'id': image.image_id, 'name': image.name, @@ -128,7 +138,7 @@ class ImageRepo(object): 'min_disk': image.min_disk, 'min_ram': image.min_ram, 'protected': image.protected, - 'locations': image.locations, + 'locations': locations, 'checksum': image.checksum, 'owner': image.owner, 'disk_format': image.disk_format, diff --git a/glance/db/sqlalchemy/migrate_repo/versions/017_quote_encrypted_swift_credentials.py b/glance/db/sqlalchemy/migrate_repo/versions/017_quote_encrypted_swift_credentials.py index 8b0ec1c91d..22fb690816 100644 --- a/glance/db/sqlalchemy/migrate_repo/versions/017_quote_encrypted_swift_credentials.py +++ b/glance/db/sqlalchemy/migrate_repo/versions/017_quote_encrypted_swift_credentials.py @@ -43,7 +43,7 @@ import glance.store.swift LOG = logging.getLogger(__name__) CONF = cfg.CONF -CONF.import_opt('metadata_encryption_key', 'glance.registry') +CONF.import_opt('metadata_encryption_key', 'glance.common.config') def upgrade(migrate_engine): diff --git a/glance/registry/__init__.py b/glance/registry/__init__.py index 546d5d541a..9e29b41e9c 100644 --- a/glance/registry/__init__.py +++ b/glance/registry/__init__.py @@ -40,7 +40,6 @@ registry_client_opts = [ cfg.StrOpt('registry_client_ca_file'), cfg.BoolOpt('registry_client_insecure', default=False), cfg.IntOpt('registry_client_timeout', default=600), - cfg.StrOpt('metadata_encryption_key', secret=True), ] registry_client_ctx_opts = [ cfg.StrOpt('admin_user', secret=True), @@ -55,6 +54,7 @@ CONF = cfg.CONF CONF.register_opts(registry_addr_opts) CONF.register_opts(registry_client_opts) CONF.register_opts(registry_client_ctx_opts) +CONF.import_opt('metadata_encryption_key', 'glance.common.config') _CLIENT_CREDS = None _CLIENT_HOST = None diff --git a/glance/tests/unit/test_db.py b/glance/tests/unit/test_db.py index 28f2293017..fe7bc4f4d2 100644 --- a/glance/tests/unit/test_db.py +++ b/glance/tests/unit/test_db.py @@ -13,6 +13,9 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo.config import cfg + +from glance.common import crypt from glance.common import exception import glance.context import glance.db @@ -21,6 +24,9 @@ import glance.tests.unit.utils as unit_test_utils import glance.tests.utils as test_utils +CONF = cfg.CONF +CONF.import_opt('metadata_encryption_key', 'glance.common.config') + UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d' UUID2 = 'a85abd86-55b3-4d5b-b0b4-5d0a6e6042fc' UUID3 = '971ec09a-8067-4bc8-a91f-ae3557f1c4c7' @@ -212,6 +218,60 @@ class TestImageRepo(test_utils.BaseTestCase): self.assertRaises(exception.NotFound, self.image_repo.get, UUID1) +class TestEncryptedLocations(test_utils.BaseTestCase): + def setUp(self): + super(TestEncryptedLocations, self).setUp() + self.db = unit_test_utils.FakeDB() + self.db.reset() + self.context = glance.context.RequestContext( + user=USER1, tenant=TENANT1) + self.image_repo = glance.db.ImageRepo(self.context, self.db) + self.image_factory = glance.domain.ImageFactory() + self.crypt_key = '0123456789abcdef' + self.config(metadata_encryption_key=self.crypt_key) + + def test_encrypt_locations_on_add(self): + image = self.image_factory.new_image(UUID1) + image.locations = ['foo', 'bar'] + self.image_repo.add(image) + db_data = self.db.image_get(self.context, UUID1) + self.assertNotEqual(db_data['locations'], ['foo', 'bar']) + decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l) + for l in db_data['locations']] + self.assertEqual(decrypted_locations, ['foo', 'bar']) + + def test_encrypt_locations_on_save(self): + image = self.image_factory.new_image(UUID1) + self.image_repo.add(image) + image.locations = ['foo', 'bar'] + self.image_repo.save(image) + db_data = self.db.image_get(self.context, UUID1) + self.assertNotEqual(db_data['locations'], ['foo', 'bar']) + decrypted_locations = [crypt.urlsafe_decrypt(self.crypt_key, l) + for l in db_data['locations']] + self.assertEqual(decrypted_locations, ['foo', 'bar']) + + def test_decrypt_locations_on_get(self): + encrypted_locations = [crypt.urlsafe_encrypt(self.crypt_key, l) + for l in ['ping', 'pong']] + self.assertNotEqual(encrypted_locations, ['ping', 'pong']) + db_data = _db_fixture(UUID1, owner=TENANT1, + locations=encrypted_locations) + self.db.image_create(None, db_data) + image = self.image_repo.get(UUID1) + self.assertEqual(image.locations, ['ping', 'pong']) + + def test_decrypt_locations_on_list(self): + encrypted_locations = [crypt.urlsafe_encrypt(self.crypt_key, l) + for l in ['ping', 'pong']] + self.assertNotEqual(encrypted_locations, ['ping', 'pong']) + db_data = _db_fixture(UUID1, owner=TENANT1, + locations=encrypted_locations) + self.db.image_create(None, db_data) + image = self.image_repo.list()[0] + self.assertEqual(image.locations, ['ping', 'pong']) + + class TestImageMemberRepo(test_utils.BaseTestCase): def setUp(self): diff --git a/glance/tests/unit/test_migrations.py b/glance/tests/unit/test_migrations.py index 24758e7444..720ec5b607 100644 --- a/glance/tests/unit/test_migrations.py +++ b/glance/tests/unit/test_migrations.py @@ -47,7 +47,7 @@ from glance.tests import utils CONF = cfg.CONF -CONF.import_opt('metadata_encryption_key', 'glance.registry') +CONF.import_opt('metadata_encryption_key', 'glance.common.config') LOG = logging.getLogger(__name__)