blob: 91f24fe7045891a568dfbb32832c0c9628d46654 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.cloudstack.userdata;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import com.cloud.configuration.ConfigurationManager;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
public class UserDataManagerImpl extends ManagerBase implements UserDataManager {
private static final int MAX_USER_DATA_LENGTH_BYTES = 2048;
private static final int MAX_HTTP_GET_LENGTH = 2 * 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;
private List<UserDataProvider> userDataProviders;
private static Map<String, UserDataProvider> userDataProvidersMap = new HashMap<>();
public void setUserDataProviders(final List<UserDataProvider> userDataProviders) {
this.userDataProviders = userDataProviders;
}
private void initializeUserdataProvidersMap() {
if (userDataProviders != null) {
for (final UserDataProvider provider : userDataProviders) {
userDataProvidersMap.put(provider.getName().toLowerCase(), provider);
}
}
}
@Override
public boolean start() {
initializeUserdataProvidersMap();
return true;
}
@Override
public String getConfigComponentName() {
return UserDataManagerImpl.class.getSimpleName();
}
@Override
public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey[] {};
}
protected UserDataProvider getUserdataProvider(String name) {
if (StringUtils.isEmpty(name)) {
// Use cloud-init as the default userdata provider
name = "cloud-init";
}
if (!userDataProvidersMap.containsKey(name)) {
throw new CloudRuntimeException("Failed to find userdata provider by the name: " + name);
}
return userDataProvidersMap.get(name);
}
@Override
public String concatenateUserData(String userdata1, String userdata2, String userdataProvider) {
UserDataProvider provider = getUserdataProvider(userdataProvider);
String appendUserData = provider.appendUserData(userdata1, userdata2);
return Base64.encodeBase64String(appendUserData.getBytes());
}
@Override
public String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod) {
byte[] decodedUserData = null;
if (userData != null) {
if (userData.contains("%")) {
try {
userData = URLDecoder.decode(userData, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new InvalidParameterValueException("Url decoding of userdata failed.");
}
}
if (!Base64.isBase64(userData)) {
throw new InvalidParameterValueException("User data is not base64 encoded");
}
// If GET, use 4K. If POST, support up to 1M.
if (httpmethod.equals(BaseCmd.HTTPMethod.GET)) {
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_GET_LENGTH, BaseCmd.HTTPMethod.GET);
} else if (httpmethod.equals(BaseCmd.HTTPMethod.POST)) {
decodedUserData = validateAndDecodeByHTTPMethod(userData, MAX_HTTP_POST_LENGTH, BaseCmd.HTTPMethod.POST);
}
if (decodedUserData == null || decodedUserData.length < 1) {
throw new InvalidParameterValueException("User data is too short");
}
// Re-encode so that the '=' paddings are added if necessary since 'isBase64' does not require it, but python does on the VR.
return Base64.encodeBase64String(decodedUserData);
}
return null;
}
private byte[] validateAndDecodeByHTTPMethod(String userData, int maxHTTPLength, BaseCmd.HTTPMethod httpMethod) {
byte[] decodedUserData = null;
if (userData.length() >= maxHTTPLength) {
throw new InvalidParameterValueException(String.format("User data is too long for an http %s request", httpMethod.toString()));
}
if (userData.length() > ConfigurationManager.VM_USERDATA_MAX_LENGTH.value()) {
throw new InvalidParameterValueException("User data has exceeded configurable max length : " + ConfigurationManager.VM_USERDATA_MAX_LENGTH.value());
}
decodedUserData = Base64.decodeBase64(userData.getBytes());
if (decodedUserData.length > maxHTTPLength) {
throw new InvalidParameterValueException(String.format("User data is too long for http %s request", httpMethod.toString()));
}
return decodedUserData;
}
}