/*************************************************************************
* 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.resources.standard.actions;
import com.eucalyptus.cloudformation.ValidationErrorException;
import com.eucalyptus.cloudformation.resources.ResourceAction;
import com.eucalyptus.cloudformation.resources.ResourceInfo;
import com.eucalyptus.cloudformation.resources.ResourceProperties;
import com.eucalyptus.cloudformation.resources.standard.info.AWSEC2SecurityGroupEgressResourceInfo;
import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2SecurityGroupEgressProperties;
import com.eucalyptus.cloudformation.template.JsonHelper;
import com.eucalyptus.cloudformation.util.MessageHelper;
import com.eucalyptus.cloudformation.workflow.steps.Step;
import com.eucalyptus.cloudformation.workflow.steps.StepBasedResourceAction;
import com.eucalyptus.cloudformation.workflow.updateinfo.UpdateType;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.compute.common.AuthorizeSecurityGroupEgressResponseType;
import com.eucalyptus.compute.common.AuthorizeSecurityGroupEgressType;
import com.eucalyptus.compute.common.Compute;
import com.eucalyptus.compute.common.DescribeSecurityGroupsResponseType;
import com.eucalyptus.compute.common.DescribeSecurityGroupsType;
import com.eucalyptus.compute.common.Filter;
import com.eucalyptus.compute.common.IpPermissionType;
import com.eucalyptus.compute.common.RevokeSecurityGroupEgressResponseType;
import com.eucalyptus.compute.common.RevokeSecurityGroupEgressType;
import com.eucalyptus.compute.common.SecurityGroupItemType;
import com.eucalyptus.compute.common.UserIdGroupPairType;
import com.eucalyptus.util.async.AsyncRequests;
import com.fasterxml.jackson.databind.node.TextNode;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Objects;
/**
* Created by ethomas on 2/3/14.
*/
public class AWSEC2SecurityGroupEgressResourceAction extends StepBasedResourceAction {
private AWSEC2SecurityGroupEgressProperties properties = new AWSEC2SecurityGroupEgressProperties();
private AWSEC2SecurityGroupEgressResourceInfo info = new AWSEC2SecurityGroupEgressResourceInfo();
public AWSEC2SecurityGroupEgressResourceAction() {
// only replacement update options
super(fromEnum(CreateSteps.class), fromEnum(DeleteSteps.class), null, null);
}
@Override
public UpdateType getUpdateType(ResourceAction resourceAction, boolean stackTagsChanged) {
UpdateType updateType = info.supportsTags() && stackTagsChanged ? UpdateType.NO_INTERRUPTION : UpdateType.NONE;
AWSEC2SecurityGroupEgressResourceAction otherAction = (AWSEC2SecurityGroupEgressResourceAction) resourceAction;
if (!Objects.equals(properties.getGroupId(), otherAction.properties.getGroupId())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getIpProtocol(), otherAction.properties.getIpProtocol())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getCidrIp(), otherAction.properties.getCidrIp())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getDestinationSecurityGroupId(), otherAction.properties.getDestinationSecurityGroupId())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getFromPort(), otherAction.properties.getFromPort())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
if (!Objects.equals(properties.getToPort(), otherAction.properties.getToPort())) {
updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT);
}
return updateType;
}
private enum CreateSteps implements Step {
CREATE_EGRESS_RULE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2SecurityGroupEgressResourceAction action = (AWSEC2SecurityGroupEgressResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
action.validateProperties();
// Make sure security group exists.
DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, action.info.getEffectiveUserId());
describeSecurityGroupsType.setFilterSet( Lists.newArrayList( Filter.filter( "group-id", action.properties.getGroupId( ) ) ) );
DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType);
ArrayList<SecurityGroupItemType> securityGroupItemTypeArrayList = describeSecurityGroupsResponseType.getSecurityGroupInfo();
boolean hasDefaultEgressRule = false;
if (securityGroupItemTypeArrayList == null || securityGroupItemTypeArrayList.isEmpty()) {
throw new ValidationErrorException("No such group with id '" + action.properties.getGroupId()+"'");
} else {
// should only be one, so take the first one
SecurityGroupItemType securityGroupItemType = securityGroupItemTypeArrayList.get(0);
if (securityGroupItemType.getIpPermissionsEgress() != null) {
for (IpPermissionType ipPermissionType : securityGroupItemType.getIpPermissionsEgress()) {
if (action.isDefaultEgressRule(ipPermissionType)) {
hasDefaultEgressRule = true;
break;
}
}
}
}
AuthorizeSecurityGroupEgressType authorizeSecurityGroupEgressType = MessageHelper.createMessage(AuthorizeSecurityGroupEgressType.class, action.info.getEffectiveUserId());
authorizeSecurityGroupEgressType.setGroupId(action.properties.getGroupId());
IpPermissionType ipPermissionType = new IpPermissionType(
action.properties.getIpProtocol(),
action.properties.getFromPort(),
action.properties.getToPort()
);
if (!Strings.isNullOrEmpty(action.properties.getCidrIp())) {
ipPermissionType.setCidrIpRanges(Lists.newArrayList(action.properties.getCidrIp()));
}
if (!Strings.isNullOrEmpty(action.properties.getDestinationSecurityGroupId())) {
ipPermissionType.setGroups(Lists.newArrayList(new UserIdGroupPairType(null, null, action.properties.getDestinationSecurityGroupId())));
}
authorizeSecurityGroupEgressType.setIpPermissions(Lists.newArrayList(ipPermissionType));
AuthorizeSecurityGroupEgressResponseType authorizeSecurityGroupIngressResponseType = AsyncRequests.<AuthorizeSecurityGroupEgressType, AuthorizeSecurityGroupEgressResponseType> sendSync(configuration, authorizeSecurityGroupEgressType);
// remove default (if there)
if (hasDefaultEgressRule) {
RevokeSecurityGroupEgressType revokeSecurityGroupEgressType = MessageHelper.createMessage(RevokeSecurityGroupEgressType.class, action.info.getEffectiveUserId());
revokeSecurityGroupEgressType.setGroupId(action.properties.getGroupId());
revokeSecurityGroupEgressType.setIpPermissions(Lists.newArrayList(DEFAULT_EGRESS_RULE()));
RevokeSecurityGroupEgressResponseType revokeSecurityGroupEgressResponseType = AsyncRequests.<RevokeSecurityGroupEgressType, RevokeSecurityGroupEgressResponseType> sendSync(configuration, revokeSecurityGroupEgressType);
}
action.info.setPhysicalResourceId(action.getDefaultPhysicalResourceId());
action.info.setCreatedEnoughToDelete(true);
action.info.setReferenceValueJson(JsonHelper.getStringFromJsonNode(new TextNode(action.info.getPhysicalResourceId())));
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
private enum DeleteSteps implements Step {
DELETE_EGRESS_RULE {
@Override
public ResourceAction perform(ResourceAction resourceAction) throws Exception {
AWSEC2SecurityGroupEgressResourceAction action = (AWSEC2SecurityGroupEgressResourceAction) resourceAction;
ServiceConfiguration configuration = Topology.lookup(Compute.class);
if (!Boolean.TRUE.equals(action.info.getCreatedEnoughToDelete())) return action;
// property validation
action.validateProperties();
// Make sure security group exists.
DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, action.info.getEffectiveUserId());
describeSecurityGroupsType.setFilterSet( Lists.newArrayList( Filter.filter( "group-id", action.properties.getGroupId( ) ) ) );
DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType);
ArrayList<SecurityGroupItemType> securityGroupItemTypeArrayList = describeSecurityGroupsResponseType.getSecurityGroupInfo();
if (securityGroupItemTypeArrayList == null || securityGroupItemTypeArrayList.isEmpty()) {
return action; // no group
}
RevokeSecurityGroupEgressType revokeSecurityGroupEgressType = MessageHelper.createMessage(RevokeSecurityGroupEgressType.class, action.info.getEffectiveUserId());
revokeSecurityGroupEgressType.setGroupId(action.properties.getGroupId());
IpPermissionType ipPermissionType = new IpPermissionType(
action.properties.getIpProtocol(),
action.properties.getFromPort(),
action.properties.getToPort()
);
if (!Strings.isNullOrEmpty(action.properties.getCidrIp())) {
ipPermissionType.setCidrIpRanges(Lists.newArrayList(action.properties.getCidrIp()));
}
if (!Strings.isNullOrEmpty(action.properties.getDestinationSecurityGroupId())) {
// Generally no need for DestinationSecurityGroupOwnerId if DestinationSecurityGroupId is set, but pass it along if set
ipPermissionType.setGroups(Lists.newArrayList(new UserIdGroupPairType(null, null, action.properties.getDestinationSecurityGroupId())));
}
revokeSecurityGroupEgressType.setIpPermissions(Lists.newArrayList(ipPermissionType));
RevokeSecurityGroupEgressResponseType revokeSecurityGroupEgressResponseType = AsyncRequests.<RevokeSecurityGroupEgressType, RevokeSecurityGroupEgressResponseType> sendSync(configuration, revokeSecurityGroupEgressType);
// do one last check, if there and no egress rules, re-add default rule
DescribeSecurityGroupsType describeSecurityGroupsType2 = MessageHelper.createMessage(DescribeSecurityGroupsType.class, action.info.getEffectiveUserId());
describeSecurityGroupsType2.setFilterSet( Lists.newArrayList( Filter.filter( "group-id", action.properties.getGroupId( ) ) ) );
DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType2 = AsyncRequests.sendSync(configuration, describeSecurityGroupsType);
ArrayList<SecurityGroupItemType> securityGroupItemTypeArrayList2 = describeSecurityGroupsResponseType2.getSecurityGroupInfo();
if (securityGroupItemTypeArrayList2 == null || securityGroupItemTypeArrayList2.isEmpty()) {
return action; // no group
}
if (securityGroupItemTypeArrayList2.get(0).getIpPermissionsEgress() == null || securityGroupItemTypeArrayList2.get(0).getIpPermissionsEgress().isEmpty()) {
AuthorizeSecurityGroupEgressType authorizeSecurityGroupEgressType = MessageHelper.createMessage(AuthorizeSecurityGroupEgressType.class, action.info.getEffectiveUserId());
authorizeSecurityGroupEgressType.setGroupId(action.properties.getGroupId());
authorizeSecurityGroupEgressType.setIpPermissions(Lists.newArrayList(DEFAULT_EGRESS_RULE()));
AuthorizeSecurityGroupEgressResponseType authorizeSecurityGroupIngressResponseType = AsyncRequests.<AuthorizeSecurityGroupEgressType, AuthorizeSecurityGroupEgressResponseType> sendSync(configuration, authorizeSecurityGroupEgressType);
}
return action;
}
};
@Nullable
@Override
public Integer getTimeout() {
return null;
}
}
@Override
public ResourceProperties getResourceProperties() {
return properties;
}
@Override
public void setResourceProperties(ResourceProperties resourceProperties) {
properties = (AWSEC2SecurityGroupEgressProperties) resourceProperties;
}
@Override
public ResourceInfo getResourceInfo() {
return info;
}
@Override
public void setResourceInfo(ResourceInfo resourceInfo) {
info = (AWSEC2SecurityGroupEgressResourceInfo) resourceInfo;
}
private static final IpPermissionType DEFAULT_EGRESS_RULE() {
IpPermissionType ipPermissionType = new IpPermissionType();
ipPermissionType.setIpProtocol("-1");
ipPermissionType.setCidrIpRanges(Lists.newArrayList("0.0.0.0/0"));
return ipPermissionType;
}
private boolean isDefaultEgressRule(IpPermissionType ipPermissionType) {
return ipPermissionType.getIpProtocol().equals("-1") && ipPermissionType.getFromPort() == null
&& ipPermissionType.getToPort() == null && ipPermissionType.getCidrIpRanges() != null &&
ipPermissionType.getCidrIpRanges().size() == 1 && ipPermissionType.getCidrIpRanges().get(0).equals("0.0.0.0/0");
}
private void validateProperties() throws ValidationErrorException {
// Can't specify cidr and destination security group
if (!Strings.isNullOrEmpty(properties.getCidrIp()) && !Strings.isNullOrEmpty(properties.getDestinationSecurityGroupId())) {
throw new ValidationErrorException("Both CidrIp and DestinationSecurityGroup cannot be specified");
}
}
}