// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package com.cloud.network.vpc; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.element.NetworkACLServiceProvider; import com.cloud.network.vpc.dao.NetworkACLDao; import com.cloud.network.vpc.NetworkACLItem.State; import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd; import org.apache.cloudstack.api.command.user.network.CreateNetworkACLListCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.Service; import com.cloud.network.NetworkModel; import com.cloud.network.Networks; import com.cloud.network.firewall.NetworkACLService; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.server.ResourceTag.TaggedResourceType; import com.cloud.tags.ResourceTagVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.UserContext; import com.cloud.utils.Pair; import com.cloud.utils.Ternary; import com.cloud.utils.component.Manager; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.Transaction; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @Component @Local(value = { NetworkACLService.class, NetworkACLManager.class}) public class NetworkACLManagerImpl extends ManagerBase implements NetworkACLManager{ private static final Logger s_logger = Logger.getLogger(NetworkACLManagerImpl.class); @Inject AccountManager _accountMgr; @Inject NetworkModel _networkMgr; @Inject VpcManager _vpcMgr; @Inject ResourceTagDao _resourceTagDao; @Inject NetworkACLDao _networkACLDao; @Inject NetworkACLItemDao _networkACLItemDao; @Inject List _networkAclElements; @Inject NetworkModel _networkModel; @Inject NetworkDao _networkDao; @Override public boolean revokeACLItemsForNetwork(long networkId, long userId, Account caller) throws ResourceUnavailableException { Network network = _networkDao.findById(networkId); List aclItems = _networkACLItemDao.listByACL(network.getNetworkACLId()); if (aclItems.isEmpty()) { s_logger.debug("Found no network ACL Items for network id=" + networkId); return true; } if (s_logger.isDebugEnabled()) { s_logger.debug("Releasing " + aclItems.size() + " Network ACL Items for network id=" + networkId); } for (NetworkACLItemVO aclItem : aclItems) { // Mark all Network ACLs rules as Revoke, but don't revoke them yet - we have to revoke all rules for ip, no // need to send them one by one revokeNetworkACLItem(aclItem.getId(), false, caller, Account.ACCOUNT_ID_SYSTEM); } //List ACLsToRevoke = _networkACLItemDao.listByNetwork(networkId); // now send everything to the backend boolean success = applyNetworkACL(network.getNetworkACLId(), caller); if (s_logger.isDebugEnabled()) { s_logger.debug("Successfully released Network ACLs for network id=" + networkId + " and # of rules now = " + aclItems.size()); } return success; } @Override public List listNetworkACLItems(long guestNtwkId) { Network network = _networkMgr.getNetwork(guestNtwkId); return _networkACLItemDao.listByACL(network.getNetworkACLId()); } @Override public NetworkACLItem getNetworkACLItem(long ruleId) { return _networkACLItemDao.findById(ruleId); } @Override public boolean applyNetworkACLtoNetworks(long aclId, Account caller) throws ResourceUnavailableException { boolean handled = false; List rules = _networkACLItemDao.listByACL(aclId); //Find all networks using this ACL List networks = _networkDao.listByAclId(aclId); for(NetworkVO network : networks){ applyNetworkACL(network.getId(), caller); } return handled; } @Override public boolean applyNetworkACL(long networkId, Account caller) throws ResourceUnavailableException { Network network = _networkDao.findById(networkId); boolean handled = false; List rules = _networkACLItemDao.listByACL(network.getNetworkACLId()); for (NetworkACLServiceProvider element: _networkAclElements) { Network.Provider provider = element.getProvider(); boolean isAclProvider = _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.NetworkACL, provider); if (!isAclProvider) { continue; } handled = element.applyNetworkACLs(network, rules); if (handled) break; } return handled; } @Override public NetworkACLItem createNetworkACLItem(CreateNetworkACLCmd aclItemCmd) throws NetworkRuleConflictException { if (aclItemCmd.getSourceCidrList() == null) { //_networkACLItemDao.loadSourceCidrs(aclItemCmd); } return createNetworkACLItem(UserContext.current().getCaller(), aclItemCmd.getSourcePortStart(), aclItemCmd.getSourcePortEnd(), aclItemCmd.getProtocol(), aclItemCmd.getSourceCidrList(), aclItemCmd.getIcmpCode(), aclItemCmd.getIcmpType(), null, aclItemCmd.getType(), aclItemCmd.getNetworkId(), aclItemCmd.getTrafficType(), aclItemCmd.getACLId()); } @DB @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_ITEM_CREATE, eventDescription = "creating network ACL Item", create = true) protected NetworkACLItem createNetworkACLItem(Account caller, Integer portStart, Integer portEnd, String protocol, List sourceCidrList, Integer icmpCode, Integer icmpType, Long relatedRuleId, NetworkACLItem.NetworkACLType type, Long networkId, NetworkACLItem.TrafficType trafficType, Long aclId) throws NetworkRuleConflictException { if(aclId == null){ Network network = _networkMgr.getNetwork(networkId); if (network == null) { throw new InvalidParameterValueException("Can't find network by id"); } aclId = network.getNetworkACLId(); if (aclId == null) { throw new InvalidParameterValueException("Network is not associated with any ACL"); } } NetworkACL networkACL = _networkACLDao.findById(aclId); Vpc vpc = _vpcMgr.getVpc(networkACL.getVpcId()); Account aclOwner = _accountMgr.getAccount(vpc.getAccountId()); //check if the caller can access vpc _accountMgr.checkAccess(caller, null, false, vpc); //check if the acl can be created for this network _accountMgr.checkAccess(aclOwner, AccessType.ModifyEntry, false, networkACL); // icmp code and icmp type can't be passed in for any other protocol rather than icmp if (!protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (icmpCode != null || icmpType != null)) { throw new InvalidParameterValueException("Can specify icmpCode and icmpType for ICMP protocol only"); } if (protocol.equalsIgnoreCase(NetUtils.ICMP_PROTO) && (portStart != null || portEnd != null)) { throw new InvalidParameterValueException("Can't specify start/end port when protocol is ICMP"); } //validate icmp code and type if (icmpType != null) { if (icmpType.longValue() != -1 && !NetUtils.validateIcmpType(icmpType.longValue())) { throw new InvalidParameterValueException("Invalid icmp type; should belong to [0-255] range"); } if (icmpCode != null) { if (icmpCode.longValue() != -1 && !NetUtils.validateIcmpCode(icmpCode.longValue())) { throw new InvalidParameterValueException("Invalid icmp code; should belong to [0-15] range and can" + " be defined when icmpType belongs to [0-40] range"); } } } validateNetworkACLItem(caller, portStart, portEnd, protocol); Transaction txn = Transaction.currentTxn(); txn.start(); NetworkACLItemVO newRule = new NetworkACLItemVO(portStart, portEnd, protocol.toLowerCase(), aclId, sourceCidrList, icmpCode, icmpType, trafficType); newRule.setType(type); newRule = _networkACLItemDao.persist(newRule); if (type == NetworkACLItem.NetworkACLType.User) { //ToDo: Is this required now with?? //detectNetworkACLConflict(newRule); } if (!_networkACLItemDao.setStateToAdd(newRule)) { throw new CloudRuntimeException("Unable to update the state to add for " + newRule); } UserContext.current().setEventDetails("ACL Item Id: " + newRule.getId()); txn.commit(); return getNetworkACLItem(newRule.getId()); } protected void validateNetworkACLItem(Account caller, Integer portStart, Integer portEnd, String proto) { if (portStart != null && !NetUtils.isValidPort(portStart)) { throw new InvalidParameterValueException("publicPort is an invalid value: " + portStart); } if (portEnd != null && !NetUtils.isValidPort(portEnd)) { throw new InvalidParameterValueException("Public port range is an invalid value: " + portEnd); } // start port can't be bigger than end port if (portStart != null && portEnd != null && portStart > portEnd) { throw new InvalidParameterValueException("Start port can't be bigger than end port"); } } @Override public boolean revokeNetworkACLItem(long ruleId, boolean apply) { Account caller = UserContext.current().getCaller(); long userId = UserContext.current().getCallerUserId(); return revokeNetworkACLItem(ruleId, apply, caller, userId); } @ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_DELETE, eventDescription = "revoking network acl", async = true) protected boolean revokeNetworkACLItem(long ruleId, boolean apply, Account caller, long userId) { NetworkACLItemVO rule = _networkACLItemDao.findById(ruleId); if (rule == null) { throw new InvalidParameterValueException("Unable to find network ACL Item" + ruleId); } // _accountMgr.checkAccess(caller, null, true, rule); revokeRule(rule, caller, userId, false); boolean success = false; if (apply) { try { success = applyNetworkACL(rule.getACLId(), caller); } catch (ResourceUnavailableException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } } else { success = true; } return success; } @Override public Pair, Integer> listNetworkACLItems(ListNetworkACLsCmd cmd) { Long networkId = cmd.getNetworkId(); Long id = cmd.getId(); String trafficType = cmd.getTrafficType(); Map tags = cmd.getTags(); Account caller = UserContext.current().getCaller(); List permittedAccounts = new ArrayList(); Ternary domainIdRecursiveListProject = new Ternary(cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedAccounts, domainIdRecursiveListProject, cmd.listAll(), false); Long domainId = domainIdRecursiveListProject.first(); Boolean isRecursive = domainIdRecursiveListProject.second(); ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third(); Filter filter = new Filter(NetworkACLItemVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal()); SearchBuilder sb = _networkACLItemDao.createSearchBuilder(); // _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); sb.and("id", sb.entity().getId(), Op.EQ); //sb.and("networkId", sb.entity().getNetworkId(), Op.EQ); sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ); if (tags != null && !tags.isEmpty()) { SearchBuilder tagSearch = _resourceTagDao.createSearchBuilder(); for (int count=0; count < tags.size(); count++) { tagSearch.or().op("key" + String.valueOf(count), tagSearch.entity().getKey(), SearchCriteria.Op.EQ); tagSearch.and("value" + String.valueOf(count), tagSearch.entity().getValue(), SearchCriteria.Op.EQ); tagSearch.cp(); } tagSearch.and("resourceType", tagSearch.entity().getResourceType(), SearchCriteria.Op.EQ); sb.groupBy(sb.entity().getId()); sb.join("tagSearch", tagSearch, sb.entity().getId(), tagSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER); } SearchCriteria sc = sb.create(); // _accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); if (id != null) { sc.setParameters("id", id); } if (networkId != null) { sc.setParameters("networkId", networkId); } if (trafficType != null) { sc.setParameters("trafficType", trafficType); } if (tags != null && !tags.isEmpty()) { int count = 0; sc.setJoinParameters("tagSearch", "resourceType", TaggedResourceType.NetworkACL.toString()); for (String key : tags.keySet()) { sc.setJoinParameters("tagSearch", "key" + String.valueOf(count), key); sc.setJoinParameters("tagSearch", "value" + String.valueOf(count), tags.get(key)); count++; } } Pair, Integer> result = _networkACLItemDao.searchAndCount(sc, filter); return new Pair, Integer>(result.first(), result.second()); } @Override public NetworkACL createNetworkACL(CreateNetworkACLListCmd cmd) { NetworkACLVO acl = new NetworkACLVO(cmd.getName(), cmd.getDescription(), cmd.getVpcId()); _networkACLDao.persist(acl); return acl; } @Override public NetworkACL getNetworkACL(long id) { return _networkACLDao.findById(id); } @Override public boolean deleteNetworkACL(long id) { return _networkACLDao.remove(id); } @Override public Pair, Integer> listNetworkACLs(ListNetworkACLListsCmd listNetworkACLListsCmd) { SearchBuilder sb = _networkACLDao.createSearchBuilder(); SearchCriteria sc = sb.create(); Filter filter = new Filter(NetworkACLVO.class, "id", false, null, null); Pair, Integer> acls = _networkACLDao.searchAndCount(sc, filter); return new Pair, Integer>(acls.first(), acls.second()); } @Override public boolean replaceNetworkACL(long aclId, long networkId) { NetworkVO network = _networkDao.findById(networkId); network.setNetworkACLId(aclId); return _networkDao.update(networkId, network); } @Override @DB public void revokeRule(NetworkACLItemVO rule, Account caller, long userId, boolean needUsageEvent) { if (caller != null) { //_accountMgr.checkAccess(caller, null, true, rule); } Transaction txn = Transaction.currentTxn(); boolean generateUsageEvent = false; txn.start(); if (rule.getState() == State.Staged) { if (s_logger.isDebugEnabled()) { s_logger.debug("Found a rule that is still in stage state so just removing it: " + rule); } _networkACLItemDao.remove(rule.getId()); generateUsageEvent = true; } else if (rule.getState() == State.Add || rule.getState() == State.Active) { rule.setState(State.Revoke); _networkACLItemDao.update(rule.getId(), rule); generateUsageEvent = true; } /* if (generateUsageEvent && needUsageEvent) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NET_RULE_DELETE, rule.getAccountId(), 0, rule.getId(), null, rule.getClass().getName(), rule.getUuid()); }*/ txn.commit(); } }