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

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

commit fc3c625beba9699054f469b7cc69431924692b95
Author: Wei Zhou <weiz...@apache.org>
AuthorDate: Thu Mar 21 18:51:49 2024 +0100

    server: fix security issues caused by extraconfig on KVM
    
    - Move allow.additional.vm.configuration.list.kvm from Global to Account 
setting
    - Disallow VM details start with "extraconfig" when deploy VMs
    - Skip changes on VM details start with "extraconfig" when update VM 
settings
    - Allow only extraconfig for DPDK in service offering details
    - Check if extraconfig values in vm details are supported when start VMs
    - Check if extraconfig values in service offering details are supported 
when start VMs
    - Disallow add/edit/update VM setting for extraconfig on UI
    
    (cherry picked from commit e6e4fe16fb1ee428c3664b6b57384514e5a9252e)
    Signed-off-by: Rohit Yadav <rohit.ya...@shapeblue.com>
    (cherry picked from commit 7aea9db1c8d8ef1febb723913dc01c3040641425)
    Signed-off-by: Rohit Yadav <rohit.ya...@shapeblue.com>
---
 .../cloud/configuration/ConfigurationManager.java  |  2 ++
 .../configuration/ConfigurationManagerImpl.java    | 10 ++++++
 .../com/cloud/hypervisor/HypervisorGuruBase.java   | 15 ++++++--
 .../src/main/java/com/cloud/vm/UserVmManager.java  |  3 ++
 .../main/java/com/cloud/vm/UserVmManagerImpl.java  | 41 +++++++++++++++++-----
 .../cloud/vpc/MockConfigurationManagerImpl.java    |  5 +++
 ui/public/locales/en.json                          |  1 +
 ui/src/components/view/DetailSettings.vue          | 13 +++++--
 8 files changed, 77 insertions(+), 13 deletions(-)

diff --git 
a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
 
b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
index 5343fb632b5..0232d07b8be 100644
--- 
a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
+++ 
b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java
@@ -281,4 +281,6 @@ public interface ConfigurationManager {
     Pair<String, String> getConfigurationGroupAndSubGroup(String configName);
 
     List<ConfigurationSubGroupVO> getConfigurationSubGroups(Long groupId);
+
+    void validateExtraConfigInServiceOfferingDetail(String detailName);
 }
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 3dbab7d5b68..b47215e255f 100644
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -201,6 +201,7 @@ import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
 import com.cloud.host.dao.HostTagsDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.hypervisor.kvm.dpdk.DpdkHelper;
 import com.cloud.network.IpAddress;
 import com.cloud.network.IpAddressManager;
 import com.cloud.network.Ipv6GuestPrefixSubnetNetworkMapVO;
@@ -3244,6 +3245,7 @@ public class ConfigurationManagerImpl extends ManagerBase 
implements Configurati
                     }
                 }
                 if 
(detailEntry.getKey().startsWith(ApiConstants.EXTRA_CONFIG)) {
+                    
validateExtraConfigInServiceOfferingDetail(detailEntry.getKey());
                     try {
                         detailEntryValue = 
URLDecoder.decode(detailEntry.getValue(), "UTF-8");
                     } catch (UnsupportedEncodingException | 
IllegalArgumentException e) {
@@ -3309,6 +3311,14 @@ public class ConfigurationManagerImpl extends 
ManagerBase implements Configurati
         }
     }
 
