From 7d5a4cde7c35daf3866013cfa60f929a1c8d5a43 Mon Sep 17 00:00:00 2001 From: sureshanaparti <12028987+sureshanaparti@users.noreply.github.com> Date: Wed, 21 Oct 2020 17:50:33 +0530 Subject: [PATCH] Updated DB encryption for ScaleIO credentials and Added lock while spooling managed storage template (#103) * Encrypt the ScaleIO storage pool credentials in the DB * Added sync lock while spooling managed storage template --- .../vmsnapshot/ScaleIOVMSnapshotStrategy.java | 7 +- .../storage/volume/VolumeServiceImpl.java | 73 +++++++++++-------- .../driver/ScaleIOPrimaryDataStoreDriver.java | 7 +- .../ScaleIOPrimaryDataStoreLifeCycle.java | 28 ++++--- .../provider/ScaleIOHostListener.java | 9 ++- .../ScaleIOPrimaryDataStoreLifeCycleTest.java | 11 ++- 6 files changed, 84 insertions(+), 51 deletions(-) diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java index 60e2b2c5f27..b1fcf5c1622 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java @@ -53,6 +53,7 @@ import com.cloud.storage.dao.VolumeDao; import com.cloud.uservm.UserVm; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.ManagerBase; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.DB; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; @@ -477,8 +478,10 @@ public class ScaleIOVMSnapshotStrategy extends ManagerBase implements VMSnapshot private ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) throws Exception { final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.valueIn(storagePoolId); final String url = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_ENDPOINT).getValue(); - final String username = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_USERNAME).getValue(); - final String password = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD).getValue(); + final String encryptedUsername = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_USERNAME).getValue(); + final String username = DBEncryptionUtil.decrypt(encryptedUsername); + final String encryptedPassword = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD).getValue(); + final String password = DBEncryptionUtil.decrypt(encryptedPassword); return ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); } } diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index c4c25b681ca..664ba62e8cf 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -837,6 +837,9 @@ public class VolumeServiceImpl implements VolumeService { if (templatePoolRef == null) { throw new CloudRuntimeException("Failed to find template " + srcTemplateInfo.getUniqueName() + " in storage pool " + destPrimaryDataStore.getId()); + } else if (templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) { + // Template already exists + return templateOnPrimary; } // At this point, we have an entry in the DB that points to our cached template. @@ -852,13 +855,6 @@ public class VolumeServiceImpl implements VolumeService { throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId); } - // Template already exists - if (templatePoolRef.getState() == ObjectInDataStoreStateMachine.State.Ready) { - _tmpltPoolDao.releaseFromLockTable(templatePoolRefId); - - return templateOnPrimary; - } - try { // create a cache volume on the back-end @@ -908,22 +904,20 @@ public class VolumeServiceImpl implements VolumeService { int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600); long templatePoolRefId = templatePoolRef.getId(); - templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds); - - if (templatePoolRef == null) { - throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId); - } - - if (templatePoolRef.getDownloadState() == Status.DOWNLOADED) { - // There can be cases where we acquired the lock, but the template - // was already copied by a previous thread. Just return in that case. - - s_logger.debug("Template already downloaded, nothing to do"); - - return; - } - try { + templatePoolRef = _tmpltPoolDao.acquireInLockTable(templatePoolRefId, storagePoolMaxWaitSeconds); + + if (templatePoolRef == null) { + throw new CloudRuntimeException("Unable to acquire lock on VMTemplateStoragePool: " + templatePoolRefId); + } + + if (templatePoolRef.getDownloadState() == Status.DOWNLOADED) { + // There can be cases where we acquired the lock, but the template + // was already copied by a previous thread. Just return in that case. + s_logger.debug("Template already downloaded, nothing to do"); + return; + } + // copy the template from sec storage to the created volume CreateBaseImageContext copyContext = new CreateBaseImageContext<>(null, null, destPrimaryDataStore, srcTemplateInfo, copyTemplateFuture, templateOnPrimary, templatePoolRefId); @@ -1236,16 +1230,7 @@ public class VolumeServiceImpl implements VolumeService { throw new CloudRuntimeException("Destination host should not be null."); } - PrimaryDataStore destPrimaryDataStore = dataStoreMgr.getPrimaryDataStore(destDataStoreId); - - // Check if template exists on the storage pool. If not, downland and copy to managed storage pool - VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destDataStoreId, srcTemplateId); - if (templatePoolRef != null && templatePoolRef.getDownloadState() == Status.DOWNLOADED) { - return tmplFactory.getTemplate(srcTemplateId, destPrimaryDataStore); - } - TemplateInfo srcTemplateInfo = tmplFactory.getTemplate(srcTemplateId); - if (srcTemplateInfo == null) { throw new CloudRuntimeException("Failed to get info of template: " + srcTemplateId); } @@ -1254,8 +1239,29 @@ public class VolumeServiceImpl implements VolumeService { throw new CloudRuntimeException("Unsupported format: " + Storage.ImageFormat.ISO.toString() + " for managed storage template"); } + GlobalLock lock = null; TemplateInfo templateOnPrimary = null; try { + String templateIdManagedPoolIdLockString = "templateId:" + srcTemplateId + "managedPoolId:" + destDataStoreId; + lock = GlobalLock.getInternLock(templateIdManagedPoolIdLockString); + if (lock == null) { + throw new CloudRuntimeException("Unable to create managed storage template, couldn't get global lock on " + templateIdManagedPoolIdLockString); + } + + int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600); + if (!lock.lock(storagePoolMaxWaitSeconds)) { + s_logger.debug("Unable to create managed storage template, couldn't lock on " + templateIdManagedPoolIdLockString); + throw new CloudRuntimeException("Unable to create managed storage template, couldn't lock on " + templateIdManagedPoolIdLockString); + } + + PrimaryDataStore destPrimaryDataStore = dataStoreMgr.getPrimaryDataStore(destDataStoreId); + + // Check if template exists on the storage pool. If not, downland and copy to managed storage pool + VMTemplateStoragePoolVO templatePoolRef = _tmpltPoolDao.findByPoolTemplate(destDataStoreId, srcTemplateId); + if (templatePoolRef != null && templatePoolRef.getDownloadState() == Status.DOWNLOADED) { + return tmplFactory.getTemplate(srcTemplateId, destPrimaryDataStore); + } + templateOnPrimary = createManagedTemplateVolume(srcTemplateInfo, destPrimaryDataStore); if (templateOnPrimary == null) { throw new CloudRuntimeException("Failed to create template " + srcTemplateInfo.getUniqueName() + " on primary storage: " + destDataStoreId); @@ -1311,6 +1317,11 @@ public class VolumeServiceImpl implements VolumeService { } throw new CloudRuntimeException(e.getMessage()); + } finally { + if (lock != null) { + lock.unlock(); + lock.releaseRef(); + } } } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java index 2d3e33b0d97..de405356f63 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/driver/ScaleIOPrimaryDataStoreDriver.java @@ -73,6 +73,7 @@ import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; import com.cloud.utils.Pair; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachineManager; import com.google.common.base.Preconditions; @@ -105,8 +106,10 @@ public class ScaleIOPrimaryDataStoreDriver implements PrimaryDataStoreDriver { private ScaleIOGatewayClient getScaleIOClient(final Long storagePoolId) throws Exception { final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.valueIn(storagePoolId); final String url = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_ENDPOINT).getValue(); - final String username = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_USERNAME).getValue(); - final String password = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD).getValue(); + final String encryptedUsername = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_USERNAME).getValue(); + final String username = DBEncryptionUtil.decrypt(encryptedUsername); + final String encryptedPassword = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.GATEWAY_API_PASSWORD).getValue(); + final String password = DBEncryptionUtil.decrypt(encryptedPassword); return ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); } diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java index 47bf1283711..023d9310673 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycle.java @@ -69,6 +69,7 @@ import com.cloud.storage.VMTemplateStorageResourceAssoc; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.template.TemplateManager; import com.cloud.utils.UriUtils; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.exception.CloudRuntimeException; public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCycle { @@ -100,7 +101,8 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc private org.apache.cloudstack.storage.datastore.api.StoragePool findStoragePool(String url, String username, String password, String storagePoolName) { try { - ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, 60); + final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.value(); + ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); List storagePools = client.listStoragePools(); for (org.apache.cloudstack.storage.datastore.api.StoragePool pool : storagePools) { if (pool.getName().equals(storagePoolName)) { @@ -212,8 +214,8 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc } details.put(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT, gatewayApiURL); - details.put(ScaleIOGatewayClient.GATEWAY_API_USERNAME, gatewayUsername); - details.put(ScaleIOGatewayClient.GATEWAY_API_PASSWORD, gatewayPassword); + details.put(ScaleIOGatewayClient.GATEWAY_API_USERNAME, DBEncryptionUtil.encrypt(gatewayUsername)); + details.put(ScaleIOGatewayClient.GATEWAY_API_PASSWORD, DBEncryptionUtil.encrypt(gatewayPassword)); details.put(ScaleIOGatewayClient.STORAGE_POOL_NAME, storagePoolName); details.put(ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID, scaleIOPool.getSystemId()); parameters.setDetails(details); @@ -231,10 +233,13 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc List connectedSdcIps = null; try { Map dataStoreDetails = primaryDataStoreDao.getDetails(dataStore.getId()); - String url = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT); - String username = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_USERNAME); - String password = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_PASSWORD); - ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, 60); + final String url = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT); + final String encryptedUsername = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_USERNAME); + final String username = DBEncryptionUtil.decrypt(encryptedUsername); + final String encryptedPassword = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_PASSWORD); + final String password = DBEncryptionUtil.decrypt(encryptedPassword); + final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.value(); + ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); connectedSdcIps = client.listConnectedSdcIps(); } catch (NoSuchAlgorithmException | KeyManagementException | URISyntaxException e) { LOGGER.error("Failed to create storage pool", e); @@ -293,9 +298,12 @@ public class ScaleIOPrimaryDataStoreLifeCycle implements PrimaryDataStoreLifeCyc try { Map dataStoreDetails = primaryDataStoreDao.getDetails(dataStore.getId()); String url = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT); - String username = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_USERNAME); - String password = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_PASSWORD); - ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, 60); + String encryptedUsername = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_USERNAME); + final String username = DBEncryptionUtil.decrypt(encryptedUsername); + String encryptedPassword = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_PASSWORD); + final String password = DBEncryptionUtil.decrypt(encryptedPassword); + final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.value(); + ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); connectedSdcIps = client.listConnectedSdcIps(); } catch (NoSuchAlgorithmException | KeyManagementException | URISyntaxException e) { LOGGER.error("Failed to create storage pool", e); diff --git a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java index 8096d094189..e27f8bd2608 100644 --- a/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java +++ b/plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/provider/ScaleIOHostListener.java @@ -43,6 +43,7 @@ import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.exception.CloudRuntimeException; public class ScaleIOHostListener implements HypervisorHostListener { @@ -90,9 +91,11 @@ public class ScaleIOHostListener implements HypervisorHostListener { private boolean isHostSdcConnected(String hostIpAddress, long poolId) { try { Map dataStoreDetails = _primaryDataStoreDao.getDetails(poolId); - String url = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT); - String username = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_USERNAME); - String password = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_PASSWORD); + final String url = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT); + final String encryptedUsername = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_USERNAME); + final String username = DBEncryptionUtil.decrypt(encryptedUsername); + final String encryptedPassword = dataStoreDetails.get(ScaleIOGatewayClient.GATEWAY_API_PASSWORD); + final String password = DBEncryptionUtil.decrypt(encryptedPassword); final int clientTimeout = StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.valueIn(poolId); ScaleIOGatewayClient client = ScaleIOGatewayClient.getClient(url, username, password, false, clientTimeout); return client.isSdcConnected(hostIpAddress); diff --git a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java index efe2c1dc64c..c62371f4c24 100644 --- a/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java +++ b/plugins/storage/volume/scaleio/src/test/java/org/apache/cloudstack/storage/datastore/lifecycle/ScaleIOPrimaryDataStoreLifeCycleTest.java @@ -80,6 +80,7 @@ import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.dao.StoragePoolHostDao; import com.cloud.template.TemplateManager; +import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.exception.CloudRuntimeException; @PrepareForTest(ScaleIOGatewayClient.class) @@ -136,13 +137,17 @@ public class ScaleIOPrimaryDataStoreLifeCycleTest { Map mockDataStoreDetails = new HashMap<>(); mockDataStoreDetails.put(ScaleIOGatewayClient.GATEWAY_API_ENDPOINT, "https://192.168.1.19/api"); - mockDataStoreDetails.put(ScaleIOGatewayClient.GATEWAY_API_USERNAME, "root"); - mockDataStoreDetails.put(ScaleIOGatewayClient.GATEWAY_API_PASSWORD, "Password@123"); + String encryptedUsername = DBEncryptionUtil.encrypt("root"); + mockDataStoreDetails.put(ScaleIOGatewayClient.GATEWAY_API_USERNAME, encryptedUsername); + String encryptedPassword = DBEncryptionUtil.encrypt("Password@123"); + mockDataStoreDetails.put(ScaleIOGatewayClient.GATEWAY_API_PASSWORD, encryptedPassword); when(primaryDataStoreDao.getDetails(1L)).thenReturn(mockDataStoreDetails); PowerMockito.mockStatic(ScaleIOGatewayClient.class); ScaleIOGatewayClientImpl client = mock(ScaleIOGatewayClientImpl.class); - when(ScaleIOGatewayClient.getClient("https://192.168.1.19/api", "root", "Password@123", false, 60)).thenReturn(client); + String username = DBEncryptionUtil.decrypt(encryptedUsername); + String password = DBEncryptionUtil.decrypt(encryptedPassword); + when(ScaleIOGatewayClient.getClient("https://192.168.1.19/api", username, password, false, 60)).thenReturn(client); List connectedSdcIps = new ArrayList<>(); connectedSdcIps.add("192.168.1.1");