/************************************************************************* * 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.compute.common.internal.network; import static com.eucalyptus.upgrade.Upgrades.Version.v4_2_0; import java.util.Collection; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EntityTransaction; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Index; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.PersistenceContext; import javax.persistence.PrePersist; import javax.persistence.PreUpdate; import javax.persistence.Table; import org.apache.log4j.Logger; import org.hibernate.criterion.Restrictions; import com.eucalyptus.component.id.Eucalyptus; import com.eucalyptus.compute.common.config.ExtendedNetworkingConfiguration; import com.eucalyptus.entities.AbstractPersistent; import com.eucalyptus.entities.AuxiliaryDatabaseObject; import com.eucalyptus.entities.AuxiliaryDatabaseObjects; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.TransactionResource; import com.eucalyptus.upgrade.Upgrades; import com.eucalyptus.util.Pair; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import groovy.sql.Sql; @Entity @AuxiliaryDatabaseObjects({ @AuxiliaryDatabaseObject( dialect = "org.hibernate.dialect.PostgreSQLDialect", create = "create index metadata_group_ips_rule_id_idx on ${schema}.metadata_network_rule_ip_ranges ( networkrule_id )", drop = "drop index if exists ${schema}.metadata_group_ips_rule_id_idx" ), @AuxiliaryDatabaseObject( dialect = "org.hibernate.dialect.PostgreSQLDialect", create = "create index metadata_group_peers_rule_id_idx on ${schema}.metadata_network_group_rule_peers ( networkrule_id )", drop = "drop index if exists ${schema}.metadata_group_peers_rule_id_idx" ), }) @PersistenceContext( name = "eucalyptus_cloud" ) @Table( name = "metadata_network_rule", indexes = { @Index( name = "metadata_network_group_rule_fk_idx", columnList = "metadata_network_group_rule_fk" ) } ) public class NetworkRule extends AbstractPersistent { public static final Pattern PROTOCOL_PATTERN = Pattern.compile( "icmp|tcp|udp|[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]|-1" ); /** * */ public static final int RULE_MIN_PORT = 0; /** * */ public static final int RULE_MAX_PORT = 65535; public enum Protocol { icmp(1){ @Override public Integer extractLowPort ( final NetworkRule rule ) { return null; } @Override public Integer extractHighPort( final NetworkRule rule ) { return null; } @Override public Integer extractIcmpType( final NetworkRule rule ) { return rule.getLowPort( ); } @Override public Integer extractIcmpCode( final NetworkRule rule ) { return rule.getHighPort( ); } }, tcp(6), udp(17) ; private final int number; private Protocol( int number ) { this.number = number; } public int getNumber( ) { return number; } public static Protocol fromString( final String value ) { try { return Protocol.valueOf( value ); } catch ( IllegalArgumentException e ) { final Integer protocolNumber = Ints.tryParse( value ); if ( protocolNumber != null ) for ( final Protocol protocol : values( ) ) { if ( protocolNumber == protocol.number ) return protocol; } throw e; } } public Integer extractLowPort ( NetworkRule rule ) { return rule.getLowPort( ); } public Integer extractHighPort( NetworkRule rule ) { return rule.getHighPort( ); } public Integer extractIcmpType( NetworkRule rule ) { return null; } public Integer extractIcmpCode( NetworkRule rule ) { return null; } } private static final long serialVersionUID = 1L; @ManyToOne( optional = false ) @JoinColumn( name = "metadata_network_group_rule_fk", updatable = false, nullable = false ) private NetworkGroup group; @Column( name = "metadata_network_rule_egress", updatable = false ) private Boolean egress; @Enumerated( EnumType.STRING ) @Column( name = "metadata_network_rule_protocol", updatable = false ) private Protocol protocol; @Column( name = "metadata_network_rule_protocol_number" ) private Integer protocolNumber; @Column( name = "metadata_network_rule_low_port" ) private Integer lowPort; @Column( name = "metadata_network_rule_high_port" ) private Integer highPort; @ElementCollection @CollectionTable( name = "metadata_network_rule_ip_ranges" ) private Set<String> ipRanges = Sets.newHashSet( ); @ElementCollection @CollectionTable( name = "metadata_network_group_rule_peers" ) private Set<NetworkPeer> networkPeers = Sets.newHashSet( ); protected NetworkRule( ) {} protected NetworkRule( final Protocol protocol, final Integer protocolNumber, final Integer lowPort, final Integer highPort, final Collection<String> ipRanges, final Collection<NetworkPeer> peers ) { this.egress = false; this.protocol = protocol; this.protocolNumber = protocolNumber; if ( Protocol.tcp.equals( protocol ) || Protocol.udp.equals( protocol ) ) { if ( lowPort == null || highPort == null ) { throw new IllegalArgumentException( "Must specify both from and to ports with TCP/UDP." ); } else if ( lowPort < RULE_MIN_PORT || highPort < RULE_MIN_PORT ) { throw new IllegalArgumentException( "Provided ports must be greater than " + RULE_MIN_PORT + ": lowPort=" + lowPort + " highPort=" + highPort ); } else if ( lowPort > RULE_MAX_PORT || highPort > RULE_MAX_PORT ) { throw new IllegalArgumentException( "Provided ports must be less than " + RULE_MAX_PORT + ": lowPort=" + lowPort + " highPort=" + highPort ); } else if ( lowPort > highPort ) { throw new IllegalArgumentException( "Provided lowPort is greater than highPort: lowPort=" + lowPort + " highPort=" + highPort ); } } else if ( Protocol.icmp.equals( protocol ) && ( lowPort == null || highPort == null ) ) { throw new IllegalArgumentException( "Must specify both type and code for ICMP." ); } //Only allow ports for icmp, tcp, and udp. This is consistent with AWS EC2|VPC behavior if(this.protocol != null) { this.lowPort = lowPort; this.highPort = highPort; } if ( ipRanges != null ) { this.ipRanges.addAll( ipRanges ); } if ( peers != null ) { this.networkPeers.addAll( peers ); } } public static NetworkRule create( final Protocol protocol, final Integer lowPort, final Integer highPort, final Collection<NetworkPeer> peers, final Collection<String> ipRanges ) { return create( protocol, protocol.number, lowPort, highPort, peers, ipRanges ); } public static NetworkRule create( final Protocol protocol, final Integer protocolNumber, final Integer lowPort, final Integer highPort, final Collection<NetworkPeer> peers, final Collection<String> ipRanges ) { return new NetworkRule( protocol, protocolNumber, lowPort, highPort, ipRanges, peers ); } public static NetworkRule createEgress( final Protocol protocol, final Integer protocolNumber, final Integer lowPort, final Integer highPort, final Collection<NetworkPeer> peers, final Collection<String> ipRanges ) { final NetworkRule rule = new NetworkRule( protocol, protocolNumber, lowPort, highPort, ipRanges, peers ); rule.setEgress( true ); return rule; } public static NetworkRule create( final String protocolText, final boolean vpc, final Integer lowPort, final Integer highPort, final Collection<NetworkPeer> peers, final Collection<String> ipRanges ) { Pair<Optional<Protocol>,Integer> protocolPair = parseProtocol( protocolText, vpc ); return create( protocolPair.getLeft( ).orNull( ), protocolPair.getRight( ), lowPort, highPort, peers, ipRanges ); } protected static Pair<Optional<Protocol>,Integer> parseProtocol( final String protocolText, final boolean vpc ) { Protocol protocol = null; try { protocol = Protocol.fromString(protocolText); return Pair.lopair(protocol, protocol.getNumber()); } catch (final IllegalArgumentException e) { Integer protocolNumber = protocol != null ? protocol.getNumber() : Integer.parseInt(protocolText); if (vpc || ExtendedNetworkingConfiguration.isProtocolInExceptionList(protocolNumber)) { return Pair.lopair(protocol, protocolNumber); } else { throw new IllegalArgumentException("Invalid protocol " + protocolText, e); } } } public static NetworkRule named( ) { return new NetworkRule( ); } public NetworkGroup getGroup( ) { return group; } public void setGroup( final NetworkGroup group ) { this.group = group; } /** * @since 4.1 */ public boolean isEgress( ) { return egress == null ? false : egress; } @Nullable public Boolean getEgress( ) { return egress; } public void setEgress( final Boolean egress ) { this.egress = egress; } /** * Get the protocol for this rule. * * @return The protocol if named (e.g. icmp, tcp, udp), else null */ @Nullable public Protocol getProtocol( ) { return this.protocol; } public void setProtocol( final Protocol protocol ) { this.protocol = protocol; } /** * @since 4.1 */ @Nullable public Integer getProtocolNumber( ) { return protocolNumber; } public void setProtocolNumber( final Integer protocolNumber ) { this.protocolNumber = protocolNumber; } public String getDisplayProtocol( ) { return protocol != null ? protocol.name( ) : Objects.toString( protocolNumber, "-1" ); } @Nullable public Integer getLowPort( ) { return this.lowPort; } public void setLowPort( final Integer lowPort ) { this.lowPort = lowPort; } @Nullable public Integer getHighPort( ) { return this.highPort; } public void setHighPort( final Integer highPort ) { this.highPort = highPort; } public Set<String> getIpRanges( ) { return this.ipRanges; } public void setIpRanges( final Set<String> ipRanges ) { this.ipRanges = ipRanges; } public Set<NetworkPeer> getNetworkPeers( ) { return this.networkPeers; } public void setNetworkPeers( final Set<NetworkPeer> networkPeers ) { this.networkPeers = networkPeers; } public boolean isVpcOnly( ) { return (getLowPort() == null || getHighPort() == null || getProtocol() == null) && !ExtendedNetworkingConfiguration .isProtocolInExceptionList(getProtocolNumber()); } public static Predicate<NetworkRule> egress( ) { return NetworkRulePredicates.EGRESS; } @Override public int hashCode( ) { final int prime = 31; int result = super.hashCode( ); result = prime * result + ( ( this.highPort == null ) ? 0 : this.highPort.hashCode( ) ); result = prime * result + ( ( this.ipRanges == null ) ? 0 : this.ipRanges.hashCode( ) ); result = prime * result + ( ( this.lowPort == null ) ? 0 : this.lowPort.hashCode( ) ); result = prime * result + ( ( this.networkPeers == null ) ? 0 : this.networkPeers.hashCode( ) ); result = prime * result + ( ( this.protocol == null ) ? 0 : this.protocol.hashCode( ) ); result = prime * result + ( ( this.protocolNumber == null ) ? 0 : this.protocolNumber.hashCode( ) ); result = prime * result + ( ( this.egress == null || !this.egress) ? 0 : this.egress.hashCode( ) ); return result; } @Override public boolean equals( Object obj ) { if ( this == obj ) { return true; } if ( getClass( ) != obj.getClass( ) ) { return false; } NetworkRule other = ( NetworkRule ) obj; if ( this.highPort == null ) { if ( other.highPort != null ) { return false; } } else if ( !this.highPort.equals( other.highPort ) ) { return false; } if ( this.ipRanges == null ) { if ( other.ipRanges != null ) { return false; } } else if ( !this.ipRanges.equals( other.ipRanges ) ) { return false; } if ( this.lowPort == null ) { if ( other.lowPort != null ) { return false; } } else if ( !this.lowPort.equals( other.lowPort ) ) { return false; } if ( this.networkPeers == null ) { if ( other.networkPeers != null ) { return false; } } else if ( !this.networkPeers.equals( other.networkPeers ) ) { return false; } if ( this.protocol != other.protocol ) { return false; } if ( this.protocolNumber == null ) { if ( other.protocolNumber != null ) { return false; } } else if ( !this.protocolNumber.equals( other.protocolNumber ) ) { return false; } if ( this.isEgress( ) != other.isEgress( ) ) { return false; } return true; } @Override public String toString( ) { return String.format( "NetworkRule:%s:%d:%d:ipRanges=%s:networkPeers=%s:", this.protocol, this.lowPort, this.highPort, this.ipRanges, this.networkPeers ); } @PrePersist @PreUpdate protected void onUpdate( ) { if ( protocol != null ) protocolNumber = protocol.getNumber( ); } private enum NetworkRulePredicates implements Predicate<NetworkRule> { EGRESS { @Override public boolean apply( @Nullable final NetworkRule networkRule ) { return networkRule != null && networkRule.isEgress( ); } } } @Upgrades.PreUpgrade( value = Eucalyptus.class, since = v4_2_0 ) public static class NetworkRulePreUpgrade420 implements Callable<Boolean> { private static final Logger logger = Logger.getLogger( NetworkRulePreUpgrade420.class ); @Override public Boolean call( ) throws Exception { Sql sql = null; try { sql = Upgrades.DatabaseFilters.NEWVERSION.getConnection( "eucalyptus_cloud" ); sql.execute( "create index metadata_group_ips_rule_id_idx on metadata_network_rule_ip_ranges ( networkrule_id )" ); sql.execute( "create index metadata_group_peers_rule_id_idx on metadata_network_group_rule_peers ( networkrule_id )" ); } catch (Exception ex) { logger.error( "Error creating network rule indexes", ex ); } finally { if (sql != null) { sql.close(); } } return true; } } @Upgrades.EntityUpgrade( entities = NetworkRule.class, since = Upgrades.Version.v4_2_1, value = Eucalyptus.class) public enum NetworkRuleUpgrade421 implements Predicate<Class> { INSTANCE; private static Logger logger = Logger.getLogger( NetworkRuleUpgrade421.class ); @Override public boolean apply( Class arg0 ) { addProtocolNumberToRules( ); return true; } private void addProtocolNumberToRules( ) { try ( final TransactionResource tx = Entities.distinctTransactionFor( NetworkRule.class ) ) { for ( final NetworkRule rule : Entities.query( NetworkRule.named( ), false, Restrictions.and( Restrictions.isNotNull( "protocol" ), Restrictions.isNull( "protocolNumber" ) ), Collections.<String,String>emptyMap( ) ) ) { logger.info( "Updating protocol " + rule.getProtocol( ) + " for rule in group " + rule.getGroup( ).getGroupId( ) + "/" + rule.getGroup( ).getDisplayName( ) ); rule.setProtocolNumber( rule.getProtocol( ).getNumber( ) ); } tx.commit( ); } catch (Exception ex) { logger.error( "Error adding protocol numbers to rules", ex ); } } } @Upgrades.EntityUpgrade( entities = NetworkRule.class, since = Upgrades.Version.v4_3_0, value = Eucalyptus.class) public enum NetworkRuleUpgrade430 implements Predicate<Class> { INSTANCE; private static final Logger logger = Logger.getLogger( NetworkRuleUpgrade430.class ); @Override public boolean apply( Class arg0 ) { setPortsForIcmpRules( ); return true; } /** * Any imcp rules created without ports should have the values updated to -1 (any) */ private void setPortsForIcmpRules( ) { try ( final TransactionResource tx = Entities.distinctTransactionFor( NetworkRule.class ) ) { for ( final NetworkRule rule : Entities.criteriaQuery( Entities.restriction( NetworkRule.class ).any( Entities.restriction( NetworkRule.class ).isNull( NetworkRule_.lowPort ).build( ), Entities.restriction( NetworkRule.class ).isNull( NetworkRule_.highPort ).build( ) ).equal( NetworkRule_.protocol, Protocol.icmp ) ).list( ) ) { logger.info( "Updating ports for icmp rule in group " + rule.getGroup( ).getGroupId( ) + "/" + rule.getGroup( ).getDisplayName( ) ); if ( rule.getLowPort( ) == null ) { rule.setLowPort( -1 ); } if ( rule.getHighPort( ) == null ) { rule.setHighPort( -1 ); } } tx.commit( ); } catch (Exception ex) { logger.error( "Error updating ports for icmp rules", ex ); } } } }