This is an automated email from the ASF dual-hosted git repository.

dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/main by this push:
     new ae5dda867fd Normalize encryption on global configurations values 
(#6812)
ae5dda867fd is described below

commit ae5dda867fdd9535d97086a96668d284c5d3fd03
Author: Bryan Lima <[email protected]>
AuthorDate: Thu Sep 28 11:44:36 2023 -0300

    Normalize encryption on global configurations values (#6812)
---
 .../api/command/admin/config/UpdateCfgCmd.java     | 58 ++++++++++++++------
 .../main/java/com/cloud/domain/DomainDetailVO.java |  2 -
 .../com/cloud/upgrade/dao/Upgrade41810to41900.java | 61 ++++++++++++++++++++++
 .../main/java/com/cloud/user/AccountDetailVO.java  |  3 --
 .../framework/config/impl/ConfigurationVO.java     |  2 +-
 .../utils/crypt/EncryptionSecretKeyChanger.java    | 26 +++++++++
 .../configuration/ConfigurationManagerImpl.java    | 33 +++++++++---
 7 files changed, 156 insertions(+), 29 deletions(-)

diff --git 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java
 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java
index abe7e31cfa2..63dc51452f0 100644
--- 
a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java
+++ 
b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java
@@ -16,6 +16,7 @@
 // under the License.
 package org.apache.cloudstack.api.command.admin.config;
 
+import com.cloud.utils.crypt.DBEncryptionUtil;
 import org.apache.cloudstack.acl.RoleService;
 import org.apache.cloudstack.api.response.DomainResponse;
 import org.apache.log4j.Logger;
@@ -150,25 +151,50 @@ public class UpdateCfgCmd extends BaseCmd {
         if (cfg != null) {
             ConfigurationResponse response = 
_responseGenerator.createConfigurationResponse(cfg);
             response.setResponseName(getCommandName());
-            if (getZoneId() != null) {
-                response.setScope("zone");
-            }
-            if (getClusterId() != null) {
-                response.setScope("cluster");
-            }
-            if (getStoragepoolId() != null) {
-                response.setScope("storagepool");
-            }
-            if (getAccountId() != null) {
-                response.setScope("account");
-            }
-            if (getDomainId() != null) {
-                response.setScope("domain");
-            }
-            response.setValue(value);
+            response = setResponseScopes(response);
+            response = setResponseValue(response, cfg);
             this.setResponseObject(response);
         } else {
             throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed 
to update config");
         }
     }
+
+    /**
+     * Sets the configuration value in the response. If the configuration is 
in the `Hidden` or `Secure` categories, the value is encrypted before being set 
in the response.
+     * @param response to be set with the configuration `cfg` value
+     * @param cfg to be used in setting the response value
+     * @return the response with the configuration's value
+     */
+    public ConfigurationResponse setResponseValue(ConfigurationResponse 
response, Configuration cfg) {
+        if (cfg.isEncrypted()) {
+            response.setValue(DBEncryptionUtil.encrypt(getValue()));
+        } else {
+            response.setValue(getValue());
+        }
+        return response;
+    }
+
+    /**
+     * Sets the scope for the Configuration response only if the field is not 
null.
+     * @param response to be updated
+     * @return the response updated with the scopes
+     */
+    public ConfigurationResponse setResponseScopes(ConfigurationResponse 
response) {
+        if (getZoneId() != null) {
+            response.setScope("zone");
+        }
+        if (getClusterId() != null) {
+            response.setScope("cluster");
+        }
+        if (getStoragepoolId() != null) {
+            response.setScope("storagepool");
+        }
+        if (getAccountId() != null) {
+            response.setScope("account");
+        }
+        if (getDomainId() != null) {
+            response.setScope("domain");
+        }
+        return response;
+    }
 }
diff --git a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java 
b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java
index 61eb6cfd28e..df5a2283baa 100644
--- a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java
+++ b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java
@@ -23,7 +23,6 @@ import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Table;
 
-import com.cloud.utils.db.Encrypt;
 import org.apache.cloudstack.api.InternalIdentity;
 
 @Entity
@@ -40,7 +39,6 @@ public class DomainDetailVO implements InternalIdentity {
     @Column(name = "name")
     private String name;
 
-    @Encrypt
     @Column(name = "value")
     private String value;
 
diff --git 
a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java 
b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java
index 57f87d1f2cc..fd44e79e7cf 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java
@@ -17,15 +17,19 @@
 package com.cloud.upgrade.dao;
 
 import com.cloud.upgrade.SystemVmTemplateRegistration;
+import com.cloud.utils.crypt.DBEncryptionUtil;
 import com.cloud.utils.DateUtil;
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.log4j.Logger;
+import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
 
 import java.io.InputStream;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -34,6 +38,10 @@ public class Upgrade41810to41900 implements DbUpgrade, 
DbUpgradeSystemVmTemplate
     final static Logger LOG = Logger.getLogger(Upgrade41810to41900.class);
     private SystemVmTemplateRegistration systemVmTemplateRegistration;
 
+    private static final String ACCOUNT_DETAILS = "account_details";
+
+    private static final String DOMAIN_DETAILS = "domain_details";
+
     private final SimpleDateFormat[] formats = {
             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new 
SimpleDateFormat("MM/dd/yyyy HH:mm:ss"), new SimpleDateFormat("dd/MM/yyyy 
HH:mm:ss"),
             new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy")};
@@ -66,6 +74,7 @@ public class Upgrade41810to41900 implements DbUpgrade, 
DbUpgradeSystemVmTemplate
 
     @Override
     public void performDataMigration(Connection conn) {
+        
decryptConfigurationValuesFromAccountAndDomainScopesNotInSecureHiddenCategories(conn);
         migrateBackupDates(conn);
     }
 
@@ -95,6 +104,37 @@ public class Upgrade41810to41900 implements DbUpgrade, 
DbUpgradeSystemVmTemplate
         }
     }
 
+    protected void 
decryptConfigurationValuesFromAccountAndDomainScopesNotInSecureHiddenCategories(Connection
 conn) {
+        LOG.info("Decrypting global configuration values from the following 
tables: account_details and domain_details.");
+
+        Map<Long, String> accountsMap = getConfigsWithScope(conn, 
ACCOUNT_DETAILS);
+        updateConfigValuesWithScope(conn, accountsMap, ACCOUNT_DETAILS);
+        LOG.info("Successfully decrypted configurations from account_details 
table.");
+
+        Map<Long, String> domainsMap = getConfigsWithScope(conn, 
DOMAIN_DETAILS);
+        updateConfigValuesWithScope(conn, domainsMap, DOMAIN_DETAILS);
+        LOG.info("Successfully decrypted configurations from domain_details 
table.");
+    }
+
+    protected Map<Long, String> getConfigsWithScope(Connection conn, String 
table) {
+        Map<Long, String> configsToBeUpdated = new HashMap<>();
+        String selectDetails = String.format("SELECT details.id, details.value 
from cloud.%s details, cloud.configuration c " +
+                "WHERE details.name = c.name AND c.category NOT IN ('Hidden', 
'Secure') AND details.value <> \"\" ORDER BY details.id;", table);
+
+        try (PreparedStatement pstmt = conn.prepareStatement(selectDetails)) {
+            try (ResultSet result = pstmt.executeQuery()) {
+                while (result.next()) {
+                    configsToBeUpdated.put(result.getLong("id"), 
result.getString("value"));
+                }
+            }
+            return configsToBeUpdated;
+        } catch (SQLException e) {
+            String message = String.format("Unable to retrieve data from table 
[%s] due to [%s].", table, e.getMessage());
+            LOG.error(message, e);
+            throw new CloudRuntimeException(message, e);
+        }
+    }
+
     public void migrateBackupDates(Connection conn) {
         LOG.info("Trying to convert backups' date column from varchar(255) to 
datetime type.");
 
@@ -125,6 +165,27 @@ public class Upgrade41810to41900 implements DbUpgrade, 
DbUpgradeSystemVmTemplate
         }
     }
 
+    protected void updateConfigValuesWithScope(Connection conn, Map<Long, 
String> configsToBeUpdated, String table) {
+        String updateConfigValues = String.format("UPDATE cloud.%s SET value = 
? WHERE id = ?;", table);
+
+        for (Map.Entry<Long, String> config : configsToBeUpdated.entrySet()) {
+            try (PreparedStatement pstmt = 
conn.prepareStatement(updateConfigValues)) {
+                String decryptedValue = 
DBEncryptionUtil.decrypt(config.getValue());
+
+                pstmt.setString(1, decryptedValue);
+                pstmt.setLong(2, config.getKey());
+
+                LOG.info(String.format("Updating config with ID [%s] to value 
[%s].", config.getKey(), decryptedValue));
+                pstmt.executeUpdate();
+            } catch (SQLException | EncryptionOperationNotPossibleException e) 
{
+                String message = String.format("Unable to update config value 
with ID [%s] on table [%s] due to [%s]. The config value may already be 
decrypted.",
+                        config.getKey(), table, e);
+                LOG.error(message);
+                throw new CloudRuntimeException(message, e);
+            }
+        }
+    }
+
     private void fetchDatesAndMigrateToNewColumn(Connection conn) {
         String selectBackupDates = "SELECT `id`, `old_date` FROM 
`cloud`.`backups` WHERE 1;";
         String date;
diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java 
b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java
index 71ad765e618..863f6c96008 100644
--- a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java
+++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java
@@ -25,8 +25,6 @@ import javax.persistence.Table;
 
 import org.apache.cloudstack.api.InternalIdentity;
 
-import com.cloud.utils.db.Encrypt;
-
 @Entity
 @Table(name = "account_details")
 public class AccountDetailVO implements InternalIdentity {
@@ -41,7 +39,6 @@ public class AccountDetailVO implements InternalIdentity {
     @Column(name = "name")
     private String name;
 
-    @Encrypt
     @Column(name = "value", length=4096)
     private String value;
 
diff --git 
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
 
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
index 08ec9bfe83f..c705cc64072 100644
--- 
a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
+++ 
b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java
@@ -170,7 +170,7 @@ public class ConfigurationVO implements Configuration {
 
     @Override
     public boolean isEncrypted() {
-        return "Hidden".equals(getCategory()) || 
"Secure".equals(getCategory());
+        return StringUtils.equalsAny(getCategory(), "Hidden", "Secure");
     }
 
     @Override
diff --git 
a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
 
b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
index 88830b3e3f9..961c537d0da 100644
--- 
a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
+++ 
b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java
@@ -475,6 +475,8 @@ public class EncryptionSecretKeyChanger {
 
             // migrate resource details values
             migrateHostDetails(conn);
+            migrateEncryptedAccountDetails(conn);
+            migrateEncryptedDomainDetails(conn);
             migrateClusterDetails(conn);
             migrateImageStoreDetails(conn);
             migrateStoragePoolDetails(conn);
@@ -497,6 +499,30 @@ public class EncryptionSecretKeyChanger {
         return true;
     }
 
+    private void migrateEncryptedAccountDetails(Connection conn) {
+        System.out.println("Beginning migration of account_details encrypted 
values");
+
+        String tableName = "account_details";
+        String selectSql = "SELECT details.id, details.value from 
account_details details, cloud.configuration c " +
+                "WHERE details.name = c.name AND c.category IN ('Hidden', 
'Secure') AND details.value <> \"\" ORDER BY details.id;";
+        String updateSql = "UPDATE cloud.account_details SET value = ? WHERE 
id = ?;";
+        migrateValueAndUpdateDatabaseById(conn, tableName, selectSql, 
updateSql, false);
+
+        System.out.println("End migration of account details values");
+    }
+
+    private void migrateEncryptedDomainDetails(Connection conn) {
+        System.out.println("Beginning migration of domain_details encrypted 
values");
+
+        String tableName = "domain_details";
+        String selectSql = "SELECT details.id, details.value from 
domain_details details, cloud.configuration c " +
+                "WHERE details.name = c.name AND c.category IN ('Hidden', 
'Secure') AND details.value <> \"\" ORDER BY details.id;";
+        String updateSql = "UPDATE cloud.domain_details SET value = ? WHERE id 
= ?;";
+        migrateValueAndUpdateDatabaseById(conn, tableName, selectSql, 
updateSql, false);
+
+        System.out.println("End migration of domain details values");
+    }
+
     protected String migrateValue(String value) {
         if (StringUtils.isEmpty(value)) {
             return value;
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index ef617ed4cb7..18cebff87fe 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -47,6 +47,7 @@ import javax.naming.ConfigurationException;
 
 
 import com.cloud.hypervisor.HypervisorGuru;
+import com.cloud.utils.crypt.DBEncryptionUtil;
 import org.apache.cloudstack.acl.SecurityChecker;
 import org.apache.cloudstack.affinity.AffinityGroup;
 import org.apache.cloudstack.affinity.AffinityGroupService;
@@ -664,7 +665,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
 
     @Override
     @DB
-    public String updateConfiguration(final long userId, final String name, 
final String category, final String value, final String scope, final Long 
resourceId) {
+    public String updateConfiguration(final long userId, final String name, 
final String category, String value, final String scope, final Long resourceId) 
{
         final String validationMsg = validateConfigurationValue(name, value, 
scope);
 
         if (validationMsg != null) {
@@ -677,6 +678,11 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         // if scope is mentioned as global or not mentioned then it is normal
         // global parameter updation
         if (scope != null && !scope.isEmpty() && 
!ConfigKey.Scope.Global.toString().equalsIgnoreCase(scope)) {
+            boolean valueEncrypted = shouldEncryptValue(category);
+            if (valueEncrypted) {
+                value = DBEncryptionUtil.encrypt(value);
+            }
+
             switch (ConfigKey.Scope.valueOf(scope)) {
             case Zone:
                 final DataCenterVO zone = _zoneDao.findById(resourceId);
@@ -767,7 +773,8 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
             default:
                 throw new InvalidParameterValueException("Scope provided is 
invalid");
             }
-            return value;
+
+            return valueEncrypted ? DBEncryptionUtil.decrypt(value) : value;
         }
 
         // Execute all updates in a single transaction
@@ -864,6 +871,10 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         return _configDao.getValue(name);
     }
 
+    private boolean shouldEncryptValue(String category) {
+        return StringUtils.equalsAny(category, "Hidden", "Secure");
+    }
+
     /**
      * Updates the 'hypervisor.list' value to match the new custom hypervisor 
name set as newValue if the previous value was set
      */
@@ -890,10 +901,11 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         final Long imageStoreId = cmd.getImageStoreId();
         Long accountId = cmd.getAccountId();
         Long domainId = cmd.getDomainId();
-        CallContext.current().setEventDetails(" Name: " + name + " New Value: 
" + (name.toLowerCase().contains("password") ? "*****" : value == null ? "" : 
value));
         // check if config value exists
         final ConfigurationVO config = _configDao.findByName(name);
-        String catergory = null;
+        String category = null;
+        String eventValue = encryptEventValueIfConfigIsEncrypted(config, 
value);
+        CallContext.current().setEventDetails(String.format(" Name: %s New 
Value: %s", name, eventValue));
 
         final Account caller = CallContext.current().getCallingAccount();
         if (_accountMgr.isDomainAdmin(caller.getId())) {
@@ -912,9 +924,9 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                 s_logger.warn("Probably the component manager where 
configuration variable " + name + " is defined needs to implement Configurable 
interface");
                 throw new InvalidParameterValueException("Config parameter 
with name " + name + " doesn't exist");
             }
-            catergory = _configDepot.get(name).category();
+            category = _configDepot.get(name).category();
         } else {
-            catergory = config.getCategory();
+            category = config.getCategory();
         }
 
         validateIpAddressRelatedConfigValues(name, value);
@@ -971,7 +983,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
             value = (id == null) ? null : "";
         }
 
-        final String updatedValue = updateConfiguration(userId, name, 
catergory, value, scope, id);
+        final String updatedValue = updateConfiguration(userId, name, 
category, value, scope, id);
         if (value == null && updatedValue == null || 
updatedValue.equalsIgnoreCase(value)) {
             return _configDao.findByName(name);
         } else {
@@ -979,6 +991,13 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
         }
     }
 
+    private String encryptEventValueIfConfigIsEncrypted(ConfigurationVO 
config, String value) {
+        if (config != null && config.isEncrypted()) {
+           return  "*****";
+        }
+        return Objects.requireNonNullElse(value, "");
+    }
+
     private ParamCountPair getParamCount(Map<String, Long> scopeMap) {
         Long id = null;
         int paramCount = 0;

Reply via email to