/************************************************************************* * 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.AWSEC2SecurityGroupIngressResourceInfo; import com.eucalyptus.cloudformation.resources.standard.propertytypes.AWSEC2SecurityGroupIngressProperties; 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.AuthorizeSecurityGroupIngressResponseType; import com.eucalyptus.compute.common.AuthorizeSecurityGroupIngressType; 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.RevokeSecurityGroupIngressResponseType; import com.eucalyptus.compute.common.RevokeSecurityGroupIngressType; 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 org.apache.log4j.Logger; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Objects; /** * Created by ethomas on 2/3/14. */ public class AWSEC2SecurityGroupIngressResourceAction extends StepBasedResourceAction { private static final Logger LOG = Logger.getLogger(AWSEC2SecurityGroupIngressResourceAction.class); private AWSEC2SecurityGroupIngressProperties properties = new AWSEC2SecurityGroupIngressProperties(); private AWSEC2SecurityGroupIngressResourceInfo info = new AWSEC2SecurityGroupIngressResourceInfo(); public AWSEC2SecurityGroupIngressResourceAction() { // 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; AWSEC2SecurityGroupIngressResourceAction otherAction = (AWSEC2SecurityGroupIngressResourceAction) resourceAction; if (!Objects.equals(properties.getGroupName(), otherAction.properties.getGroupName())) { updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT); } 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.getSourceSecurityGroupName(), otherAction.properties.getSourceSecurityGroupName())) { updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT); } if (!Objects.equals(properties.getSourceSecurityGroupId(), otherAction.properties.getSourceSecurityGroupId())) { updateType = UpdateType.max(updateType, UpdateType.NEEDS_REPLACEMENT); } if (!Objects.equals(properties.getSourceSecurityGroupOwnerId(), otherAction.properties.getSourceSecurityGroupOwnerId())) { 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_INGRESS_RULE { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSEC2SecurityGroupIngressResourceAction action = (AWSEC2SecurityGroupIngressResourceAction) resourceAction; ServiceConfiguration configuration = Topology.lookup(Compute.class); // property validation action.validateProperties(); // Make sure security group exists. if (!Strings.isNullOrEmpty(action.properties.getGroupId())) { 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()) { throw new ValidationErrorException("No such group with id '" + action.properties.getGroupId()+"'"); } } if (!Strings.isNullOrEmpty(action.properties.getGroupName())) { DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, action.info.getEffectiveUserId()); describeSecurityGroupsType.setSecurityGroupSet(Lists.newArrayList(action.properties.getGroupName())); describeSecurityGroupsType.setFilterSet( Lists.newArrayList( Filter.filter( "group-name", action.properties.getGroupName( ) ) ) ); DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType); ArrayList<SecurityGroupItemType> securityGroupItemTypeArrayList = describeSecurityGroupsResponseType.getSecurityGroupInfo(); if (securityGroupItemTypeArrayList == null || securityGroupItemTypeArrayList.isEmpty()) { throw new ValidationErrorException("No such group with name '" + action.properties.getGroupName() + "'"); } // may be multiple return values, make sure at least one with non-vpc boolean foundNonVpc = false; for (SecurityGroupItemType securityGroupItemType : securityGroupItemTypeArrayList) { if (Strings.isNullOrEmpty(securityGroupItemType.getVpcId())) { foundNonVpc = true; break; } } if (!foundNonVpc) { throw new ValidationErrorException("Invalid value '" + action.properties.getGroupName() + "' for groupName. " + "You may not reference VPC security groups by name. Please use the corresponding id for this operation."); } } AuthorizeSecurityGroupIngressType authorizeSecurityGroupIngressType = MessageHelper.createMessage(AuthorizeSecurityGroupIngressType.class, action.info.getEffectiveUserId()); if (!Strings.isNullOrEmpty(action.properties.getGroupId())) { authorizeSecurityGroupIngressType.setGroupId(action.properties.getGroupId()); } if (!Strings.isNullOrEmpty(action.properties.getGroupName())) { authorizeSecurityGroupIngressType.setGroupName(action.properties.getGroupName()); } 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.getSourceSecurityGroupId())) { // Generally no need for SourceSecurityGroupOwnerId if SourceSecurityGroupId is set, but pass it along if set ipPermissionType.setGroups(Lists.newArrayList(new UserIdGroupPairType(action.properties.getSourceSecurityGroupOwnerId(), null, action.properties.getSourceSecurityGroupId()))); } if (!Strings.isNullOrEmpty(action.properties.getSourceSecurityGroupName())) { // I think SourceSecurityGroupOwnerId is needed here. If not provided, use the local account id String sourceSecurityGroupOwnerId = action.properties.getSourceSecurityGroupOwnerId(); if (Strings.isNullOrEmpty(sourceSecurityGroupOwnerId)) { sourceSecurityGroupOwnerId = action.stackEntity.getAccountId(); } ipPermissionType.setGroups(Lists.newArrayList(new UserIdGroupPairType(sourceSecurityGroupOwnerId, action.properties.getSourceSecurityGroupName(), null))); } authorizeSecurityGroupIngressType.setIpPermissions(Lists.newArrayList(ipPermissionType)); AuthorizeSecurityGroupIngressResponseType authorizeSecurityGroupIngressResponseType = AsyncRequests.<AuthorizeSecurityGroupIngressType, AuthorizeSecurityGroupIngressResponseType> sendSync(configuration, authorizeSecurityGroupIngressType); action.info.setPhysicalResourceId(action.info.getLogicalResourceId()); // Strange, but this is what AWS does in this particular case... 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_INGRESS_RULE { @Override public ResourceAction perform(ResourceAction resourceAction) throws Exception { AWSEC2SecurityGroupIngressResourceAction action = (AWSEC2SecurityGroupIngressResourceAction) 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. if (!Strings.isNullOrEmpty(action.properties.getGroupId())) { 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 } } if (!Strings.isNullOrEmpty(action.properties.getGroupName())) { DescribeSecurityGroupsType describeSecurityGroupsType = MessageHelper.createMessage(DescribeSecurityGroupsType.class, action.info.getEffectiveUserId()); describeSecurityGroupsType.setFilterSet( Lists.newArrayList( Filter.filter( "group-name", action.properties.getGroupName( ) ) ) ); DescribeSecurityGroupsResponseType describeSecurityGroupsResponseType = AsyncRequests.sendSync(configuration, describeSecurityGroupsType); ArrayList<SecurityGroupItemType> securityGroupItemTypeArrayList = describeSecurityGroupsResponseType.getSecurityGroupInfo(); if (securityGroupItemTypeArrayList == null || securityGroupItemTypeArrayList.isEmpty()) { return action; // no group } // may be multiple return values, make sure at least one with non-vpc boolean foundNonVpc = false; for (SecurityGroupItemType securityGroupItemType : securityGroupItemTypeArrayList) { if (Strings.isNullOrEmpty(securityGroupItemType.getVpcId())) { foundNonVpc = true; break; } } if (!foundNonVpc) { return action; // no (non-vpc) group } } RevokeSecurityGroupIngressType revokeSecurityGroupIngressType = MessageHelper.createMessage(RevokeSecurityGroupIngressType.class, action.info.getEffectiveUserId()); if (!Strings.isNullOrEmpty(action.properties.getGroupId())) { revokeSecurityGroupIngressType.setGroupId(action.properties.getGroupId()); } if (!Strings.isNullOrEmpty(action.properties.getGroupName())) { revokeSecurityGroupIngressType.setGroupName(action.properties.getGroupName()); } 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.getSourceSecurityGroupId())) { // Generally no need for SourceSecurityGroupOwnerId if SourceSecurityGroupId is set, but pass it along if set ipPermissionType.setGroups(Lists.newArrayList(new UserIdGroupPairType(action.properties.getSourceSecurityGroupOwnerId(), null, action.properties.getSourceSecurityGroupId()))); } if (!Strings.isNullOrEmpty(action.properties.getSourceSecurityGroupName())) { // I think SourceSecurityGroupOwnerId is needed here. If not provided, use the local account id String sourceSecurityGroupOwnerId = action.properties.getSourceSecurityGroupOwnerId(); if (Strings.isNullOrEmpty(sourceSecurityGroupOwnerId)) { sourceSecurityGroupOwnerId = action.stackEntity.getAccountId(); } ipPermissionType.setGroups(Lists.newArrayList(new UserIdGroupPairType(sourceSecurityGroupOwnerId, action.properties.getSourceSecurityGroupName(), null))); } revokeSecurityGroupIngressType.setIpPermissions(Lists.newArrayList(ipPermissionType)); RevokeSecurityGroupIngressResponseType revokeSecurityGroupIngressResponseType = AsyncRequests.<RevokeSecurityGroupIngressType, RevokeSecurityGroupIngressResponseType> sendSync(configuration, revokeSecurityGroupIngressType); return action; } }; @Nullable @Override public Integer getTimeout() { return null; } } @Override public ResourceProperties getResourceProperties() { return properties; } @Override public void setResourceProperties(ResourceProperties resourceProperties) { properties = (AWSEC2SecurityGroupIngressProperties) resourceProperties; } @Override public ResourceInfo getResourceInfo() { return info; } @Override public void setResourceInfo(ResourceInfo resourceInfo) { info = (AWSEC2SecurityGroupIngressResourceInfo) resourceInfo; } private void validateProperties() throws ValidationErrorException { // group id or group name must be set if (Strings.isNullOrEmpty(properties.getGroupId()) && Strings.isNullOrEmpty(properties.getGroupName())) { throw new ValidationErrorException("Exactly one of GroupName and GroupId must be specified"); } // but not both if (!Strings.isNullOrEmpty(properties.getGroupId()) && !Strings.isNullOrEmpty(properties.getGroupName())) { throw new ValidationErrorException("Exactly one of GroupName and GroupId must be specified"); } // Can't specify cidr and source security group if (!Strings.isNullOrEmpty(properties.getCidrIp()) && (!Strings.isNullOrEmpty(properties.getSourceSecurityGroupId()) || !Strings.isNullOrEmpty(properties.getSourceSecurityGroupName()) || !Strings.isNullOrEmpty(properties.getSourceSecurityGroupOwnerId()))) { throw new ValidationErrorException("Both CidrIp and SourceSecurityGroup cannot be specified"); } // Can't specify both source security group name and id if (!Strings.isNullOrEmpty(properties.getSourceSecurityGroupId()) && !Strings.isNullOrEmpty(properties.getSourceSecurityGroupName())) { throw new ValidationErrorException("Both SourceSecurityGroupName and SourceSecurityGroupId cannot be specified"); } } }