/*************************************************************************
* Copyright 2009-2015 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.cloudformation.template;
import com.eucalyptus.cloudformation.AccessDeniedException;
import com.eucalyptus.cloudformation.ValidationErrorException;
import com.eucalyptus.cloudformation.entity.StackEntity;
import com.eucalyptus.cloudformation.util.MessageHelper;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.ClusterInfoType;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.DescribeAvailabilityZonesResponseType;
import com.eucalyptus.compute.common.DescribeAvailabilityZonesType;
import com.eucalyptus.compute.common.DescribeImagesResponseType;
import com.eucalyptus.compute.common.DescribeImagesType;
import com.eucalyptus.compute.common.DescribeInstancesResponseType;
import com.eucalyptus.compute.common.DescribeInstancesType;
import com.eucalyptus.compute.common.DescribeKeyPairsResponseItemType;
import com.eucalyptus.compute.common.DescribeKeyPairsResponseType;
import com.eucalyptus.compute.common.DescribeKeyPairsType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsResponseType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsType;
import com.eucalyptus.compute.common.DescribeSubnetsResponseType;
import com.eucalyptus.compute.common.DescribeSubnetsType;
import com.eucalyptus.compute.common.DescribeVolumesResponseType;
import com.eucalyptus.compute.common.DescribeVolumesType;
import com.eucalyptus.compute.common.DescribeVpcsResponseType;
import com.eucalyptus.compute.common.DescribeVpcsType;
import com.eucalyptus.compute.common.ImageDetails;
import com.eucalyptus.compute.common.ReservationInfoType;
import com.eucalyptus.compute.common.RunningInstancesItemType;
import com.eucalyptus.compute.common.SecurityGroupItemType;
import com.eucalyptus.compute.common.SubnetIdSetItemType;
import com.eucalyptus.compute.common.SubnetIdSetType;
import com.eucalyptus.compute.common.SubnetType;
import com.eucalyptus.compute.common.Volume;
import com.eucalyptus.compute.common.VpcIdSetItemType;
import com.eucalyptus.compute.common.VpcIdSetType;
import com.eucalyptus.compute.common.VpcType;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static com.eucalyptus.cloudformation.template.ParameterType.*;
public class AWSParameterTypeValidationHelper {
public enum ValidParameterFamilyValues {
AVAILABILITY_ZONE_NAMES (AWS_EC2_AvailabilityZone_Name, List_AWS_EC2_AvailabilityZone_Name) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeAvailabilityZonesType describeAvailabilityZonesType = MessageHelper.createMessage(DescribeAvailabilityZonesType.class, effectiveUserId);
describeAvailabilityZonesType.setAvailabilityZoneSet(Lists.newArrayList(valuesToCheck));
DescribeAvailabilityZonesResponseType describeAvailabilityZonesResponseType = AsyncRequests.sendSync(configuration, describeAvailabilityZonesType);
if (describeAvailabilityZonesResponseType != null && describeAvailabilityZonesResponseType.getAvailabilityZoneInfo() != null) {
for (ClusterInfoType clusterInfoType:describeAvailabilityZonesResponseType.getAvailabilityZoneInfo()) {
retVal.add(clusterInfoType.getZoneName());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access availability zones. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
KEY_PAIR_NAMES (AWS_EC2_KeyPair_KeyName, List_AWS_EC2_KeyPair_KeyName) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeKeyPairsType describeKeyPairsType = MessageHelper.createMessage(DescribeKeyPairsType.class, effectiveUserId);
describeKeyPairsType.setKeySet(Lists.newArrayList(valuesToCheck));
DescribeKeyPairsResponseType describeKeyPairsResponseType = AsyncRequests.sendSync(configuration, describeKeyPairsType);
if (describeKeyPairsResponseType != null && describeKeyPairsResponseType.getKeySet() != null) {
for (DescribeKeyPairsResponseItemType describeKeyPairsResponseItemType:describeKeyPairsResponseType.getKeySet()) {
retVal.add(describeKeyPairsResponseItemType.getKeyName());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access keypairs. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
IMAGE_IDS (AWS_EC2_Image_Id, List_AWS_EC2_Image_Id) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeImagesType describeImagesType = MessageHelper.createMessage(DescribeImagesType.class, effectiveUserId);
describeImagesType.setImagesSet(Lists.newArrayList(valuesToCheck));
DescribeImagesResponseType describeImagesResponseType = AsyncRequests.sendSync(configuration, describeImagesType);
if (describeImagesResponseType != null && describeImagesResponseType.getImagesSet() != null) {
for (ImageDetails imageDetails: describeImagesResponseType.getImagesSet()) {
retVal.add(imageDetails.getImageId());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access image ids. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
INSTANCE_IDS (AWS_EC2_Instance_Id, List_AWS_EC2_Instance_Id) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeInstancesType describeInstancesType = MessageHelper.createMessage(DescribeInstancesType.class, effectiveUserId);
describeInstancesType.setInstancesSet(Lists.newArrayList(valuesToCheck));
DescribeInstancesResponseType describeInstancesResponseType = AsyncRequests.sendSync(configuration, describeInstancesType);
if (describeInstancesResponseType != null && describeInstancesResponseType.getReservationSet() != null) {
for (ReservationInfoType reservationInfoType: describeInstancesResponseType.getReservationSet()) {
if (reservationInfoType != null && reservationInfoType.getInstancesSet() != null) {
for (RunningInstancesItemType runningInstancesItemType: reservationInfoType.getInstancesSet()) {
retVal.add(runningInstancesItemType.getInstanceId());
}
}
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access instance ids. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
SUBNET_IDS (AWS_EC2_Subnet_Id, List_AWS_EC2_Subnet_Id) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeSubnetsType describeSubnetsType = MessageHelper.createMessage(DescribeSubnetsType.class, effectiveUserId);
SubnetIdSetType subnetSet = new SubnetIdSetType();
ArrayList<SubnetIdSetItemType> item = Lists.newArrayList(
Collections2.transform(valuesToCheck, new Function<String, SubnetIdSetItemType>() {
@Nullable
@Override
public SubnetIdSetItemType apply(@Nullable String subnetId) {
SubnetIdSetItemType subnetIdSetItemType = new SubnetIdSetItemType();
subnetIdSetItemType.setSubnetId(subnetId);
return subnetIdSetItemType;
}
})
);
subnetSet.setItem(item);
describeSubnetsType.setSubnetSet(subnetSet);
DescribeSubnetsResponseType describeSubnetsResponseType = AsyncRequests.sendSync(configuration, describeSubnetsType);
if (describeSubnetsResponseType != null && describeSubnetsResponseType.getSubnetSet() != null && describeSubnetsResponseType.getSubnetSet().getItem() != null) {
for (SubnetType subnetType:describeSubnetsResponseType.getSubnetSet().getItem()) {
retVal.add(subnetType.getSubnetId());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access subnet ids. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
VOLUME_IDS (AWS_EC2_Volume_Id, List_AWS_EC2_Volume_Id) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeVolumesType describeVolumesType = MessageHelper.createMessage(DescribeVolumesType.class, effectiveUserId);
describeVolumesType.setVolumeSet(Lists.newArrayList(valuesToCheck));
DescribeVolumesResponseType describeVolumesResponseType = AsyncRequests.sendSync(configuration, describeVolumesType);
if (describeVolumesResponseType != null && describeVolumesResponseType.getVolumeSet() != null && describeVolumesResponseType.getVolumeSet() != null) {
for (Volume volume: describeVolumesResponseType.getVolumeSet()) {
retVal.add(volume.getVolumeId());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access volume ids. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
VPC_IDS (AWS_EC2_VPC_Id, List_AWS_EC2_VPC_Id) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeVpcsType describeVpcsType = MessageHelper.createMessage(DescribeVpcsType.class, effectiveUserId);
VpcIdSetType vpcSet = new VpcIdSetType();
ArrayList<VpcIdSetItemType> item = Lists.newArrayList(
Collections2.transform(valuesToCheck, new Function<String, VpcIdSetItemType>() {
@Nullable
@Override
public VpcIdSetItemType apply(@Nullable String vpcId) {
VpcIdSetItemType vpcIdSetItemType = new VpcIdSetItemType();
vpcIdSetItemType.setVpcId(vpcId);
return vpcIdSetItemType;
}
})
);
vpcSet.setItem(item);
describeVpcsType.setVpcSet(vpcSet);
DescribeVpcsResponseType describeVpcsResponseType = AsyncRequests.sendSync(configuration, describeVpcsType);
if (describeVpcsResponseType != null && describeVpcsResponseType.getVpcSet() != null && describeVpcsResponseType.getVpcSet().getItem() != null) {
for (VpcType vpcType:describeVpcsResponseType.getVpcSet().getItem()) {
retVal.add(vpcType.getVpcId());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access VPC ids. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
SECURITY_GROUP_IDS (AWS_EC2_SecurityGroup_Id, List_AWS_EC2_SecurityGroup_Id) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, effectiveUserId);
describeSecurityGroupsType.setSecurityGroupIdSet(Lists.newArrayList(valuesToCheck));
DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType);
if (describeSecurityGroupsResponseType != null && describeSecurityGroupsResponseType.getSecurityGroupInfo() != null) {
for (SecurityGroupItemType securityGroupItemType:describeSecurityGroupsResponseType.getSecurityGroupInfo()) {
retVal.add(securityGroupItemType.getGroupId());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access security groups. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
},
SECURITY_GROUP_NAMES (AWS_EC2_SecurityGroup_GroupName, List_AWS_EC2_SecurityGroup_GroupName) {
@Override
public List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException {
List<String> retVal = Lists.newArrayList();
ServiceConfiguration configuration = Topology.lookup(Compute.class);
try {
DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, effectiveUserId);
describeSecurityGroupsType.setSecurityGroupSet(Lists.newArrayList(valuesToCheck));
DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType);
if (describeSecurityGroupsResponseType != null && describeSecurityGroupsResponseType.getSecurityGroupInfo() != null) {
for (SecurityGroupItemType securityGroupItemType:describeSecurityGroupsResponseType.getSecurityGroupInfo()) {
retVal.add(securityGroupItemType.getGroupName());
}
}
} catch (Exception e) {
Throwable rootCause = Throwables.getRootCause(e);
throw new AccessDeniedException("Unable to access security groups. " + (rootCause.getMessage() == null ? "" : rootCause.getMessage()));
}
return retVal;
}
};
private final ParameterType singularType;
private final ParameterType listType;
ValidParameterFamilyValues(ParameterType singularType, ParameterType listType) {
this.singularType = singularType;
this.listType = listType;
}
public boolean matchesSingularType(ParameterType parameterType) {
return singularType == parameterType;
}
public boolean matchesListType(ParameterType parameterType) {
return listType == parameterType;
}
public abstract List<String> getValidValuesFromValueSet(String effectiveUserId, Collection<String> valuesToCheck) throws AccessDeniedException;
}
private static boolean matchAndValidateTypeFamily(StackEntity.Parameter parameter,
ParameterType parameterType, String effectiveUserId,
ValidParameterFamilyValues validParameterFamilyValues) throws Exception {
boolean matchesSingular = validParameterFamilyValues.matchesSingularType(parameterType);
boolean matchesList = validParameterFamilyValues.matchesListType(parameterType);
boolean matches = matchesSingular || matchesList;
if (matches) {
JsonNode jsonNode = JsonHelper.getJsonNodeFromString(parameter.getJsonValue());
Collection<String> valuesToCheck = Sets.newHashSet();
if (matchesSingular) {
if (!jsonNode.isValueNode())
throw new ValidationErrorException("Invalid value for Parameter " + parameter.getKey());
valuesToCheck.add(jsonNode.asText());
} else {
if (!jsonNode.isArray())
throw new ValidationErrorException("Invalid value for Parameter " + parameter.getKey());
for (int i = 0; i < jsonNode.size(); i++) {
JsonNode elementNode = jsonNode.get(i);
if (!elementNode.isValueNode())
throw new ValidationErrorException("Invalid value for Parameter " + parameter.getKey());
valuesToCheck.add(elementNode.asText());
}
}
List<String> validValues = validParameterFamilyValues.getValidValuesFromValueSet(effectiveUserId, valuesToCheck);
for (String valueToCheck : valuesToCheck) {
if (!validValues.contains(valueToCheck)) {
throw new ValidationErrorException("Parameter validation failed: parameter value " + valueToCheck + " for parameter name " + parameter.getKey() + " is not an allowed value for the parameter type.");
}
}
}
return matches;
}
public static void validateParameter(StackEntity.Parameter parameter, ParameterType parameterType, String effectiveUserId) throws Exception {
if (parameterType == null) throw new ValidationErrorException("Can not find parameter type for parameter " + parameter.getKey());
for (ValidParameterFamilyValues validParameterFamilyValues:ValidParameterFamilyValues.values()) {
if (matchAndValidateTypeFamily(parameter, parameterType, effectiveUserId, validParameterFamilyValues)) {
break; // found the one to check against.
}
}
}
}