// Copyright 2012 Citrix Systems, Inc. Licensed under the // Apache License, Version 2.0 (the "License"); you may not use this // file except in compliance with the License. Citrix Systems, Inc. // reserves all rights not expressly granted by 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. // // Automatically generated by addcopyright.py at 04/03/2012 package com.cloud.network.vpc; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import javax.ejb.Local; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import com.cloud.acl.SecurityChecker.AccessType; import com.cloud.api.commands.ListNetworkACLsCmd; 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.NetworkManager; 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.network.rules.NetworkACL; import com.cloud.projects.Project.ListProjectResourcesCriteria; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.UserContext; import com.cloud.utils.Ternary; import com.cloud.utils.component.Inject; import com.cloud.utils.component.Manager; import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; 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; /** * @author Alena Prokharchyk */ @Local(value = { NetworkACLService.class, NetworkACLManager.class}) public class NetworkACLManagerImpl implements Manager,NetworkACLManager{ String _name; private static final Logger s_logger = Logger.getLogger(NetworkACLManagerImpl.class); @Inject AccountManager _accountMgr; @Inject FirewallManager _firewallMgr; @Inject FirewallRulesDao _firewallDao; @Inject NetworkManager _networkMgr; @Inject VpcManager _vpcMgr; @Override public boolean configure(String name, Map params) throws ConfigurationException { _name = name; return true; } @Override public boolean start() { return true; } @Override public boolean stop() { return true; } @Override public String getName() { return _name; } @Override public boolean applyNetworkACLs(long networkId, Account caller) throws ResourceUnavailableException { List rules = _firewallDao.listByNetworkAndPurpose(networkId, Purpose.NetworkACL); return _firewallMgr.applyFirewallRules(rules, false, caller); } @Override public NetworkACL createNetworkACL(NetworkACL acl) throws NetworkRuleConflictException { 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 NetworkACL createNetworkACL(Account caller, String xId, Integer portStart, Integer portEnd, String protocol, List 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()); _accountMgr.checkAccess(caller, AccessType.UseNetwork, false, network); if (!_networkMgr.areServicesSupportedInNetwork(networkId, Service.Firewall)) { throw new InvalidParameterValueException("Service " + Service.Firewall + " 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"); } 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 newRule; } 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 caps = _networkMgr.getNetworkServiceCapabilities(network.getId(), Service.Firewall); 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); } String firewallType = caps.get(Capability.FirewallType); if (!firewallType.equalsIgnoreCase("networkacl")) { throw new UnsupportedOperationException("Network ACLS are not supported in network " + network); } } else { throw new InvalidParameterValueException("No capabilities are found for network " + network); } } protected void detectNetworkACLConflict(NetworkACL newRule) throws NetworkRuleConflictException { List 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()) { continue; // Skips my own rule. } // if rules cidrs are different, we can skip port ranges verification boolean duplicatedCidrs = false; // Verify that the rules have different cidrs List ruleCidrList = rule.getSourceCidrList(); List newRuleCidrList = newRule.getSourceCidrList(); if (ruleCidrList == null || newRuleCidrList == null) { continue; } Collection similar = new HashSet(ruleCidrList); similar.retainAll(newRuleCidrList); if (similar.size() > 0) { duplicatedCidrs = true; } if (newRule.getProtocol().equalsIgnoreCase(NetUtils.ICMP_PROTO) && newRule.getProtocol().equalsIgnoreCase(rule.getProtocol())) { if (newRule.getIcmpCode().longValue() == rule.getIcmpCode().longValue() && newRule.getIcmpType().longValue() == rule.getIcmpType().longValue() && 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 rules = _firewallDao.listByNetworkAndPurpose(rule.getNetworkId(), Purpose.NetworkACL); success = _firewallMgr.applyFirewallRules(rules, false, caller); } else { success = true; } return success; } @Override public NetworkACL getNetworkACL(long ACLId) { FirewallRuleVO rule = _firewallDao.findById(ACLId); if (rule != null && rule.getPurpose() == Purpose.NetworkACL) { return rule; } return null; } @Override public List listNetworkACLs(ListNetworkACLsCmd cmd) { Long networkId = cmd.getNetworkId(); Long id = cmd.getId(); String trafficType = cmd.getTrafficType(); 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(FirewallRuleVO.class, "id", false, cmd.getStartIndex(), cmd.getPageSizeVal()); SearchBuilder sb = _firewallDao.createSearchBuilder(); _accountMgr.buildACLSearchBuilder(sb, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria); sb.and("id", sb.entity().getId(), Op.EQ); sb.and("network", sb.entity().getNetworkId(), Op.EQ); sb.and("purpose", sb.entity().getPurpose(), Op.EQ); sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ); 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); } sc.setParameters("purpose", Purpose.NetworkACL); return _firewallDao.search(sc, filter); } @Override public List listNetworkACLs(long guestNtwkId) { return _firewallDao.listByNetworkAndPurpose(guestNtwkId, Purpose.NetworkACL); } @Override public boolean revokeAllNetworkACLsForNetwork(long networkId, long userId, Account caller) throws ResourceUnavailableException { List ACLs = _firewallDao.listByNetworkAndPurposeAndNotRevoked(networkId, Purpose.NetworkACL); if (s_logger.isDebugEnabled()) { s_logger.debug("Releasing " + ACLs.size() + " Network ACLs for network id=" + networkId); } for (FirewallRuleVO ACL : ACLs) { // Mark all Firewall 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); } // now send everything to the backend boolean success = _firewallMgr.applyFirewallRules(ACLs, false, caller); if (s_logger.isDebugEnabled()) { s_logger.debug("Successfully released Network ACLs for network id=" + networkId + " and # of rules now = " + ACLs.size()); } // Now we check again in case more rules have been inserted. ACLs.addAll(_firewallDao.listByNetworkAndPurposeAndNotRevoked(networkId, Purpose.Firewall)); return success && ACLs.size() == 0; } }