Skip to content

Commit 2f8d557

Browse files
authored
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.
1 parent 95de827 commit 2f8d557

File tree

5 files changed

+34
-7
lines changed

5 files changed

+34
-7
lines changed

api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,9 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityG
150150
+ "The parameter is required and respected only when hypervisor info is not set on the ISO/Template passed to the call")
151151
private String hypervisor;
152152

153-
@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)
153+
@Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING,
154+
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.",
155+
length = 1048576)
154156
private String userData;
155157

156158
@Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, description = "name of the ssh key pair used to login to the virtual machine")

api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,13 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction,
8080

8181
@Parameter(name = ApiConstants.USER_DATA,
8282
type = CommandType.STRING,
83-
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.",
84-
length = 32768)
83+
description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " +
84+
"This binary data must be base64 encoded before adding it to the request. " +
85+
"Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " +
86+
"Using HTTP POST(via POST body), you can send up to 1MB of data after base64 encoding." +
87+
"You also need to change vm.userdata.max.length value",
88+
length = 1048576,
89+
since = "4.16.0")
8590
private String userData;
8691

8792
@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})

engine/schema/src/main/java/com/cloud/vm/UserVmVO.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public class UserVmVO extends VMInstanceVO implements UserVm {
3838
@Column(name = "iso_id", nullable = true, length = 17)
3939
private Long isoId = null;
4040

41-
@Column(name = "user_data", updatable = true, nullable = true, length = 32768)
41+
@Column(name = "user_data", updatable = true, nullable = true, length = 1048576)
4242
@Basic(fetch = FetchType.LAZY)
4343
private String userData;
4444

server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
411411
protected Set<String> configValuesForValidation;
412412
private Set<String> weightBasedParametersForValidation;
413413
private Set<String> overprovisioningFactorsForValidation;
414+
public static final String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length";
414415

415416
public static final ConfigKey<Boolean> SystemVMUseLocalStorage = new ConfigKey<Boolean>(Boolean.class, "system.vm.use.local.storage", "Advanced", "false",
416417
"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
430431
"Indicates whether the host in down state can be put into maintenance state so thats its not enabled after it comes back.",
431432
true, ConfigKey.Scope.Zone, null);
432433

434+
public static final ConfigKey<Integer> VM_USERDATA_MAX_LENGTH = new ConfigKey<Integer>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768",
435+
"Max length of vm userdata after base64 decoding. Default is 32768 and maximum is 1048576", true);
436+
433437
private static final String IOPS_READ_RATE = "IOPS Read";
434438
private static final String IOPS_WRITE_RATE = "IOPS Write";
435439
private static final String BYTES_READ_RATE = "Bytes Read";
@@ -484,6 +488,7 @@ private void populateConfigValuesForValidationSet() {
484488
configValuesForValidation.add(StorageManager.STORAGE_POOL_DISK_WAIT.key());
485489
configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_TIMEOUT.key());
486490
configValuesForValidation.add(StorageManager.STORAGE_POOL_CLIENT_MAX_CONNECTIONS.key());
491+
configValuesForValidation.add(VM_USERDATA_MAX_LENGTH_STRING);
487492
}
488493

489494
private void weightBasedParametersForValidation() {
@@ -958,6 +963,11 @@ private String validateConfigurationValue(final String name, String value, final
958963
throw new InvalidParameterValueException("Please enter a value less than 257 for the configuration parameter:" + name);
959964
}
960965
}
966+
if (VM_USERDATA_MAX_LENGTH_STRING.equalsIgnoreCase(name)) {
967+
if (val > 1048576) {
968+
throw new InvalidParameterValueException("Please enter a value less than 1048576 for the configuration parameter:" + name);
969+
}
970+
}
961971
} catch (final NumberFormatException e) {
962972
s_logger.error("There was an error trying to parse the integer value for:" + name);
963973
throw new InvalidParameterValueException("There was an error trying to parse the integer value for:" + name);
@@ -6519,6 +6529,7 @@ public String getConfigComponentName() {
65196529
@Override
65206530
public ConfigKey<?>[] getConfigKeys() {
65216531
return new ConfigKey<?>[] {SystemVMUseLocalStorage, IOPS_MAX_READ_LENGTH, IOPS_MAX_WRITE_LENGTH,
6522-
BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE};
6532+
BYTES_MAX_READ_LENGTH, BYTES_MAX_WRITE_LENGTH, ADD_HOST_ON_SERVICE_RESTART_KVM, SET_HOST_DOWN_TO_MAINTENANCE,
6533+
VM_USERDATA_MAX_LENGTH};
65236534
}
65246535
}

server/src/main/java/com/cloud/vm/UserVmManagerImpl.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@
341341
import com.cloud.vm.snapshot.VMSnapshotVO;
342342
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
343343

344+
import static com.cloud.configuration.ConfigurationManagerImpl.VM_USERDATA_MAX_LENGTH;
345+
344346
public class UserVmManagerImpl extends ManagerBase implements UserVmManager, VirtualMachineGuru, UserVmService, Configurable {
345347
private static final Logger s_logger = Logger.getLogger(UserVmManagerImpl.class);
346348

@@ -541,7 +543,8 @@ public class UserVmManagerImpl extends ManagerBase implements UserVmManager, Vir
541543
private Map<Long, VmAndCountDetails> vmIdCountMap = new ConcurrentHashMap<>();
542544

543545
private static final int MAX_HTTP_GET_LENGTH = 2 * MAX_USER_DATA_LENGTH_BYTES;
544-
private static final int MAX_HTTP_POST_LENGTH = 16 * MAX_USER_DATA_LENGTH_BYTES;
546+
private static final int NUM_OF_2K_BLOCKS = 512;
547+
private static final int MAX_HTTP_POST_LENGTH = NUM_OF_2K_BLOCKS * MAX_USER_DATA_LENGTH_BYTES;
545548

546549
@Inject
547550
private OrchestrationService _orchSrvc;
@@ -4471,11 +4474,14 @@ protected String validateUserData(String userData, HTTPMethod httpmethod) {
44714474
if (!Base64.isBase64(userData)) {
44724475
throw new InvalidParameterValueException("User data is not base64 encoded");
44734476
}
4474-
// If GET, use 4K. If POST, support upto 32K.
4477+
// If GET, use 4K. If POST, support up to 1M.
44754478
if (httpmethod.equals(HTTPMethod.GET)) {
44764479
if (userData.length() >= MAX_HTTP_GET_LENGTH) {
44774480
throw new InvalidParameterValueException("User data is too long for an http GET request");
44784481
}
4482+
if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) {
4483+
throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value());
4484+
}
44794485
decodedUserData = Base64.decodeBase64(userData.getBytes());
44804486
if (decodedUserData.length > MAX_HTTP_GET_LENGTH) {
44814487
throw new InvalidParameterValueException("User data is too long for GET request");
@@ -4484,6 +4490,9 @@ protected String validateUserData(String userData, HTTPMethod httpmethod) {
44844490
if (userData.length() >= MAX_HTTP_POST_LENGTH) {
44854491
throw new InvalidParameterValueException("User data is too long for an http POST request");
44864492
}
4493+
if (userData.length() > VM_USERDATA_MAX_LENGTH.value()) {
4494+
throw new InvalidParameterValueException("User data has exceeded configurable max length : " + VM_USERDATA_MAX_LENGTH.value());
4495+
}
44874496
decodedUserData = Base64.decodeBase64(userData.getBytes());
44884497
if (decodedUserData.length > MAX_HTTP_POST_LENGTH) {
44894498
throw new InvalidParameterValueException("User data is too long for POST request");

0 commit comments

Comments
 (0)