+    @Override
+    public void validateExtraConfigInServiceOfferingDetail(String detailName) {
+        if (!detailName.equals(DpdkHelper.DPDK_NUMA) && 
!detailName.equals(DpdkHelper.DPDK_HUGE_PAGES)
+                && !detailName.startsWith(DpdkHelper.DPDK_INTERFACE_PREFIX)) {
+            throw new InvalidParameterValueException("Only extraconfig for 
DPDK are supported in service offering details");
+        }
+    }
+
     private DiskOfferingVO createDiskOfferingInternal(final long userId, final 
boolean isSystem, final VirtualMachine.Type vmType,
                                                       final String name, final 
Integer cpu, final Integer ramSize, final Integer speed, final String 
displayText, final ProvisioningType typedProvisioningType, final boolean 
localStorageRequired,
                                                       final boolean offerHA, 
final boolean limitResourceUse, final boolean volatileVm, String tags, final 
List<Long> domainIds, List<Long> zoneIds, final String hostTag,
diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java 
b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
index 177fd331dee..74d9130cc04 100644
--- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
+++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
@@ -38,6 +38,7 @@ import com.cloud.agent.api.Command;
 import com.cloud.agent.api.to.DiskTO;
 import com.cloud.agent.api.to.NicTO;
 import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.configuration.ConfigurationManager;
 import com.cloud.gpu.GPU;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
@@ -60,6 +61,7 @@ import com.cloud.utils.Pair;
 import com.cloud.utils.component.AdapterBase;
 import com.cloud.vm.NicProfile;
 import com.cloud.vm.NicVO;
+import com.cloud.vm.UserVmManager;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
@@ -97,6 +99,10 @@ public abstract class HypervisorGuruBase extends AdapterBase 
implements Hypervis
     @Inject
     protected
     HostDao hostDao;
+    @Inject
+    private UserVmManager userVmManager;
+    @Inject
+    private ConfigurationManager configurationManager;
 
     public static ConfigKey<Boolean> 
VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor = new 
ConfigKey<Boolean>("Advanced", Boolean.class, 
"vm.min.memory.equals.memory.divided.by.mem.overprovisioning.factor", "true",
             "If we set this to 'true', a minimum memory (memory/ 
mem.overprovisioning.factor) will be set to the VM, independent of using a 
scalable service offering or not.", true, ConfigKey.Scope.Cluster);
@@ -181,10 +187,12 @@ public abstract class HypervisorGuruBase extends 
AdapterBase implements Hypervis
     /**
      * Add extra configuration from VM details. Extra configuration is stored 
as details starting with 'extraconfig'
      */
-    private void addExtraConfig(Map<String, String> details, VirtualMachineTO 
to) {
+    private void addExtraConfig(Map<String, String> details, VirtualMachineTO 
to, long accountId, Hypervisor.HypervisorType hypervisorType) {
         for (String key : details.keySet()) {
             if (key.startsWith(ApiConstants.EXTRA_CONFIG)) {
-                to.addExtraConfig(key, details.get(key));
+                String extraConfig = details.get(key);
+                userVmManager.validateExtraConfig(accountId, hypervisorType, 
extraConfig);
+                to.addExtraConfig(key, extraConfig);
             }
         }
     }
@@ -200,6 +208,7 @@ public abstract class HypervisorGuruBase extends 
AdapterBase implements Hypervis
         if (CollectionUtils.isNotEmpty(details)) {
             for (ServiceOfferingDetailsVO detail : details) {
                 if (detail.getName().startsWith(ApiConstants.EXTRA_CONFIG)) {
+                    
configurationManager.validateExtraConfigInServiceOfferingDetail(detail.getName());
                     to.addExtraConfig(detail.getName(), detail.getValue());
                 }
             }
@@ -263,7 +272,7 @@ public abstract class HypervisorGuruBase extends 
AdapterBase implements Hypervis
         Map<String, String> detailsInVm = 
_userVmDetailsDao.listDetailsKeyPairs(vm.getId());
         if (detailsInVm != null) {
             to.setDetails(detailsInVm);
-            addExtraConfig(detailsInVm, to);
+            addExtraConfig(detailsInVm, to, vm.getAccountId(), 
vm.getHypervisorType());
         }
 
         addServiceOfferingExtraConfiguration(offering, to);
diff --git a/server/src/main/java/com/cloud/vm/UserVmManager.java 
b/server/src/main/java/com/cloud/vm/UserVmManager.java
index 4f1396913cc..b107a520205 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManager.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManager.java
@@ -30,6 +30,7 @@ import com.cloud.exception.ManagementServerException;
 import com.cloud.exception.ResourceAllocationException;
 import com.cloud.exception.ResourceUnavailableException;
 import com.cloud.exception.VirtualMachineMigrationException;
+import com.cloud.hypervisor.Hypervisor.HypervisorType;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.service.ServiceOfferingVO;
 import com.cloud.storage.Storage.StoragePoolType;
@@ -96,6 +97,8 @@ public interface UserVmManager extends UserVmService {
 
     String validateUserData(String userData, HTTPMethod httpmethod);
 
+    void validateExtraConfig(long accountId, HypervisorType hypervisorType, 
String extraConfig);
+
     boolean isVMUsingLocalStorage(VMInstanceVO vm);
 
     boolean expunge(UserVmVO vm);
diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java 
b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
index 0d3f047809a..ae0d66ee482 100644
--- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
+++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
@@ -645,7 +645,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             "enable.additional.vm.configuration", "false", "allow additional 
arbitrary configuration to vm", true, ConfigKey.Scope.Account);
 
     private static final ConfigKey<String> KvmAdditionalConfigAllowList = new 
ConfigKey<>(String.class,
-    "allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma 
separated list of allowed additional configuration options.", true, 
ConfigKey.Scope.Global, null, null, EnableAdditionalVmConfig.key(), null, null, 
ConfigKey.Kind.CSV, null);
+    "allow.additional.vm.configuration.list.kvm", "Advanced", "", "Comma 
separated list of allowed additional configuration options.", true, 
ConfigKey.Scope.Account, null, null, EnableAdditionalVmConfig.key(), null, 
null, ConfigKey.Kind.CSV, null);
 
     private static final ConfigKey<String> XenServerAdditionalConfigAllowList 
= new ConfigKey<>(String.class,
     "allow.additional.vm.configuration.list.xenserver", "Advanced", "", "Comma 
separated list of allowed additional configuration options", true, 
ConfigKey.Scope.Global, null, null, EnableAdditionalVmConfig.key(), null, null, 
ConfigKey.Kind.CSV, null);
@@ -2796,14 +2796,15 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
         if (cleanupDetails){
             if (caller != null && caller.getType() == Account.Type.ADMIN) {
                 for (final UserVmDetailVO detail : existingDetails) {
-                    if (detail != null && detail.isDisplay()) {
+                    if (detail != null && detail.isDisplay() && 
!isExtraConfig(detail.getName())) {
                         userVmDetailsDao.removeDetail(id, detail.getName());
                     }
                 }
             } else {
                 for (final UserVmDetailVO detail : existingDetails) {
                     if (detail != null && 
!userDenyListedSettings.contains(detail.getName())
-                            && 
!userReadOnlySettings.contains(detail.getName()) && detail.isDisplay()) {
+                            && 
!userReadOnlySettings.contains(detail.getName()) && detail.isDisplay()
+                            && !isExtraConfig(detail.getName())) {
                         userVmDetailsDao.removeDetail(id, detail.getName());
                     }
                 }
@@ -2814,6 +2815,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                     throw new InvalidParameterValueException("'extraconfig' 
should not be included in details as key");
                 }
 
+                details.entrySet().removeIf(detail -> 
isExtraConfig(detail.getKey()));
+
                 if (caller != null && caller.getType() != Account.Type.ADMIN) {
                     // Ensure denied or read-only detail is not passed by 
non-root-admin user
                     for (final String detailName : details.keySet()) {
@@ -2837,7 +2840,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
 
                 // ensure details marked as non-displayable are maintained, 
regardless of admin or not
                 for (final UserVmDetailVO existingDetail : existingDetails) {
-                    if (!existingDetail.isDisplay()) {
+                    if (!existingDetail.isDisplay() || 
isExtraConfig(existingDetail.getName())) {
                         details.put(existingDetail.getName(), 
existingDetail.getValue());
                     }
                 }
@@ -2859,6 +2862,10 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                 cmd.getHttpMethod(), cmd.getCustomId(), hostName, 
cmd.getInstanceName(), securityGroupIdList, cmd.getDhcpOptionsMap());
     }
 
+    private boolean isExtraConfig(String detailName) {
+        return detailName != null && 
detailName.startsWith(ApiConstants.EXTRA_CONFIG);
+    }
+
     protected void updateDisplayVmFlag(Boolean isDisplayVm, Long id, UserVmVO 
vmInstance) {
         vmInstance.setDisplayVm(isDisplayVm);
 
@@ -6173,7 +6180,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
      */
     protected void persistExtraConfigKvm(String decodedUrl, UserVm vm) {
         // validate config against denied cfg commands
-        validateKvmExtraConfig(decodedUrl);
+        validateKvmExtraConfig(decodedUrl, vm.getAccountId());
         String[] extraConfigs = decodedUrl.split("\n\n");
         for (String cfg : extraConfigs) {
             int i = 1;
@@ -6191,6 +6198,18 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             i++;
         }
     }
+    /**
+     * This method is used to validate if extra config is valid
+     */
+    @Override
+    public void validateExtraConfig(long accountId, HypervisorType 
hypervisorType, String extraConfig) {
+        if (!EnableAdditionalVmConfig.valueIn(accountId)) {
+            throw new CloudRuntimeException("Additional VM configuration is 
not enabled for this account");
+        }
+        if (HypervisorType.KVM.equals(hypervisorType)) {
+            validateKvmExtraConfig(extraConfig, accountId);
+        }
+    }
 
     /**
      * This method is called by the persistExtraConfigKvm
@@ -6198,8 +6217,8 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
      * controlled by Root admin
      * @param decodedUrl string containing xml configuration to be validated
      */
-    protected void validateKvmExtraConfig(String decodedUrl) {
-        String[] allowedConfigOptionList = 
KvmAdditionalConfigAllowList.value().split(",");
+    protected void validateKvmExtraConfig(String decodedUrl, long accountId) {
+        String[] allowedConfigOptionList = 
KvmAdditionalConfigAllowList.valueIn(accountId).split(",");
         // Skip allowed keys validation for DPDK
         if (!decodedUrl.contains(":")) {
             try {
@@ -6219,7 +6238,7 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
                         }
                     }
                     if (!isValidConfig) {
-                        throw new CloudRuntimeException(String.format("Extra 
config %s is not on the list of allowed keys for KVM hypervisor hosts", 
currentConfig));
+                        throw new CloudRuntimeException(String.format("Extra 
config '%s' is not on the list of allowed keys for KVM hypervisor hosts", 
currentConfig));
                     }
                 }
             } catch (ParserConfigurationException | IOException | SAXException 
e) {
@@ -6321,6 +6340,12 @@ public class UserVmManagerImpl extends ManagerBase 
implements UserVmManager, Vir
             if (details.containsKey("extraconfig")) {
                 throw new InvalidParameterValueException("'extraconfig' should 
not be included in details as key");
             }
+
+            for (String detailName : details.keySet()) {
+                if (isExtraConfig(detailName)) {
+                    throw new InvalidParameterValueException("detail name 
should not start with extraconfig");
+                }
+            }
         }
     }
 
diff --git 
a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java 
b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
index 1d9b65afd1b..8c6e73fcf70 100644
--- a/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
+++ b/server/src/test/java/com/cloud/vpc/MockConfigurationManagerImpl.java
@@ -677,4 +677,9 @@ public class MockConfigurationManagerImpl extends 
ManagerBase implements Configu
         // TODO Auto-generated method stub
         return null;
     }
+
+    @Override
+    public void validateExtraConfigInServiceOfferingDetail(String detailName) {
+        // TODO Auto-generated method stub
+    }
 }
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index 17a70b423b3..245e9efc23f 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -13,6 +13,7 @@
 "error.release.dedicate.host": "Failed to release dedicated host.",
 "error.release.dedicate.pod": "Failed to release dedicated pod.",
 "error.release.dedicate.zone": "Failed to release dedicated zone.",
+"error.unable.to.add.setting.extraconfig": "It is not allowed to add setting 
for extraconfig. Please update VirtualMachine with extraconfig parameter.",
 "error.unable.to.proceed": "Unable to proceed. Please contact your 
administrator.",
 "firewall.close": "Firewall",
 "icmp.code.desc": "Please specify -1 if you want to allow all ICMP codes.",
diff --git a/ui/src/components/view/DetailSettings.vue 
b/ui/src/components/view/DetailSettings.vue
index ba96ad6ee8b..ba9f61860e4 100644
--- a/ui/src/components/view/DetailSettings.vue
+++ b/ui/src/components/view/DetailSettings.vue
@@ -101,7 +101,7 @@
             <tooltip-button
               :tooltip="$t('label.edit')"
               icon="edit-outlined"
-              :disabled="deployasistemplate === true"
+              :disabled="deployasistemplate === true || 
item.name.startsWith('extraconfig')"
               v-if="!item.edit"
               @onClick="showEditDetail(index)" />
           </div>
@@ -115,7 +115,12 @@
               :cancelText="$t('label.no')"
               placement="left"
             >
-              <tooltip-button :tooltip="$t('label.delete')" 
:disabled="deployasistemplate === true" type="primary" :danger="true" 
icon="delete-outlined" />
+              <tooltip-button
+                :tooltip="$t('label.delete')"
+                :disabled="deployasistemplate === true || 
item.name.startsWith('extraconfig')"
+                type="primary"
+                :danger="true"
+                icon="delete-outlined" />
             </a-popconfirm>
           </div>
         </template>
@@ -307,6 +312,10 @@ export default {
         this.error = this.$t('message.error.provide.setting')
         return
       }
+      if (this.newKey.startsWith('extraconfig')) {
+        this.error = this.$t('error.unable.to.add.setting.extraconfig')
+        return
+      }
       if (!this.allowEditOfDetail(this.newKey)) {
         this.error = this.$t('error.unable.to.proceed')
         return

Reply via email to