Skip to content

Commit a26a502

Browse files
marcaureleyadvr
authored andcommitted
CLOUDSTACK-9593: userdata: enforce data is a multiple of 4 characters (#1760)
Python base64 requires that the string is a multiple of 4 characters but the Apache codec does not. RFC states is not mandatory so the data should not fail the VR script (vmdata.py). Signed-off-by: Marc-Aurèle Brothier <m@brothier.org> Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
1 parent 391952d commit a26a502

3 files changed

Lines changed: 60 additions & 3 deletions

File tree

engine/schema/src/com/cloud/upgrade/dao/Upgrade41000to41100.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.Map;
2828
import java.util.Set;
2929

30+
import org.apache.commons.codec.binary.Base64;
3031
import org.apache.log4j.Logger;
3132

3233
import com.cloud.hypervisor.Hypervisor;
@@ -64,9 +65,49 @@ public InputStream[] getPrepareScripts() {
6465

6566
@Override
6667
public void performDataMigration(Connection conn) {
68+
validateUserDataInBase64(conn);
6769
updateSystemVmTemplates(conn);
6870
}
6971

72+
private void validateUserDataInBase64(Connection conn) {
73+
try (final PreparedStatement selectStatement = conn.prepareStatement("SELECT `id`, `user_data` FROM `cloud`.`user_vm` WHERE `user_data` IS NOT NULL;");
74+
final ResultSet selectResultSet = selectStatement.executeQuery()) {
75+
while (selectResultSet.next()) {
76+
final Long userVmId = selectResultSet.getLong(1);
77+
final String userData = selectResultSet.getString(2);
78+
if (Base64.isBase64(userData)) {
79+
final String newUserData = Base64.encodeBase64String(Base64.decodeBase64(userData.getBytes()));
80+
if (!userData.equals(newUserData)) {
81+
try (final PreparedStatement updateStatement = conn.prepareStatement("UPDATE `cloud`.`user_vm` SET `user_data` = ? WHERE `id` = ? ;")) {
82+
updateStatement.setString(1, newUserData);
83+
updateStatement.setLong(2, userVmId);
84+
updateStatement.executeUpdate();
85+
} catch (SQLException e) {
86+
LOG.error("Failed to update cloud.user_vm user_data for id:" + userVmId + " with exception: " + e.getMessage());
87+
throw new CloudRuntimeException("Exception while updating cloud.user_vm for id " + userVmId, e);
88+
}
89+
}
90+
} else {
91+
// Update to NULL since it's invalid
92+
LOG.warn("Removing user_data for vm id " + userVmId + " because it's invalid");
93+
LOG.warn("Removed data was: " + userData);
94+
try (final PreparedStatement updateStatement = conn.prepareStatement("UPDATE `cloud`.`user_vm` SET `user_data` = NULL WHERE `id` = ? ;")) {
95+
updateStatement.setLong(1, userVmId);
96+
updateStatement.executeUpdate();
97+
} catch (SQLException e) {
98+
LOG.error("Failed to update cloud.user_vm user_data for id:" + userVmId + " to NULL with exception: " + e.getMessage());
99+
throw new CloudRuntimeException("Exception while updating cloud.user_vm for id " + userVmId + " to NULL", e);
100+
}
101+
}
102+
}
103+
} catch (SQLException e) {
104+
throw new CloudRuntimeException("Exception while validating existing user_vm table's user_data column to be base64 valid with padding", e);
105+
}
106+
if (LOG.isDebugEnabled()) {
107+
LOG.debug("Done validating base64 content of user data");
108+
}
109+
}
110+
70111
@SuppressWarnings("serial")
71112
private void updateSystemVmTemplates(final Connection conn) {
72113
LOG.debug("Updating System Vm template IDs");

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2525,7 +2525,7 @@ public UserVm updateVirtualMachine(long id, String displayName, String group, Bo
25252525
if (userData != null) {
25262526
// check and replace newlines
25272527
userData = userData.replace("\\n", "");
2528-
validateUserData(userData, httpMethod);
2528+
userData = validateUserData(userData, httpMethod);
25292529
// update userData on domain router.
25302530
updateUserdata = true;
25312531
} else {
@@ -3396,7 +3396,7 @@ protected UserVm createVirtualMachine(DataCenter zone, ServiceOffering serviceOf
33963396
_accountMgr.checkAccess(owner, AccessType.UseEntry, false, template);
33973397

33983398
// check if the user data is correct
3399-
validateUserData(userData, httpmethod);
3399+
userData = validateUserData(userData, httpmethod);
34003400

34013401
// Find an SSH public key corresponding to the key pair name, if one is
34023402
// given
@@ -3943,7 +3943,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) {
39433943
}
39443944
}
39453945

3946-
private void validateUserData(String userData, HTTPMethod httpmethod) {
3946+
protected String validateUserData(String userData, HTTPMethod httpmethod) {
39473947
byte[] decodedUserData = null;
39483948
if (userData != null) {
39493949
if (!Base64.isBase64(userData)) {
@@ -3971,7 +3971,10 @@ private void validateUserData(String userData, HTTPMethod httpmethod) {
39713971
if (decodedUserData == null || decodedUserData.length < 1) {
39723972
throw new InvalidParameterValueException("User data is too short");
39733973
}
3974+
// Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
3975+
return Base64.encodeBase64String(decodedUserData);
39743976
}
3977+
return null;
39753978
}
39763979

39773980
@Override

server/test/com/cloud/vm/UserVmManagerTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import com.cloud.event.dao.UsageEventDao;
4949
import com.cloud.uservm.UserVm;
5050
import org.junit.Assert;
51+
import org.apache.cloudstack.api.BaseCmd;
5152
import org.junit.Before;
5253
import org.junit.Test;
5354
import org.mockito.Mock;
@@ -1056,4 +1057,16 @@ public void testPersistDeviceBusInfo() {
10561057
_userVmMgr.persistDeviceBusInfo(_vmMock, "lsilogic");
10571058
verify(_vmDao, times(1)).saveDetails(any(UserVmVO.class));
10581059
}
1060+
1061+
@Test
1062+
public void testValideBase64WithoutPadding() {
1063+
// fo should be encoded in base64 either as Zm8 or Zm8=
1064+
String encodedUserdata = "Zm8";
1065+
String encodedUserdataWithPadding = "Zm8=";
1066+
1067+
// Verify that we accept both but return the padded version
1068+
assertTrue("validate return the value with padding", encodedUserdataWithPadding.equals(_userVmMgr.validateUserData(encodedUserdata, BaseCmd.HTTPMethod.GET)));
1069+
assertTrue("validate return the value with padding", encodedUserdataWithPadding.equals(_userVmMgr.validateUserData(encodedUserdataWithPadding, BaseCmd.HTTPMethod.GET)));
1070+
}
1071+
10591072
}

0 commit comments

Comments
 (0)