mirror of
				https://github.com/apache/cloudstack.git
				synced 2025-10-26 08:42:29 +01:00 
			
		
		
		
	Fix userdata append header restrictions (#9575)
This commit is contained in:
		
							parent
							
								
									48e745cad2
								
							
						
					
					
						commit
						b4325eccfb
					
				| @ -88,14 +88,20 @@ public class CloudInitUserDataProvider extends AdapterBase implements UserDataPr | |||||||
|                 .filter(x -> (x.startsWith("#") && !x.startsWith("##")) || (x.startsWith("Content-Type:"))) |                 .filter(x -> (x.startsWith("#") && !x.startsWith("##")) || (x.startsWith("Content-Type:"))) | ||||||
|                 .collect(Collectors.toList()); |                 .collect(Collectors.toList()); | ||||||
|         if (CollectionUtils.isEmpty(lines)) { |         if (CollectionUtils.isEmpty(lines)) { | ||||||
|             throw new CloudRuntimeException("Failed to detect the user data format type as it " + |             LOGGER.debug("Failed to detect the user data format type as it does not contain a header"); | ||||||
|                     "does not contain a header"); |             return null; | ||||||
|         } |         } | ||||||
|         return lines.get(0); |         return lines.get(0); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected FormatType mapUserDataHeaderToFormatType(String header) { |     protected FormatType mapUserDataHeaderToFormatType(String header, FormatType defaultFormatType) { | ||||||
|         if (header.equalsIgnoreCase("#cloud-config")) { |         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; |             return FormatType.CLOUD_CONFIG; | ||||||
|         } else if (header.startsWith("#!")) { |         } else if (header.startsWith("#!")) { | ||||||
|             return FormatType.BASH_SCRIPT; |             return FormatType.BASH_SCRIPT; | ||||||
| @ -115,9 +121,11 @@ public class CloudInitUserDataProvider extends AdapterBase implements UserDataPr | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Detect the user data type |      * 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" /> |      * 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)) { |         if (StringUtils.isBlank(userdata)) { | ||||||
|             String msg = "User data expected but provided empty user data"; |             String msg = "User data expected but provided empty user data"; | ||||||
|             LOGGER.error(msg); |             LOGGER.error(msg); | ||||||
| @ -125,7 +133,7 @@ public class CloudInitUserDataProvider extends AdapterBase implements UserDataPr | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         String header = extractUserDataHeader(userdata); |         String header = extractUserDataHeader(userdata); | ||||||
|         return mapUserDataHeaderToFormatType(header); |         return mapUserDataHeaderToFormatType(header, defaultFormatType); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private String getContentType(String userData, FormatType formatType) throws MessagingException { |     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) { |     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) { |     private void checkGzipAppend(String encodedUserData1, String encodedUserData2) { | ||||||
| @ -249,8 +259,8 @@ public class CloudInitUserDataProvider extends AdapterBase implements UserDataPr | |||||||
|             checkGzipAppend(encodedUserData1, encodedUserData2); |             checkGzipAppend(encodedUserData1, encodedUserData2); | ||||||
|             String userData1 = new String(Base64.decodeBase64(encodedUserData1)); |             String userData1 = new String(Base64.decodeBase64(encodedUserData1)); | ||||||
|             String userData2 = new String(Base64.decodeBase64(encodedUserData2)); |             String userData2 = new String(Base64.decodeBase64(encodedUserData2)); | ||||||
|             FormatType formatType1 = getUserDataFormatType(userData1); |             FormatType formatType1 = getUserDataFormatType(userData1, null); | ||||||
|             FormatType formatType2 = getUserDataFormatType(userData2); |             FormatType formatType2 = getUserDataFormatType(userData2, formatType1); | ||||||
|             if (formatType1.equals(formatType2) && List.of(FormatType.CLOUD_CONFIG, FormatType.BASH_SCRIPT).contains(formatType1)) { |             if (formatType1.equals(formatType2) && List.of(FormatType.CLOUD_CONFIG, FormatType.BASH_SCRIPT).contains(formatType1)) { | ||||||
|                 return simpleAppendSameFormatTypeUserData(userData1, userData2); |                 return simpleAppendSameFormatTypeUserData(userData1, userData2); | ||||||
|             } |             } | ||||||
|  | |||||||
| @ -73,21 +73,28 @@ public class CloudInitUserDataProviderTest { | |||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     public void testGetUserDataFormatType() { |     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); |         Assert.assertEquals(CloudInitUserDataProvider.FormatType.CLOUD_CONFIG, type); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Test(expected = CloudRuntimeException.class) |     @Test(expected = CloudRuntimeException.class) | ||||||
|     public void testGetUserDataFormatTypeNoHeader() { |     public void testGetUserDataFormatTypeNoHeader() { | ||||||
|         String userdata = "password: password\nchpasswd: { expire: False }\nssh_pwauth: True"; |         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) |     @Test(expected = CloudRuntimeException.class) | ||||||
|     public void testGetUserDataFormatTypeInvalidType() { |     public void testGetUserDataFormatTypeInvalidType() { | ||||||
|         String userdata = "#invalid-type\n" + |         String userdata = "#invalid-type\n" + | ||||||
|                 "password: password\nchpasswd: { expire: False }\nssh_pwauth: True"; |                 "password: password\nchpasswd: { expire: False }\nssh_pwauth: True"; | ||||||
|         provider.getUserDataFormatType(userdata); |         provider.getUserDataFormatType(userdata, null); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private MimeMultipart getCheckedMultipartFromMultipartData(String multipartUserData, int count) { |     private MimeMultipart getCheckedMultipartFromMultipartData(String multipartUserData, int count) { | ||||||
| @ -111,6 +118,16 @@ public class CloudInitUserDataProviderTest { | |||||||
|         getCheckedMultipartFromMultipartData(multipartUserData, 2); |         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 |     @Test | ||||||
|     public void testAppendSameShellScriptTypeUserData() { |     public void testAppendSameShellScriptTypeUserData() { | ||||||
|         String result = SHELL_SCRIPT_USERDATA + "\n\n" + |         String result = SHELL_SCRIPT_USERDATA + "\n\n" + | ||||||
| @ -129,6 +146,22 @@ public class CloudInitUserDataProviderTest { | |||||||
|         Assert.assertEquals(result, appendUserData); |         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 |     @Test | ||||||
|     public void testAppendUserDataMIMETemplateData() { |     public void testAppendUserDataMIMETemplateData() { | ||||||
|         String multipartUserData = provider.appendUserData( |         String multipartUserData = provider.appendUserData( | ||||||
|  | |||||||
| @ -506,7 +506,7 @@ export const genericUtilPlugin = { | |||||||
|       if (isBase64(text)) { |       if (isBase64(text)) { | ||||||
|         return text |         return text | ||||||
|       } |       } | ||||||
|       return encodeURIComponent(btoa(unescape(encodeURIComponent(text)))) |       return encodeURI(btoa(unescape(encodeURIComponent(text)))) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user