/************************************************************************* * Copyright 2009-2016 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. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.network; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.NoSuchElementException; import javax.annotation.Nullable; import javax.persistence.EntityNotFoundException; import com.eucalyptus.auth.principal.AccountFullName; import com.eucalyptus.auth.principal.UserFullName; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.compute.ClientUnauthorizedComputeException; import com.eucalyptus.compute.ComputeException; import com.eucalyptus.compute.common.CloudMetadatas; import com.eucalyptus.compute.common.internal.network.NetworkGroup; import com.eucalyptus.compute.common.internal.network.NetworkRule; import com.eucalyptus.compute.common.internal.util.MetadataConstraintException; import com.eucalyptus.compute.common.internal.util.MetadataException; import com.eucalyptus.compute.common.internal.util.NoSuchMetadataException; import com.eucalyptus.compute.ClientComputeException; import com.eucalyptus.compute.common.IpPermissionType; import com.eucalyptus.compute.common.UserIdGroupPairType; import com.eucalyptus.compute.common.internal.identifier.InvalidResourceIdentifier; import com.eucalyptus.compute.common.internal.identifier.ResourceIdentifiers; import com.eucalyptus.compute.common.internal.vpc.Vpc; import com.eucalyptus.compute.vpc.VpcConfiguration; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionException; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.records.Logs; import com.eucalyptus.util.EucalyptusCloudException; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.RestrictedTypes; import com.eucalyptus.util.Strings; import com.eucalyptus.ws.EucalyptusWebServiceException; import com.google.common.base.CharMatcher; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.eucalyptus.compute.common.backend.AuthorizeSecurityGroupEgressResponseType; import com.eucalyptus.compute.common.backend.AuthorizeSecurityGroupEgressType; import com.eucalyptus.compute.common.backend.AuthorizeSecurityGroupIngressResponseType; import com.eucalyptus.compute.common.backend.AuthorizeSecurityGroupIngressType; import com.eucalyptus.compute.common.backend.CreateSecurityGroupResponseType; import com.eucalyptus.compute.common.backend.CreateSecurityGroupType; import com.eucalyptus.compute.common.backend.DeleteSecurityGroupResponseType; import com.eucalyptus.compute.common.backend.DeleteSecurityGroupType; import com.eucalyptus.compute.common.backend.RevokeSecurityGroupEgressResponseType; import com.eucalyptus.compute.common.backend.RevokeSecurityGroupEgressType; import com.eucalyptus.compute.common.backend.RevokeSecurityGroupIngressResponseType; import com.eucalyptus.compute.common.backend.RevokeSecurityGroupIngressType; @ComponentNamed("computeNetworkGroupManager") public class NetworkGroupManager { public CreateSecurityGroupResponseType create( final CreateSecurityGroupType request ) throws EucalyptusCloudException, MetadataException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName(); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final String groupName = request.getGroupName( ); final String groupDescription = request.getGroupDescription( ); if ( Strings.startsWith( "sg-" ).apply( groupName ) ) { throw new ClientComputeException("InvalidParameterValue", "Value ("+groupName+") for parameter GroupName is invalid. Group names may not be in the format sg-*" ); } final String vpcId = Optional.fromNullable( ResourceIdentifiers.tryNormalize( ).apply( request.getVpcId( ) ) ) .or( Optional.fromNullable( getDefaultVpcId( accountFullName ) ) ).orNull( ); if ( vpcId == null && !CharMatcher.ASCII.matchesAllOf(groupName) ) { throw new ClientComputeException("InvalidParameterValue", "Value ("+groupName+") for parameter GroupName is invalid. Character sets beyond ASCII are not supported."); } else if ( vpcId != null && !NetworkGroups.VPC_GROUP_NAME_PATTERN.matcher( groupName ).matches( ) ) { throw new ClientComputeException("InvalidParameterValue", "Invalid security group name. Valid names are non-empty strings less than 256 characters from the following set: a-zA-Z0-9. _-:/()#,@[]+=&;{}!$*"); } if ( vpcId == null && !CharMatcher.ASCII.matchesAllOf( groupDescription ) ) { throw new ClientComputeException("InvalidParameterValue", "Value ("+groupDescription+") for parameter GroupDescription is invalid. Character sets beyond ASCII are not supported."); } else if ( vpcId != null && !NetworkGroups.VPC_GROUP_DESC_PATTERN.matcher( groupDescription ).matches( ) ) { throw new ClientComputeException("InvalidParameterValue", "Invalid security group description. Valid descriptions are strings less than 256 characters from the following set: a-zA-Z0-9. _-:/()#,@[]+=&;{}!$*"); } final CreateSecurityGroupResponseType reply = request.getReply( ); try { Supplier<NetworkGroup> allocator = new Supplier<NetworkGroup>( ) { @Override public NetworkGroup get( ) { try ( final TransactionResource tx = Entities.transactionFor( NetworkGroup.class ) ) { if ( vpcId != null && Entities.count( NetworkGroup.namedForVpc( vpcId, null ) ) >= VpcConfiguration.getSecurityGroupsPerVpc( ) ) { throw Exceptions.toUndeclared( new ClientComputeException( "SecurityGroupLimitExceeded", "Security group limit exceeded for " + vpcId ) ); } final Vpc vpc = vpcId == null ? null : Entities.uniqueResult( Vpc.exampleWithName( userFullName.asAccountFullName( ), vpcId ) ); final NetworkGroup group = NetworkGroups.create( ctx.getUserFullName( ), vpc, groupName, groupDescription ); if ( vpc != null ) { group.addNetworkRules( Lists.newArrayList( NetworkRule.createEgress( null/*protocol name*/, -1, null/*low port*/, null/*high port*/, null/*peers*/, Collections.singleton( "0.0.0.0/0" ) ) ) ); } tx.commit(); return group; } catch ( NoSuchElementException e ) { throw Exceptions.toUndeclared( new ClientComputeException( "InvalidVpcID.NotFound", "The vpc ('"+vpcId+"') was not found" ) ); } catch ( TransactionException | MetadataException ex ) { throw Exceptions.toUndeclared( ex ); } } }; final NetworkGroup group = RestrictedTypes.allocateUnitlessResource( allocator ); reply.setGroupId( group.getGroupId( ) ); return reply; } catch ( final Exception ex ) { Exceptions.findAndRethrow( ex, ComputeException.class ); String cause = Exceptions.causeString( ex ); if ( cause.contains( "DuplicateMetadataException" ) ) throw new ClientComputeException( "InvalidGroup.Duplicate", "The security group '" + groupName + "' already exists" ); else throw new EucalyptusCloudException( "CreateSecurityGroup failed because: " + cause, ex ); } } public DeleteSecurityGroupResponseType delete( final DeleteSecurityGroupType request ) throws EucalyptusCloudException, MetadataException { final Context ctx = Contexts.lookup( ); final DeleteSecurityGroupResponseType reply = request.getReply( ); final NetworkGroup group = lookupGroup( request.getGroupId(), request.getGroupName() ); if ( !RestrictedTypes.filterPrivileged( ).apply( group ) ) { throw new ClientUnauthorizedComputeException( "Not authorized to delete network group " + group.getDisplayName() + " for " + ctx.getUser( ).getName( ) ); } if ( NetworkGroups.defaultNetworkName( ).equals( group.getDisplayName( ) ) ) { if ( group.getVpcId( ) != null ) { throw new ClientComputeException( "CannotDelete", "Group ("+group.getGroupId()+") cannot be deleted, it is the default group for " + group.getVpcId( ) ); } else { throw new ClientComputeException( "InvalidGroup.Reserved", "The security group 'default' is reserved" ); } } if ( NetworkGroups.defaultNetworkName( ).equals( group.getDisplayName( ) ) ) { NetworkGroups.createDefault( AccountFullName.getInstance( group.getOwnerAccountNumber( ) ) ); } try { NetworkGroups.delete( group.getGroupId( ) ); } catch ( MetadataConstraintException e ) { throw new ClientComputeException( group.getVpcId( ) != null ? "DependencyViolation" : "InvalidGroup.InUse", "Specified group cannot be deleted because it is in use." ); } return reply; } public RevokeSecurityGroupIngressResponseType revokeSecurityGroupIngress( final RevokeSecurityGroupIngressType request ) throws EucalyptusCloudException { final RevokeSecurityGroupIngressResponseType reply = request.getReply( ).markFailed( ); final Context ctx = Contexts.lookup( ); final Predicate<Void> updateFunction = new Predicate<Void>( ) { @Override public boolean apply( @Nullable final Void aVoid ) { try { final NetworkGroup ruleGroup = lookupGroup( request.getGroupId(), request.getGroupName() ); final List<IpPermissionType> ipPermissions = handleOldAndNewIpPermissions( ruleGroup.getVpcId( ) != null, request.getCidrIp(), request.getIpProtocol(), request.getFromPort(), request.getToPort(), request.getSourceSecurityGroupName(), request.getSourceSecurityGroupOwnerId(), request.getIpPermissions()); if ( RestrictedTypes.filterPrivileged().apply( ruleGroup ) ) { try { NetworkGroups.resolvePermissions( ipPermissions, ctx.getUser( ).getAccountNumber( ), ruleGroup.getVpcId( ), true ); if ( Iterators.removeAll( // iterator used to work around broken equals/hashCode in NetworkRule ruleGroup.getNetworkRules( ).iterator( ), NetworkGroups.ipPermissionsAsNetworkRules( ipPermissions, ruleGroup.getVpcId( ) != null, true ) ) ) { ruleGroup.updateTimeStamps( ); } } catch ( IllegalArgumentException e ) { throw new ClientComputeException( "InvalidPermission.Malformed", e.getMessage( ) ); } catch ( NoSuchMetadataException e ) { throw new ClientComputeException( "InvalidGroup.NotFound", e.getMessage( ) ); } } else { throw new ClientUnauthorizedComputeException( "Not authorized to revoke network group " + request.getGroupName() + " for " + ctx.getUser().getName()); } } catch ( final EntityNotFoundException e ) { // Another rule may have been deleted, retry throw new Entities.RetryTransactionException( e ); } catch ( final Exception e ) { throw Exceptions.toUndeclared( e ); } return true; } }; try { reply.set_return( Entities.asDistinctTransaction( NetworkGroup.class, updateFunction ).apply( null ) ); NetworkGroups.flushRules(); } catch ( Exception ex ) { Exceptions.findAndRethrow( ex, EucalyptusCloudException.class, EucalyptusWebServiceException.class ); Logs.exhaust( ).error( ex, ex ); throw new EucalyptusCloudException( "RevokeSecurityGroupIngress failed because: " + ex.getMessage( ), ex ); } return reply; } public AuthorizeSecurityGroupIngressResponseType authorizeSecurityGroupIngress( final AuthorizeSecurityGroupIngressType request ) throws Exception { final AuthorizeSecurityGroupIngressResponseType reply = request.getReply( ); final Context ctx = Contexts.lookup( ); final Predicate<Void> updateFunction = new Predicate<Void>( ) { @Override public boolean apply( @Nullable final Void aVoid ) { try { final NetworkGroup ruleGroup = lookupGroup( request.getGroupId(), request.getGroupName() ); if ( !RestrictedTypes.filterPrivileged( ).apply( ruleGroup ) ) { throw new ClientUnauthorizedComputeException( "Not authorized to authorize network group " + ruleGroup.getDisplayName() + " for " + ctx.getUser( ).getName() ); } final List<NetworkRule> ruleList = Lists.newArrayList( ); final List<IpPermissionType> ipPermissions = handleOldAndNewIpPermissions( ruleGroup.getVpcId( ) != null, request.getCidrIp(), request.getIpProtocol(), request.getFromPort(), request.getToPort(), request.getSourceSecurityGroupName(), request.getSourceSecurityGroupOwnerId(), request.getIpPermissions()); try { NetworkGroups.resolvePermissions( ipPermissions, ctx.getUser().getAccountNumber(), ruleGroup.getVpcId(), false ); } catch ( final NoSuchMetadataException e ) { throw new ClientComputeException( "InvalidGroup.NotFound", e.getMessage( ) ); } for ( final IpPermissionType ipPerm : ipPermissions ) { if ( ipPerm.getCidrIpRanges().isEmpty() && ipPerm.getGroups().isEmpty() ) { continue; // see EUCA-5934 } if ((ipPerm.getIpProtocol( ).equals( "icmp" ) || (ipPerm.getIpProtocol( ).equals( "1" ))) && ( MoreObjects.firstNonNull( ipPerm.getFromPort( ), 0 ) > 255 || MoreObjects.firstNonNull( ipPerm.getToPort( ), 0 ) > 255)) { throw new ClientComputeException( "InvalidParameterValue", "ICMP code (" + ( ( MoreObjects.firstNonNull( ipPerm.getFromPort( ), 0 ) > 255 ) ? ipPerm.getFromPort( ) : ipPerm.getToPort( ) ) + ") " + "out of range. Supported range for ICMP code and type is 0 to 255" ); } if ( ipPerm.getIpProtocol( ) != null && !NetworkRule.PROTOCOL_PATTERN.matcher( ipPerm.getIpProtocol( ) ).matches( ) ) { throw new ClientComputeException("InvalidPermission.Malformed", "Invalid protocol ("+ipPerm.getIpProtocol( )+")" ); } try { final List<NetworkRule> rules = NetworkGroups.ipPermissionAsNetworkRules( ipPerm, ruleGroup.getVpcId( ) != null, false ); ruleList.addAll( rules ); } catch ( final IllegalArgumentException ex ) { throw new ClientComputeException("InvalidPermission.Malformed", ex.getMessage( ) ); } } if ( Iterables.any( ruleGroup.getNetworkRules( ), new Predicate<NetworkRule>( ) { @Override public boolean apply( final NetworkRule rule ) { return Iterables.any( ruleList, Predicates.equalTo( rule ) ); } } ) ) { return false; } else { ruleGroup.addNetworkRules( ruleList ); if ( ruleGroup.getVpcId() != null && ruleGroup.getNetworkRules().size() > VpcConfiguration.getRulesPerSecurityGroup() ) { throw new ClientComputeException( "RulesPerSecurityGroupLimitExceeded", "Rules limit exceeded for " + request.getGroupId() ); } } } catch ( final Exception e ) { throw Exceptions.toUndeclared( e ); } return true; } }; try { reply.set_return( Entities.asDistinctTransaction( NetworkGroup.class, updateFunction ).apply( null ) ); NetworkGroups.flushRules( ); } catch ( Exception ex ) { Exceptions.findAndRethrow( ex, EucalyptusCloudException.class, EucalyptusWebServiceException.class ); Logs.exhaust( ).error( ex, ex ); throw ex; } return reply; } public AuthorizeSecurityGroupEgressResponseType authorizeSecurityGroupEgress(final AuthorizeSecurityGroupEgressType request) throws Exception { final AuthorizeSecurityGroupEgressResponseType reply = request.getReply(); final Context ctx = Contexts.lookup( ); final Predicate<Void> updateFunction = new Predicate<Void>( ) { @Override public boolean apply( @Nullable final Void aVoid ) { try { final NetworkGroup ruleGroup = lookupGroup( request.getGroupId(), null ); if ( !RestrictedTypes.filterPrivileged( ).apply( ruleGroup ) ) { throw new ClientUnauthorizedComputeException( "Not authorized to authorize network group " + ruleGroup.getDisplayName() + " for " + ctx.getUser( ).getName() ); } if ( ruleGroup.getVpcId( ) == null ) { throw new ClientComputeException( "InvalidGroup.NotFound", "VPC security group ("+request.getGroupId()+") not found" ); } final List<NetworkRule> ruleList = Lists.newArrayList( ); final List<IpPermissionType> ipPermissions = request.getIpPermissions( ); try { NetworkGroups.resolvePermissions( ipPermissions, ctx.getUser().getAccountNumber(), ruleGroup.getVpcId(), false ); } catch ( final NoSuchMetadataException e ) { throw new ClientComputeException( "InvalidGroup.NotFound", e.getMessage( ) ); } for ( final IpPermissionType ipPerm : ipPermissions ) { if ( ipPerm.getCidrIpRanges().isEmpty() && ipPerm.getGroups().isEmpty() ) { continue; // see EUCA-5934 } if ((ipPerm.getIpProtocol( ).equals( "icmp" ) || (ipPerm.getIpProtocol( ).equals( "1" ))) && ( MoreObjects.firstNonNull( ipPerm.getFromPort( ), 0 ) > 255 || MoreObjects.firstNonNull( ipPerm.getToPort( ), 0 ) > 255)) { throw new ClientComputeException( "InvalidParameterValue", "ICMP code (" + ( ( MoreObjects.firstNonNull( ipPerm.getFromPort( ), 0 ) > 255 ) ? ipPerm.getFromPort( ) : ipPerm.getToPort( ) ) + ") " + "out of range. Supported range for ICMP code and type is 0 to 255" ); } if ( ipPerm.getIpProtocol( ) == null || !NetworkRule.PROTOCOL_PATTERN.matcher( ipPerm.getIpProtocol( ) ).matches( ) ) { throw new ClientComputeException("InvalidPermission.Malformed", "Invalid protocol ("+ipPerm.getIpProtocol( )+")" ); } try { final List<NetworkRule> rules = NetworkGroups.ipPermissionAsNetworkRules( ipPerm, ruleGroup.getVpcId( ) != null, false ); for ( final NetworkRule rule : rules ) rule.setEgress( true ); ruleList.addAll( rules ); } catch ( final IllegalArgumentException ex ) { throw new ClientComputeException("InvalidPermission.Malformed", ex.getMessage( ) ); } } if ( Iterables.any( ruleGroup.getNetworkRules( ), new Predicate<NetworkRule>( ) { @Override public boolean apply( final NetworkRule rule ) { return Iterables.any( ruleList, Predicates.equalTo( rule ) ); } } ) ) { return false; } else { ruleGroup.addNetworkRules( ruleList ); if ( ruleGroup.getVpcId( ) != null && ruleGroup.getNetworkRules( ).size( ) > VpcConfiguration.getRulesPerSecurityGroup( ) ) { throw new ClientComputeException("RulesPerSecurityGroupLimitExceeded", "Rules limit exceeded for " + request.getGroupId( ) ); } } } catch ( final Exception e ) { throw Exceptions.toUndeclared( e ); } return true; } }; try { reply.set_return( Entities.asDistinctTransaction( NetworkGroup.class, updateFunction ).apply( null ) ); NetworkGroups.flushRules(); } catch ( Exception ex ) { Exceptions.findAndRethrow( ex, EucalyptusCloudException.class, EucalyptusWebServiceException.class ); Logs.exhaust( ).error( ex, ex ); throw ex; } return reply; } public RevokeSecurityGroupEgressResponseType revokeSecurityGroupEgress(final RevokeSecurityGroupEgressType request) throws EucalyptusCloudException { final RevokeSecurityGroupEgressResponseType reply = request.getReply( ).markFailed( ); final Context ctx = Contexts.lookup( ); final Predicate<Void> updateFunction = new Predicate<Void>( ) { @Override public boolean apply( @Nullable final Void aVoid ) { try { final NetworkGroup ruleGroup = lookupGroup( request.getGroupId(), null ); final List<IpPermissionType> ipPermissions = request.getIpPermissions( ); if ( RestrictedTypes.filterPrivileged().apply( ruleGroup ) ) { if ( ruleGroup.getVpcId( ) == null ) { throw new ClientComputeException( "InvalidGroup.NotFound", "VPC security group ("+request.getGroupId()+") not found" ); } try { final List<NetworkRule> rules = NetworkGroups.ipPermissionsAsNetworkRules( ipPermissions, true, true ); for ( final NetworkRule rule : rules ) rule.setEgress( true ); NetworkGroups.resolvePermissions( ipPermissions, ctx.getUser( ).getAccountNumber( ), ruleGroup.getVpcId( ), true ); if ( Iterators.removeAll( // iterator used to work around broken equals/hashCode in NetworkRule ruleGroup.getNetworkRules( ).iterator( ), rules ) ) { ruleGroup.updateTimeStamps( ); } } catch ( IllegalArgumentException e ) { throw new ClientComputeException( "InvalidPermission.Malformed", e.getMessage( ) ); } catch ( NoSuchMetadataException e ) { throw new ClientComputeException( "InvalidGroup.NotFound", e.getMessage( ) ); } } else { throw new ClientUnauthorizedComputeException( "Not authorized to revoke network group " + request.getGroupId() + " for " + ctx.getUser().getName()); } } catch ( final EntityNotFoundException e ) { // Another rule may have been deleted, retry throw new Entities.RetryTransactionException( e ); } catch ( final Exception e ) { throw Exceptions.toUndeclared( e ); } return true; } }; try { reply.set_return( Entities.asDistinctTransaction( NetworkGroup.class, updateFunction ).apply( null ) ); NetworkGroups.flushRules(); } catch ( Exception ex ) { Exceptions.findAndRethrow( ex, EucalyptusCloudException.class, EucalyptusWebServiceException.class ); Logs.exhaust( ).error( ex, ex ); throw new EucalyptusCloudException( "RevokeSecurityGroupEgress failed because: " + ex.getMessage( ), ex ); } return reply; } private List<IpPermissionType> handleOldAndNewIpPermissions( final boolean isVpcGroup, String cidrIp, String ipProtocol, Integer fromPort, Integer toPort, String sourceSecurityGroupName, String sourceSecurityGroupOwnerId, ArrayList<IpPermissionType> ipPermissions) throws MetadataException { // TODO: match AWS error messages (whenever possible) // Due to old api calls there are three possible (allowed) scenarios // 1) cidrIp, ip protocol, from port, to port must all be set // 2) sourceSecurityGroupName and sourceSecurityGroupOwnerId must be set // 3) ipPermissions must be set (size at least 1. // Exactly one of the above must be set, and no fields from any other condition must be set. // Easiest to start with condition 3 HashMap<String, Object> condition1Params = Maps.newHashMap( ); condition1Params.put("cidrIp", cidrIp); condition1Params.put("ipProtocol", ipProtocol); if ( !isVpcGroup ) { condition1Params.put("fromPort", fromPort); condition1Params.put("toPort", toPort); } HashMap<String, Object> condition2Params = Maps.newHashMap( ); condition2Params.put("sourceSecurityGroupName", sourceSecurityGroupName); condition2Params.put("sourceSecurityGroupOwnerId", sourceSecurityGroupOwnerId); if (ipPermissions != null && ipPermissions.size() > 0) { for (String key: condition1Params.keySet()) { Object value = condition1Params.get(key); if (value != null) { throw new MetadataException("InvalidParameterCombination: " + key + " and ipPermissions must not both be set"); } } for (String key: condition2Params.keySet()) { Object value = condition2Params.get(key); if (value != null) { throw new MetadataException("InvalidParameterCombination: " + key + " and ipPermissions must not both be set"); } } return ipPermissions; } // now check 2 String unsetCondition2Key = null; String setCondition2Key = null; for (String key: condition2Params.keySet()) { Object value = condition2Params.get(key); if (value == null && unsetCondition2Key == null) { unsetCondition2Key = key; } else if (value != null && setCondition2Key == null) { setCondition2Key = key; } } if (setCondition2Key != null) { if (unsetCondition2Key != null) { throw new MetadataException( "MissingParameter: " + unsetCondition2Key + " must be set if " + setCondition2Key + " is set." ); } else if ( isVpcGroup ) { throw new MetadataException( "MissingParameter: IpProtocol" ); } else { // both conditions are set, make sure no condition 1 items are set... for (String key: condition1Params.keySet()) { Object value = condition1Params.get(key); if (value != null) { throw new MetadataException("InvalidParameterCombination: " + key + " and " + setCondition2Key + " must not both be set"); } } // set a rule for tcp:1-65535, udp:1-65535, icmp: -1 IpPermissionType tcpPermission = new IpPermissionType("tcp", 1, 65535); tcpPermission.setGroups(Lists.newArrayList(new UserIdGroupPairType(sourceSecurityGroupOwnerId, sourceSecurityGroupName, null))); IpPermissionType udpPermission = new IpPermissionType("udp", 1, 65535); udpPermission.setGroups(Lists.newArrayList(new UserIdGroupPairType(sourceSecurityGroupOwnerId, sourceSecurityGroupName, null))); IpPermissionType icmpPermission = new IpPermissionType("icmp", -1, -1); icmpPermission.setGroups(Lists.newArrayList(new UserIdGroupPairType(sourceSecurityGroupOwnerId, sourceSecurityGroupName, null))); return Lists.newArrayList(tcpPermission, udpPermission, icmpPermission); } } // now in condition 1. make sure all fields set. // now check 2 String unsetCondition1Key = null; String setCondition1Key = null; for (String key: condition1Params.keySet()) { Object value = condition1Params.get(key); if (value == null && unsetCondition1Key == null) { unsetCondition1Key = key; } else if (value != null && setCondition1Key == null) { setCondition1Key = key; } } if (setCondition1Key != null) { if (unsetCondition1Key != null) { throw new MetadataException("MissingParameter: " + unsetCondition1Key + " must be set if " + setCondition1Key + " is set."); } else { // we have everything we need IpPermissionType permission = new IpPermissionType(ipProtocol, fromPort, toPort); permission.setCidrIpRanges(Lists.newArrayList(cidrIp)); return Lists.newArrayList(permission); } } throw new MetadataException("Missing source specification: include source security group or CIDR information"); } /** * Caller must perform authorization checks */ private static NetworkGroup lookupGroup( final String groupId, final String groupName ) throws EucalyptusCloudException, MetadataException { final Context ctx = Contexts.lookup( ); final AccountFullName lookUpGroupAccount = ctx.getUserFullName( ).asAccountFullName(); try ( final TransactionResource tx = Entities.transactionFor( NetworkGroup.class ) ){ if ( groupName != null ) { final String defaultVpcId = getDefaultVpcId( lookUpGroupAccount ); if ( defaultVpcId != null ) { return NetworkGroups.lookup( lookUpGroupAccount, defaultVpcId, groupName ); } else { return NetworkGroups.lookup( lookUpGroupAccount, groupName ); } } else if ( groupId != null ) { return NetworkGroups.lookupByGroupId( ctx.isAdministrator( ) ? null : lookUpGroupAccount, normalizeGroupIdentifier( groupId ) ); } else { throw new EucalyptusCloudException( "Group id or name required" ); } } catch ( NoSuchMetadataException e ) { throw new ClientComputeException( "InvalidGroup.NotFound", String.format( "The security group '%s' does not exist", Objects.firstNonNull( groupName, groupId ) ) ); } } private static String getDefaultVpcId( final AccountFullName accountFullName ) { try ( final TransactionResource tx = Entities.transactionFor( Vpc.class ) ) { return Iterables.tryFind( Entities.query( Vpc.exampleDefault( accountFullName ) ), Predicates.alwaysTrue() ).transform( CloudMetadatas.toDisplayName() ).orNull(); } } private static String normalizeIdentifier( final String identifier, final String prefix, final boolean required, final String message ) throws ClientComputeException { try { return com.google.common.base.Strings.emptyToNull( identifier ) == null && !required ? null : ResourceIdentifiers.parse( prefix, identifier ).getIdentifier( ); } catch ( final InvalidResourceIdentifier e ) { throw new ClientComputeException( "InvalidGroupId.Malformed", String.format( message, e.getIdentifier( ) ) ); } } private static String normalizeGroupIdentifier( final String identifier ) throws EucalyptusCloudException { return normalizeIdentifier( identifier, NetworkGroup.ID_PREFIX, true, "Invalid id: \"%s\" (expecting \"sg-...\")" ); } }