com.eucalyptus.network.NetworkGroupManager.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.network.NetworkGroupManager.java

Source

/*************************************************************************
 * Copyright 2009-2014 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.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;
import javax.persistence.EntityTransaction;
import com.eucalyptus.auth.principal.AccountFullName;
import com.eucalyptus.compute.common.CloudMetadatas;
import com.eucalyptus.cloud.util.MetadataConstraintException;
import com.eucalyptus.cloud.util.MetadataException;
import com.eucalyptus.cloud.util.NoSuchMetadataException;
import com.eucalyptus.compute.ClientComputeException;
import com.eucalyptus.compute.identifier.InvalidResourceIdentifier;
import com.eucalyptus.compute.identifier.ResourceIdentifiers;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.entities.Entities;
import com.eucalyptus.entities.TransactionException;
import com.eucalyptus.entities.Transactions;
import com.eucalyptus.records.Logs;
import com.eucalyptus.tags.Filter;
import com.eucalyptus.tags.Filters;
import com.eucalyptus.tags.Tag;
import com.eucalyptus.tags.TagSupport;
import com.eucalyptus.tags.Tags;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.OwnerFullName;
import com.eucalyptus.util.RestrictedTypes;
import com.eucalyptus.util.Strings;
import com.eucalyptus.util.TypeMappers;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Objects;
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 edu.ucsb.eucalyptus.msgs.AuthorizeSecurityGroupIngressResponseType;
import edu.ucsb.eucalyptus.msgs.AuthorizeSecurityGroupIngressType;
import edu.ucsb.eucalyptus.msgs.CreateSecurityGroupResponseType;
import edu.ucsb.eucalyptus.msgs.CreateSecurityGroupType;
import edu.ucsb.eucalyptus.msgs.DeleteSecurityGroupResponseType;
import edu.ucsb.eucalyptus.msgs.DeleteSecurityGroupType;
import edu.ucsb.eucalyptus.msgs.DescribeSecurityGroupsResponseType;
import edu.ucsb.eucalyptus.msgs.DescribeSecurityGroupsType;
import edu.ucsb.eucalyptus.msgs.IpPermissionType;
import edu.ucsb.eucalyptus.msgs.ResourceTag;
import edu.ucsb.eucalyptus.msgs.RevokeSecurityGroupIngressResponseType;
import edu.ucsb.eucalyptus.msgs.RevokeSecurityGroupIngressType;
import edu.ucsb.eucalyptus.msgs.SecurityGroupItemType;
import edu.ucsb.eucalyptus.msgs.UserIdGroupPairType;

public class NetworkGroupManager {

    public CreateSecurityGroupResponseType create(final CreateSecurityGroupType request)
            throws EucalyptusCloudException, MetadataException {
        final Context ctx = Contexts.lookup();
        final String groupName = request.getGroupName();
        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-*");
        }
        if (!CharMatcher.ASCII.matchesAllOf(groupName)) {
            throw new ClientComputeException("InvalidParameterValue", "Value (" + groupName
                    + ") for parameter GroupName is invalid. Character sets beyond ASCII are not supported.");
        }
        final CreateSecurityGroupResponseType reply = request.getReply();
        try {
            Supplier<NetworkGroup> allocator = new Supplier<NetworkGroup>() {

                @Override
                public NetworkGroup get() {
                    try {
                        return NetworkGroups.create(ctx.getUserFullName(), groupName,
                                request.getGroupDescription());
                    } catch (MetadataException ex) {
                        throw new RuntimeException(ex);
                    }
                }
            };
            final NetworkGroup group = RestrictedTypes.allocateUnitlessResource(allocator);
            reply.setGroupId(group.getGroupId());
            return reply;
        } catch (final Exception ex) {
            String cause = Exceptions.causeString(ex);
            if (cause.contains("DuplicateMetadataException"))
                throw new ClientComputeException("InvalidGroup.Duplicate",
                        "The security group '" + groupName + "' alread 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 EucalyptusCloudException(
                    "Not authorized to delete network group " + group.getDisplayName() + " for " + ctx.getUser());
        }

        if (NetworkGroups.defaultNetworkName().equals(group.getDisplayName())) {
            NetworkGroups.createDefault(AccountFullName.getInstance(group.getOwnerAccountNumber()));
        }
        try {
            NetworkGroups.delete(group.getGroupId());
        } catch (MetadataConstraintException e) {
            throw new ClientComputeException("InvalidGroup.InUse",
                    "Specified group cannot be deleted because it is in use.");
        }
        reply.set_return(true);
        return reply;
    }

    public DescribeSecurityGroupsResponseType describe(final DescribeSecurityGroupsType request)
            throws EucalyptusCloudException, MetadataException, TransactionException {
        final DescribeSecurityGroupsResponseType reply = request.getReply();
        final Context ctx = Contexts.lookup();
        final boolean showAll = request.getSecurityGroupSet().remove("verbose")
                || request.getSecurityGroupIdSet().remove("verbose");
        NetworkGroups.createDefault(ctx.getUserFullName()); //ensure the default group exists to cover some old broken installs

        final Filter filter = Filters.generate(request.getFilterSet(), NetworkGroup.class);
        final Predicate<? super NetworkGroup> requestedAndAccessible = CloudMetadatas
                .filteringFor(NetworkGroup.class)
                .byPredicate(Predicates.or(
                        request.getSecurityGroupSet().isEmpty() && request.getSecurityGroupIdSet().isEmpty()
                                ? Predicates.<NetworkGroup>alwaysTrue()
                                : Predicates.<NetworkGroup>alwaysFalse(),
                        request.getSecurityGroupSet().isEmpty() ? Predicates.<NetworkGroup>alwaysFalse()
                                : CloudMetadatas.<NetworkGroup>filterById(request.getSecurityGroupSet()),
                        request.getSecurityGroupIdSet().isEmpty() ? Predicates.<NetworkGroup>alwaysFalse()
                                : CloudMetadatas.filterByProperty(
                                        normalizeGroupIdentifiers(request.getSecurityGroupIdSet()),
                                        NetworkGroups.groupId())))
                .byPredicate(filter.asPredicate()).byPrivileges().buildPredicate();

        final OwnerFullName ownerFn = Contexts.lookup().isAdministrator() && showAll ? null
                : AccountFullName.getInstance(ctx.getAccount());

        final Iterable<SecurityGroupItemType> securityGroupItems = Transactions.filteredTransform(
                NetworkGroup.withOwner(ownerFn), filter.asCriterion(), filter.getAliases(), requestedAndAccessible,
                TypeMappers.lookup(NetworkGroup.class, SecurityGroupItemType.class));

        final Map<String, List<Tag>> tagsMap = TagSupport.forResourceClass(NetworkGroup.class).getResourceTagMap(
                AccountFullName.getInstance(ctx.getAccount()),
                Iterables.transform(securityGroupItems, SecurityGroupItemToGroupId.INSTANCE));
        for (final SecurityGroupItemType securityGroupItem : securityGroupItems) {
            Tags.addFromTags(securityGroupItem.getTagSet(), ResourceTag.class,
                    tagsMap.get(securityGroupItem.getGroupId()));
        }

        Iterables.addAll(reply.getSecurityGroupInfo(), securityGroupItems);

        return reply;
    }

    public RevokeSecurityGroupIngressResponseType revoke(final RevokeSecurityGroupIngressType request)
            throws EucalyptusCloudException {
        final Context ctx = Contexts.lookup();
        final RevokeSecurityGroupIngressResponseType reply = request.getReply();
        reply.markFailed();

        final EntityTransaction db = Entities.get(NetworkGroup.class);
        try {
            final List<IpPermissionType> ipPermissions = handleOldAndNewIpPermissions(request.getCidrIp(),
                    request.getIpProtocol(), request.getFromPort(), request.getToPort(),
                    request.getSourceSecurityGroupName(), request.getSourceSecurityGroupOwnerId(),
                    request.getIpPermissions());
            final NetworkGroup ruleGroup = lookupGroup(request.getGroupId(), request.getGroupName());
            if (RestrictedTypes.filterPrivileged().apply(ruleGroup)) {
                NetworkGroups.resolvePermissions(ipPermissions, ctx.getUser().getAccountNumber(), true);
                try {
                    Iterators.removeAll( // iterator used to work around broken equals/hashCode in NetworkRule
                            ruleGroup.getNetworkRules().iterator(),
                            NetworkGroups.ipPermissionsAsNetworkRules(ipPermissions));
                } catch (IllegalArgumentException e) {
                    throw new ClientComputeException("InvalidPermission.Malformed", e.getMessage());
                }
            } else {
                throw new EucalyptusCloudException("Not authorized to revoke network group "
                        + request.getGroupName() + " for " + ctx.getUser());
            }
            reply.set_return(true);
            db.commit();
            NetworkGroups.flushRules();
        } catch (EucalyptusCloudException ex) {
            throw ex;
        } catch (Exception ex) {
            Logs.exhaust().error(ex, ex);
            throw new EucalyptusCloudException("RevokeSecurityGroupIngress failed because: " + ex.getMessage(), ex);
        } finally {
            if (db.isActive())
                db.rollback();
        }
        return reply;
    }

    public AuthorizeSecurityGroupIngressResponseType authorize(final AuthorizeSecurityGroupIngressType request)
            throws Exception {
        final Context ctx = Contexts.lookup();
        final AuthorizeSecurityGroupIngressResponseType reply = request.getReply();

        final EntityTransaction db = Entities.get(NetworkGroup.class);
        try {
            final NetworkGroup ruleGroup = lookupGroup(request.getGroupId(), request.getGroupName());
            if (!RestrictedTypes.filterPrivileged().apply(ruleGroup)) {
                throw new EucalyptusCloudException("Not authorized to authorize network group "
                        + ruleGroup.getDisplayName() + " for " + ctx.getUser());
            }
            final List<NetworkRule> ruleList = Lists.newArrayList();
            List<IpPermissionType> ipPermissions = handleOldAndNewIpPermissions(request.getCidrIp(),
                    request.getIpProtocol(), request.getFromPort(), request.getToPort(),
                    request.getSourceSecurityGroupName(), request.getSourceSecurityGroupOwnerId(),
                    request.getIpPermissions());
            NetworkGroups.resolvePermissions(ipPermissions, ctx.getUser().getAccountNumber(), false);
            for (final IpPermissionType ipPerm : ipPermissions) {
                if (ipPerm.getCidrIpRanges().isEmpty() && ipPerm.getGroups().isEmpty()) {
                    continue; // see EUCA-5934
                }
                try {
                    final List<NetworkRule> rules = NetworkGroups.IpPermissionTypeAsNetworkRule.INSTANCE
                            .apply(ipPerm);
                    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));
                }
            })) {
                reply.set_return(false);
                return reply;
            } else {
                ruleGroup.getNetworkRules().addAll(ruleList);
                reply.set_return(true);
            }
            db.commit();
            NetworkGroups.flushRules();
            return reply;
        } catch (Exception ex) {
            Logs.exhaust().error(ex, ex);
            throw ex;
        } finally {
            if (db.isActive())
                db.rollback();
        }
    }

    private List<IpPermissionType> handleOldAndNewIpPermissions(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);
        condition1Params.put("ipProtocol", ipProtocol);
        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 {
                // 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 {
            if (groupName != null) {
                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 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-...\")");
    }

    private static List<String> normalizeGroupIdentifiers(final List<String> identifiers)
            throws EucalyptusCloudException {
        try {
            return ResourceIdentifiers.normalize(NetworkGroup.ID_PREFIX, identifiers);
        } catch (final InvalidResourceIdentifier e) {
            throw new ClientComputeException("InvalidGroupId.Malformed",
                    "Invalid id: \"" + e.getIdentifier() + "\" (expecting \"sg-...\")");
        }
    }

    private enum SecurityGroupItemToGroupId implements Function<SecurityGroupItemType, String> {
        INSTANCE {
            @Override
            public String apply(SecurityGroupItemType securityGroupItemType) {
                return securityGroupItemType.getGroupId();
            }
        }
    }
}