| // 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 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.dao.FirewallRulesDao; |
| import com.cloud.network.firewall.NetworkACLService; |
| import com.cloud.network.rules.FirewallManager; |
| import com.cloud.network.rules.FirewallRule; |
| import com.cloud.network.rules.FirewallRule.Purpose; |
| import com.cloud.network.rules.FirewallRule.TrafficType; |
| import com.cloud.network.rules.FirewallRuleVO; |
| 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 |
| FirewallManager _firewallMgr; |
| @Inject |
| FirewallRulesDao _firewallDao; |
| @Inject |
| NetworkModel _networkMgr; |
| @Inject |
| VpcManager _vpcMgr; |
| @Inject |
| ResourceTagDao _resourceTagDao; |
| |
| @Override |
| public boolean applyNetworkACLs(long networkId, Account caller) throws ResourceUnavailableException { |
| List<FirewallRuleVO> rules = _firewallDao.listByNetworkAndPurpose(networkId, Purpose.NetworkACL); |
| return _firewallMgr.applyFirewallRules(rules, false, caller); |
| } |
| |
| @Override |
| public FirewallRule createNetworkACL(FirewallRule acl) throws NetworkRuleConflictException { |
| if (acl.getSourceCidrList() == null && (acl.getPurpose() == Purpose.Firewall || acl.getPurpose() == Purpose.NetworkACL)) { |
| _firewallDao.loadSourceCidrs((FirewallRuleVO)acl); |
| } |
| return createNetworkACL(UserContext.current().getCaller(), acl.getXid(), acl.getSourcePortStart(), |
| acl.getSourcePortEnd(), acl.getProtocol(), acl.getSourceCidrList(), acl.getIcmpCode(), |
| acl.getIcmpType(), null, acl.getType(), acl.getNetworkId(), acl.getTrafficType()); |
| } |
| |
| @DB |
| @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_OPEN, eventDescription = "creating firewall rule", create = true) |
| protected FirewallRule createNetworkACL(Account caller, String xId, Integer portStart, |
| Integer portEnd, String protocol, List<String> sourceCidrList, Integer icmpCode, Integer icmpType, |
| Long relatedRuleId, FirewallRule.FirewallRuleType type, long networkId, TrafficType trafficType) throws NetworkRuleConflictException { |
| |
| Network network = _networkMgr.getNetwork(networkId); |
| if (network == null) { |
| throw new InvalidParameterValueException("Can't find network by id"); |
| } |
| |
| if (network.getVpcId() == null) { |
| throw new UnsupportedOperationException("Network ACL rules are supported just for VPC networks"); |
| } |
| |
| Vpc vpc = _vpcMgr.getVpc(network.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.UseNetwork, false, network); |
| |
| if (!_networkMgr.areServicesSupportedInNetwork(networkId, Service.NetworkACL)) { |
| throw new InvalidParameterValueException("Service " + Service.NetworkACL + " is not supported in network " + network); |
| } |
| |
| // 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"); |
| } |
| } |
| } |
| |
| validateNetworkACL(caller, network, portStart, portEnd, protocol); |
| |
| Transaction txn = Transaction.currentTxn(); |
| txn.start(); |
| |
| FirewallRuleVO newRule = new FirewallRuleVO(xId, null, portStart, portEnd, protocol.toLowerCase(), networkId, |
| aclOwner.getAccountId(), aclOwner.getDomainId(), Purpose.NetworkACL, sourceCidrList, icmpCode, icmpType, |
| relatedRuleId, trafficType); |
| newRule.setType(type); |
| newRule = _firewallDao.persist(newRule); |
| |
| if (type == FirewallRule.FirewallRuleType.User) { |
| detectNetworkACLConflict(newRule); |
| } |
| |
| if (!_firewallDao.setStateToAdd(newRule)) { |
| throw new CloudRuntimeException("Unable to update the state to add for " + newRule); |
| } |
| UserContext.current().setEventDetails("Rule Id: " + newRule.getId()); |
| |
| txn.commit(); |
| |
| return getNetworkACL(newRule.getId()); |
| } |
| |
| |
| protected void validateNetworkACL(Account caller, Network network, 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"); |
| } |
| |
| if (network.getTrafficType() != Networks.TrafficType.Guest) { |
| throw new InvalidParameterValueException("Network ACL can be created just for networks of type " + Networks.TrafficType.Guest); |
| } |
| |
| // Verify that the network guru supports the protocol specified |
| Map<Network.Capability, String> caps = _networkMgr.getNetworkServiceCapabilities(network.getId(), Service.NetworkACL); |
| |
| |
| if (caps != null) { |
| String supportedProtocols = caps.get(Capability.SupportedProtocols).toLowerCase(); |
| if (!supportedProtocols.contains(proto.toLowerCase())) { |
| throw new InvalidParameterValueException("Protocol " + proto + " is not supported by the network " + network); |
| } |
| } else { |
| throw new InvalidParameterValueException("No capabilities are found for network " + network); |
| } |
| } |
| |
| protected void detectNetworkACLConflict(FirewallRuleVO newRule) throws NetworkRuleConflictException { |
| if (newRule.getPurpose() != Purpose.NetworkACL) { |
| return; |
| } |
| |
| List<FirewallRuleVO> rules = _firewallDao.listByNetworkPurposeTrafficTypeAndNotRevoked(newRule.getNetworkId(), |
| Purpose.NetworkACL, newRule.getTrafficType()); |
| assert (rules.size() >= 1) : "For network ACLs, we now always first persist the rule and then check for " + |
| "network conflicts so we should at least have one rule at this point."; |
| |
| for (FirewallRuleVO rule : rules) { |
| if (rule.getId() == newRule.getId() || !rule.getProtocol().equalsIgnoreCase(newRule.getProtocol())) { |
| continue; // Skips my own rule and skip the rule if the protocol is different |
| } |
| |
| // if one cidr overlaps another, do port veirficatino |
| boolean duplicatedCidrs = false; |
| // Verify that the rules have different cidrs |
| _firewallDao.loadSourceCidrs(rule); |
| List<String> ruleCidrList = rule.getSourceCidrList(); |
| List<String> newRuleCidrList = newRule.getSourceCidrList(); |
| |
| if (ruleCidrList == null || newRuleCidrList == null) { |
| continue; |
| } |
| |
| for (String newCidr : newRuleCidrList) { |
| for (String ruleCidr : ruleCidrList) { |
| if (NetUtils.isNetworksOverlap(newCidr, ruleCidr)) { |
| duplicatedCidrs = true; |
| break; |
| } |
| if (duplicatedCidrs) { |
| break; |
| } |
| } |
| } |
| |
| if (newRule.getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO) |
| && newRule.getProtocol().equalsIgnoreCase(rule.getProtocol())) { |
| if ((newRule.getIcmpCode().longValue() == rule.getIcmpCode().longValue() |
| || rule.getIcmpCode().longValue() == -1 || newRule.getIcmpCode().longValue() == -1) |
| && (newRule.getIcmpType().longValue() == rule.getIcmpType().longValue() |
| || rule.getIcmpType().longValue() == -1 || newRule.getIcmpType().longValue() == -1) |
| && newRule.getProtocol().equalsIgnoreCase(rule.getProtocol()) && duplicatedCidrs) { |
| throw new InvalidParameterValueException("New network ACL conflicts with existing network ACL id=" + rule.getId()); |
| } |
| } |
| |
| boolean notNullPorts = (newRule.getSourcePortStart() != null && newRule.getSourcePortEnd() != null && |
| rule.getSourcePortStart() != null && rule.getSourcePortEnd() != null); |
| if (!notNullPorts) { |
| continue; |
| } else if (duplicatedCidrs |
| && ((rule.getSourcePortStart().intValue() <= newRule.getSourcePortStart().intValue() |
| && rule.getSourcePortEnd().intValue() >= newRule.getSourcePortStart().intValue()) |
| || (rule.getSourcePortStart().intValue() <= newRule.getSourcePortEnd().intValue() |
| && rule.getSourcePortEnd().intValue() >= newRule.getSourcePortEnd().intValue()) |
| || (newRule.getSourcePortStart().intValue() <= rule.getSourcePortStart().intValue() |
| && newRule.getSourcePortEnd().intValue() >= rule.getSourcePortStart().intValue()) |
| || (newRule.getSourcePortStart().intValue() <= rule.getSourcePortEnd().intValue() |
| && newRule.getSourcePortEnd().intValue() >= rule.getSourcePortEnd().intValue()))) { |
| |
| throw new NetworkRuleConflictException("The range specified, " + newRule.getSourcePortStart() + "-" |
| + newRule.getSourcePortEnd() + ", conflicts with rule " + rule.getId() |
| + " which has " + rule.getSourcePortStart() + "-" + rule.getSourcePortEnd()); |
| |
| } |
| } |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("No network rule conflicts detected for " + newRule + " against " + (rules.size() - 1) |
| + " existing network ACLs"); |
| } |
| } |
| |
| @Override |
| public boolean revokeNetworkACL(long ruleId, boolean apply) { |
| Account caller = UserContext.current().getCaller(); |
| long userId = UserContext.current().getCallerUserId(); |
| return revokeNetworkACL(ruleId, apply, caller, userId); |
| } |
| |
| @ActionEvent(eventType = EventTypes.EVENT_FIREWALL_CLOSE, eventDescription = "revoking firewall rule", async = true) |
| protected boolean revokeNetworkACL(long ruleId, boolean apply, Account caller, long userId) { |
| |
| FirewallRuleVO rule = _firewallDao.findById(ruleId); |
| if (rule == null || rule.getPurpose() != Purpose.NetworkACL) { |
| throw new InvalidParameterValueException("Unable to find " + ruleId + " having purpose " + Purpose.NetworkACL); |
| } |
| |
| _accountMgr.checkAccess(caller, null, true, rule); |
| |
| _firewallMgr.revokeRule(rule, caller, userId, false); |
| |
| boolean success = false; |
| |
| if (apply) { |
| List<FirewallRuleVO> rules = _firewallDao.listByNetworkAndPurpose(rule.getNetworkId(), Purpose.NetworkACL); |
| success = _firewallMgr.applyFirewallRules(rules, false, caller); |
| } else { |
| success = true; |
| } |
| |
| return success; |
| } |
| |
| |
| @Override |
| public FirewallRule getNetworkACL(long ACLId) { |
| FirewallRule rule = _firewallDao.findById(ACLId); |
| if (rule != null && rule.getPurpose() == Purpose.NetworkACL) { |
| return rule; |
| } |
| return null; |
| } |
| |
| |
| @Override |
| public Pair<List<? extends FirewallRule>,Integer> listNetworkACLs(ListNetworkACLsCmd cmd) { |
| Long networkId = cmd.getNetworkId(); |
| Long id = cmd.getId(); |
| String trafficType = cmd.getTrafficType(); |
| Map<String, String> tags = cmd.getTags(); |
| |
| Account caller = UserContext.current().getCaller(); |
| List<Long> permittedAccounts = new ArrayList<Long>(); |
| |
| Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = |
| new Ternary<Long, Boolean, ListProjectResourcesCriteria>(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(FirewallRuleVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal()); |
| SearchBuilder<FirewallRuleVO> sb = _firewallDao.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("purpose", sb.entity().getPurpose(), Op.EQ); |
| sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ); |
| |
| if (tags != null && !tags.isEmpty()) { |
| SearchBuilder<ResourceTagVO> 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<FirewallRuleVO> 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++; |
| } |
| } |
| |
| sc.setParameters("purpose", Purpose.NetworkACL); |
| |
| Pair<List<FirewallRuleVO>, Integer> result = _firewallDao.searchAndCount(sc, filter); |
| return new Pair<List<? extends FirewallRule>, Integer>(result.first(), result.second()); |
| } |
| |
| |
| @Override |
| public List<? extends FirewallRule> listNetworkACLs(long guestNtwkId) { |
| return _firewallDao.listByNetworkAndPurpose(guestNtwkId, Purpose.NetworkACL); |
| } |
| |
| |
| @Override |
| public boolean revokeAllNetworkACLsForNetwork(long networkId, long userId, Account caller) throws ResourceUnavailableException { |
| |
| List<FirewallRuleVO> ACLs = _firewallDao.listByNetworkAndPurpose(networkId, Purpose.NetworkACL); |
| |
| if (ACLs.isEmpty()) { |
| s_logger.debug("Found no network ACLs for network id=" + networkId); |
| return true; |
| } |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Releasing " + ACLs.size() + " Network ACLs for network id=" + networkId); |
| } |
| |
| for (FirewallRuleVO ACL : ACLs) { |
| // 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 |
| revokeNetworkACL(ACL.getId(), false, caller, Account.ACCOUNT_ID_SYSTEM); |
| } |
| |
| List<FirewallRuleVO> ACLsToRevoke = _firewallDao.listByNetworkAndPurpose(networkId, Purpose.NetworkACL); |
| |
| // now send everything to the backend |
| boolean success = _firewallMgr.applyFirewallRules(ACLsToRevoke, false, caller); |
| |
| if (s_logger.isDebugEnabled()) { |
| s_logger.debug("Successfully released Network ACLs for network id=" + networkId + " and # of rules now = " |
| + ACLs.size()); |
| } |
| |
| return success; |
| } |
| |
| } |