This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch 4.19
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push:
new b4325eccfb4 Fix userdata append header restrictions (#9575)
b4325eccfb4 is described below
commit b4325eccfb44ea1d842d1fe9e836cae0f31e2b1a
Author: Nicolas Vazquez <[email protected]>
AuthorDate: Thu Aug 29 06:09:14 2024 -0300
Fix userdata append header restrictions (#9575)
---
.../userdata/CloudInitUserDataProvider.java | 28 +++++++++++-----
.../userdata/CloudInitUserDataProviderTest.java | 39 ++++++++++++++++++++--
ui/src/utils/plugins.js | 2 +-
3 files changed, 56 insertions(+), 13 deletions(-)
diff --git
a/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java
b/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java
index 65996f181a9..feca8712a7d 100644
---
a/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java
+++
b/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java
@@ -88,14 +88,20 @@ public class CloudInitUserDataProvider extends AdapterBase
implements UserDataPr
.filter(x -> (x.startsWith("#") && !x.startsWith("##")) ||
(x.startsWith("Content-Type:")))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(lines)) {
- throw new CloudRuntimeException("Failed to detect the user data
format type as it " +
- "does not contain a header");
+ LOGGER.debug("Failed to detect the user data format type as it
does not contain a header");
+ return null;
}
return lines.get(0);
}
- protected FormatType mapUserDataHeaderToFormatType(String header) {
- if (header.equalsIgnoreCase("#cloud-config")) {
+ protected FormatType mapUserDataHeaderToFormatType(String header,
FormatType defaultFormatType) {
+ if (StringUtils.isBlank(header)) {
+ if (defaultFormatType == null) {
+ throw new CloudRuntimeException("Failed to detect the user
data format type as it does not contain a header");
+ }
+ LOGGER.debug(String.format("Empty header for userdata, using the
default format type: %s", defaultFormatType.name()));
+ return defaultFormatType;
+ } else if (header.equalsIgnoreCase("#cloud-config")) {
return FormatType.CLOUD_CONFIG;
} else if (header.startsWith("#!")) {
return FormatType.BASH_SCRIPT;
@@ -115,9 +121,11 @@ public class CloudInitUserDataProvider extends AdapterBase
implements UserDataPr
/**
* Detect the user data type
+ * @param userdata the userdata string to detect the type
+ * @param defaultFormatType if not null, then use it in case the header
does not exist in the userdata, otherwise fail
* Reference: <a
href="https://canonical-cloud-init.readthedocs-hosted.com/en/latest/explanation/format.html#user-data-formats"
/>
*/
- protected FormatType getUserDataFormatType(String userdata) {
+ protected FormatType getUserDataFormatType(String userdata, FormatType
defaultFormatType) {
if (StringUtils.isBlank(userdata)) {
String msg = "User data expected but provided empty user data";
LOGGER.error(msg);
@@ -125,7 +133,7 @@ public class CloudInitUserDataProvider extends AdapterBase
implements UserDataPr
}
String header = extractUserDataHeader(userdata);
- return mapUserDataHeaderToFormatType(header);
+ return mapUserDataHeaderToFormatType(header, defaultFormatType);
}
private String getContentType(String userData, FormatType formatType)
throws MessagingException {
@@ -234,7 +242,9 @@ public class CloudInitUserDataProvider extends AdapterBase
implements UserDataPr
}
private String simpleAppendSameFormatTypeUserData(String userData1, String
userData2) {
- return String.format("%s\n\n%s", userData1,
userData2.substring(userData2.indexOf('\n')+1));
+ String userdata2Header = extractUserDataHeader(userData2);
+ int beginIndex = StringUtils.isNotBlank(userdata2Header) ?
userData2.indexOf('\n')+1 : 0;
+ return String.format("%s\n\n%s", userData1,
userData2.substring(beginIndex));
}
private void checkGzipAppend(String encodedUserData1, String
encodedUserData2) {
@@ -249,8 +259,8 @@ public class CloudInitUserDataProvider extends AdapterBase
implements UserDataPr
checkGzipAppend(encodedUserData1, encodedUserData2);
String userData1 = new
String(Base64.decodeBase64(encodedUserData1));
String userData2 = new
String(Base64.decodeBase64(encodedUserData2));
- FormatType formatType1 = getUserDataFormatType(userData1);
- FormatType formatType2 = getUserDataFormatType(userData2);
+ FormatType formatType1 = getUserDataFormatType(userData1, null);
+ FormatType formatType2 = getUserDataFormatType(userData2,
formatType1);
if (formatType1.equals(formatType2) &&
List.of(FormatType.CLOUD_CONFIG, FormatType.BASH_SCRIPT).contains(formatType1))
{
return simpleAppendSameFormatTypeUserData(userData1,
userData2);
}
diff --git
a/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java
b/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java
index 4ca9fb7ebd6..86b6a6fb6ea 100644
---
a/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java
+++
b/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java
@@ -73,21 +73,28 @@ public class CloudInitUserDataProviderTest {
@Test
public void testGetUserDataFormatType() {
- CloudInitUserDataProvider.FormatType type =
provider.getUserDataFormatType(CLOUD_CONFIG_USERDATA);
+ CloudInitUserDataProvider.FormatType type =
provider.getUserDataFormatType(CLOUD_CONFIG_USERDATA, null);
Assert.assertEquals(CloudInitUserDataProvider.FormatType.CLOUD_CONFIG,
type);
}
@Test(expected = CloudRuntimeException.class)
public void testGetUserDataFormatTypeNoHeader() {
String userdata = "password: password\nchpasswd: { expire: False
}\nssh_pwauth: True";
- provider.getUserDataFormatType(userdata);
+ provider.getUserDataFormatType(userdata, null);
+ }
+
+ @Test
+ public void testGetUserDataFormatTypeNoHeaderDefaultFormat() {
+ String userdata = "password: password\nchpasswd: { expire: False
}\nssh_pwauth: True";
+ CloudInitUserDataProvider.FormatType defaultFormatType =
CloudInitUserDataProvider.FormatType.CLOUD_CONFIG;
+ Assert.assertEquals(defaultFormatType,
provider.getUserDataFormatType(userdata, defaultFormatType));
}
@Test(expected = CloudRuntimeException.class)
public void testGetUserDataFormatTypeInvalidType() {
String userdata = "#invalid-type\n" +
"password: password\nchpasswd: { expire: False }\nssh_pwauth:
True";
- provider.getUserDataFormatType(userdata);
+ provider.getUserDataFormatType(userdata, null);
}
private MimeMultipart getCheckedMultipartFromMultipartData(String
multipartUserData, int count) {
@@ -111,6 +118,16 @@ public class CloudInitUserDataProviderTest {
getCheckedMultipartFromMultipartData(multipartUserData, 2);
}
+ @Test
+ public void testAppendUserDataSecondWithoutHeader() {
+ String userdataWithHeader =
Base64.encodeBase64String(SHELL_SCRIPT_USERDATA1.getBytes());
+ String bashScriptWithoutHeader = "echo \"without header\"";
+ String userdataWithoutHeader =
Base64.encodeBase64String(bashScriptWithoutHeader.getBytes());
+ String appended = provider.appendUserData(userdataWithHeader,
userdataWithoutHeader);
+ String expected = String.format("%s\n\n%s", SHELL_SCRIPT_USERDATA1,
bashScriptWithoutHeader);
+ Assert.assertEquals(expected, appended);
+ }
+
@Test
public void testAppendSameShellScriptTypeUserData() {
String result = SHELL_SCRIPT_USERDATA + "\n\n" +
@@ -129,6 +146,22 @@ public class CloudInitUserDataProviderTest {
Assert.assertEquals(result, appendUserData);
}
+ @Test
+ public void testAppendCloudConfig() {
+ String userdata1 = "#cloud-config\n" +
+ "chpasswd:\n" +
+ " list: |\n" +
+ " root:password\n" +
+ " expire: False";
+ String userdata2 = "write_files:\n" +
+ "- path: /root/CLOUD_INIT_WAS_HERE";
+ String userdataWithHeader =
Base64.encodeBase64String(userdata1.getBytes());
+ String userdataWithoutHeader =
Base64.encodeBase64String(userdata2.getBytes());
+ String appended = provider.appendUserData(userdataWithHeader,
userdataWithoutHeader);
+ String expected = String.format("%s\n\n%s", userdata1, userdata2);
+ Assert.assertEquals(expected, appended);
+ }
+
@Test
public void testAppendUserDataMIMETemplateData() {
String multipartUserData = provider.appendUserData(
diff --git a/ui/src/utils/plugins.js b/ui/src/utils/plugins.js
index e358988fac9..6beda22a8da 100644
--- a/ui/src/utils/plugins.js
+++ b/ui/src/utils/plugins.js
@@ -506,7 +506,7 @@ export const genericUtilPlugin = {
if (isBase64(text)) {
return text
}
- return encodeURIComponent(btoa(unescape(encodeURIComponent(text))))
+ return encodeURI(btoa(unescape(encodeURIComponent(text))))
}
}
}