From 2f8d557f58d84a8c5a0bea72d786d8eddc99b160 Mon Sep 17 00:00:00 2001 From: Rakesh Date: Mon, 9 Aug 2021 10:44:19 +0200 Subject: [PATCH] api: Change GET/POST request max length of VM user data to 4K/1M (#4737) Currently we can send a default value of 4K/32K for GET/POST request of user data field. Most new browsers and also nginx support till 1MB of post data. Added a new global setting `vm.userdata.max.length` with default value of 32KB which can be increased till 1MB. --- .../cloudstack/api/command/user/vm/DeployVMCmd.java | 4 +++- .../cloudstack/api/command/user/vm/UpdateVMCmd.java | 9 +++++++-- .../schema/src/main/java/com/cloud/vm/UserVmVO.java | 2 +- .../configuration/ConfigurationManagerImpl.java | 13 ++++++++++++- .../main/java/com/cloud/vm/UserVmManagerImpl.java | 13 +++++++++++-- 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index 733bddb4514..12a4a02c026 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -150,7 +150,9 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG + "The parameter is required and respected only when hypervisor info is not set on the ISO/Template passed to the call") private String hypervisor; - @Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. This binary data must be base64 encoded before adding it to the request. Using HTTP GET (via querystring), you can send up to 2KB of data after base64 encoding. Using HTTP POST(via POST body), you can send up to 32K of data after base64 encoding.", length = 32768) + @Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, + description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. This binary data must be base64 encoded before adding it to the request. Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding.", + length = 1048576) private String userData; @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, description = "name of the ssh key pair used to login to the virtual machine") diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java index 38d1a5d5dd4..38289537bc6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java @@ -80,8 +80,13 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction, @Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, - description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. This binary data must be base64 encoded before adding it to the request. Using HTTP GET (via querystring), you can send up to 2KB of data after base64 encoding. Using HTTP POST(via POST body), you can send up to 32K of data after base64 encoding.", - length = 32768) + description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " + + "This binary data must be base64 encoded before adding it to the request. " + + "Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " + + "Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding." + + "You also need to change vm.userdata.max.length value", + length = 1048576, + since = "4.16.0") private String userData; @Parameter(name = ApiConstants.DISPLAY_VM, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the vm to the end user or not.", authorized = {RoleType.Admin}) diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java index e3950340469..f02380a0471 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java @@ -38,7 +38,7 @@ public class UserVmVO extends VMInstanceVO implements UserVm { @Column(name = "iso_id", nullable = true, length = 17) private Long isoId = null; - @Column(name = "user_data", updatable = true, nullable = true, length = 32768) + @Column(name = "user_data", updatable = true, nullable = true, length = 1048576) @Basic(fetch = FetchType.LAZY) private String userData; diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java index d06f7481710..c0b1e2dc1c1 100755 --- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java @@ -411,6 +411,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati protected Set configValuesForValidation; private Set weightBasedParametersForValidation; private Set overprovisioningFactorsForValidation; + public static final String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length"; public static final ConfigKey SystemVMUseLocalStorage = new ConfigKey(Boolean.class, "system.vm.use.local.storage", "Advanced", "false", "Indicates whether to use local storage pools or shared storage pools for system VMs.", false, ConfigKey.Scope.Zone, null); @@ -430,6 +431,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati "Indicates whether the host in down state can be put into maintenance state so thats its not enabled after it comes back.", true, ConfigKey.Scope.Zone, null); + public static final ConfigKey VM_USERDATA_MAX_LENGTH = new ConfigKey("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768", + "Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true); + private static final String IOPS_READ_RATE = "IOPS Read"; private static final String IOPS_WRITE_RATE = "IOPS Write"; private static final String BYTES_READ_RATE = "Bytes Read"; @@ -484,6 +488,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati configValuesForValidation.add(StorageManager.STORAGE_POOL_DISK_WAIT.key()); configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.key()); configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.key()); + configValuesForValidation.add(VM_USERDATA_MAX_LENGTH_STRING); } private void weightBasedParametersForValidation() { @@ -958,6 +963,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati throw new InvalidParameterValueException("Please enter a value less than 257 for the configuration parameter:" + name); } } + if (VM_USERDATA_MAX_LENGTH_STRING.equalsIgnoreCase(name)) { + if (val > 1048576) { + throw new InvalidParameterValueException("Please enter a value less than 1048576 for the configuration parameter:" + name); + } + } } catch (final NumberFormatException e) { s_logger.error("There was an error trying to parse the integer value for:" + name); throw new InvalidParameterValueException("There was an error trying to parse the integer value for:" + name); @@ -6519,6 +6529,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[] {SystemVMUseLocalStorage, IOPS_MAX_READ_LENGTH, IOPS_MAX_WRITE_LENGTH, - BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE}; + BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE, + VM_USERDATA_MAX_LENGTH}; } } diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 4bd80dadb9c..f18e7b39996 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -341,6 +341,8 @@ import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; +import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH; + public class UserVmManagerImpl extends ManagerBase implements UserVmManager, VirtualMachineGuru, UserVmService, Configurable { private static final Logger s_logger = Logger.getLogger(UserVmManagerImpl.class); @@ -541,7 +543,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir private Map vmIdCountMap = new ConcurrentHashMap<>(); private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES; - private static final int MAX_HTTP_POST_LENGTH = 16 * MAX_USER_DATA_LENGTH_BYTES; + private static final int NUM_OF_2K_BLOCKS = 512; + private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES; @Inject private OrchestrationService _orchSrvc; @@ -4471,11 +4474,14 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (!Base64.isBase64(userData)) { throw new InvalidParameterValueException("User data is not base64 encoded"); } - // If GET, use 4K. If POST, support upto 32K. + // If GET, use 4K. If POST, support up to 1M. if (httpmethod.equals(HTTPMethod.GET)) { if (userData.length() >= MAX_HTTP_GET_LENGTH) { throw new InvalidParameterValueException("User data is too long for an http GET request"); } + if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) { + throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value()); + } decodedUserData = Base64.decodeBase64(userData.getBytes()); if (decodedUserData.length > MAX_HTTP_GET_LENGTH) { throw new InvalidParameterValueException("User data is too long for GET request"); @@ -4484,6 +4490,9 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir if (userData.length() >= MAX_HTTP_POST_LENGTH) { throw new InvalidParameterValueException("User data is too long for an http POST request"); } + if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) { + throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value()); + } decodedUserData = Base64.decodeBase64(userData.getBytes()); if (decodedUserData.length > MAX_HTTP_POST_LENGTH) { throw new InvalidParameterValueException("User data is too long for POST request");