diff --git a/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java b/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java index 76d6d952814c..d43625c09a92 100644 --- a/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java @@ -19,6 +19,9 @@ import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.PortForwardingRule; import com.cloud.utils.net.NetUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; /** * PortForwardingRuleTO specifies one port forwarding rule. @@ -29,6 +32,8 @@ public class PortForwardingRuleTO extends FirewallRuleTO { String dstIp; int[] dstPortRange; + List sourceCidrList; + protected PortForwardingRuleTO() { super(); } @@ -37,6 +42,7 @@ public PortForwardingRuleTO(PortForwardingRule rule, String srcVlanTag, String s super(rule, srcVlanTag, srcIp); this.dstIp = rule.getDestinationIpAddress().addr(); this.dstPortRange = new int[] {rule.getDestinationPortStart(), rule.getDestinationPortEnd()}; + this.sourceCidrList = rule.getSourceCidrList(); } public PortForwardingRuleTO(long id, String srcIp, int srcPortStart, int srcPortEnd, String dstIp, int dstPortStart, int dstPortEnd, String protocol, @@ -58,4 +64,11 @@ public String getStringDstPortRange() { return NetUtils.portRangeToString(dstPortRange); } + public String getSourceCidrListAsString() { + if (sourceCidrList != null) { + return StringUtils.join(sourceCidrList, ","); + } + return null; + } + } diff --git a/api/src/main/java/com/cloud/network/rules/RulesService.java b/api/src/main/java/com/cloud/network/rules/RulesService.java index 0b4afeef9458..547d4ab51e03 100644 --- a/api/src/main/java/com/cloud/network/rules/RulesService.java +++ b/api/src/main/java/com/cloud/network/rules/RulesService.java @@ -26,6 +26,7 @@ import com.cloud.user.Account; import com.cloud.utils.Pair; import com.cloud.utils.net.Ip; +import org.apache.cloudstack.api.command.user.firewall.UpdatePortForwardingRuleCmd; public interface RulesService { Pair, Integer> searchStaticNatRules(Long ipId, Long id, Long vmId, Long start, Long size, String accountName, Long domainId, @@ -81,6 +82,8 @@ Pair, Integer> searchStaticNatRules(Long ipId, Long boolean disableStaticNat(long ipId) throws ResourceUnavailableException, NetworkRuleConflictException, InsufficientAddressCapacityException; - PortForwardingRule updatePortForwardingRule(long id, Integer privatePort, Integer privateEndPort, Long virtualMachineId, Ip vmGuestIp, String customId, Boolean forDisplay); + PortForwardingRule updatePortForwardingRule(UpdatePortForwardingRuleCmd cmd); + + void validatePortForwardingSourceCidrList(List sourceCidrList); } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index d6099ac47178..bb78505fac6c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -426,6 +426,7 @@ public class ApiConstants { public static final String SNAPSHOT_TYPE = "snapshottype"; public static final String SNAPSHOT_QUIESCEVM = "quiescevm"; public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot"; + public static final String SOURCE_CIDR_LIST = "sourcecidrlist"; public static final String SOURCE_ZONE_ID = "sourcezoneid"; public static final String START_DATE = "startdate"; public static final String START_ID = "startid"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java index 5e1362101502..05d627fe5779 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java @@ -107,8 +107,12 @@ public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements P description = "the ID of the virtual machine for the port forwarding rule") private Long virtualMachineId; - @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "the cidr list to forward traffic from. Multiple entries must be separated by a single comma character (,). This parameter is deprecated. Do not use.") - private List cidrlist; + @Parameter(name = ApiConstants.CIDR_LIST, + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = " the source CIDR list to allow traffic from; all other CIDRs will be blocked. " + + "Multiple entries must be separated by a single comma character (,). This param will be used only for VPC tiers. By default, all CIDRs are allowed.") + private List sourceCidrList; @Parameter(name = ApiConstants.OPEN_FIREWALL, type = CommandType.BOOLEAN, description = "if true, firewall rule for source/end public port is automatically created; " + "if false - firewall rule has to be created explicitly. If not specified 1) defaulted to false when PF" @@ -157,11 +161,7 @@ public long getVirtualMachineId() { @Override public List getSourceCidrList() { - if (cidrlist != null) { - throw new InvalidParameterValueException("Parameter cidrList is deprecated; if you need to open firewall " - + "rule for the specific cidr, please refer to createFirewallRule command"); - } - return null; + return sourceCidrList; } public Boolean getOpenFirewall() { @@ -334,12 +334,6 @@ public int getDestinationPortEnd() { @Override public void create() { - // cidr list parameter is deprecated - if (cidrlist != null) { - throw new InvalidParameterValueException( - "Parameter cidrList is deprecated; if you need to open firewall rule for the specific cidr, please refer to createFirewallRule command"); - } - Ip privateIp = getVmSecondaryIp(); if (privateIp != null) { if (!NetUtils.isValidIp4(privateIp.toString())) { @@ -347,6 +341,8 @@ public void create() { } } + _rulesService.validatePortForwardingSourceCidrList(sourceCidrList); + try { PortForwardingRule result = _rulesService.createPortForwardingRule(this, virtualMachineId, privateIp, getOpenFirewall(), isDisplay()); setEntityId(result.getId()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java index 2afc0bb66adf..da02aa4b174c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdatePortForwardingRuleCmd.java @@ -33,6 +33,8 @@ import com.cloud.user.Account; import com.cloud.utils.net.Ip; +import java.util.List; + @APICommand(name = "updatePortForwardingRule", responseObject = FirewallRuleResponse.class, description = "Updates a port forwarding rule. Only the private port and the virtual machine can be updated.", entityType = {PortForwardingRule.class}, @@ -65,6 +67,13 @@ public class UpdatePortForwardingRuleCmd extends BaseAsyncCustomIdCmd { @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "an optional field, whether to the display the rule to the end user or not", since = "4.4", authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name = ApiConstants.CIDR_LIST, + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = " the source CIDR list to allow traffic from; all other CIDRs will be blocked. " + + "Multiple entries must be separated by a single comma character (,). This param will be used only for VPC tiers. By default, all CIDRs are allowed.") + private List sourceCidrList; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -96,6 +105,10 @@ public Boolean getDisplay() { return display; } + public List getSourceCidrList() { + return sourceCidrList; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -132,7 +145,7 @@ public void checkUuid() { @Override public void execute() { - PortForwardingRule rule = _rulesService.updatePortForwardingRule(getId(), getPrivatePort(), getPrivateEndPort(), getVirtualMachineId(), getVmGuestIp(), getCustomId(), getDisplay()); + PortForwardingRule rule = _rulesService.updatePortForwardingRule(this); FirewallRuleResponse fwResponse = new FirewallRuleResponse(); if (rule != null) { fwResponse = _responseGenerator.createPortForwardingRuleResponse(rule); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java index ef9e46f3a76f..b741310cb162 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java @@ -106,7 +106,7 @@ public class CreateLoadBalancerRuleCmd extends BaseAsyncCreateCmd /*implements L @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "the domain ID associated with the load balancer") private Long domainId; - @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, since = "4.18.0.0", description = "the CIDR list to allow traffic, " + @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, since = "4.18.0.0", description = "the source CIDR list to allow traffic from; " + "all other CIDRs will be blocked. Multiple entries must be separated by a single comma character (,). By default, all CIDRs are allowed.") private List cidrlist; diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java index c9d0e74e2e40..4daef64ed8a4 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetPortForwardingRulesConfigItem.java @@ -41,7 +41,7 @@ public List generateConfig(final NetworkElementCommand cmd) { for (final PortForwardingRuleTO rule : command.getRules()) { final ForwardingRule fwdRule = new ForwardingRule(rule.revoked(), rule.getProtocol().toLowerCase(), rule.getSrcIp(), rule.getStringSrcPortRange(), rule.getDstIp(), - rule.getStringDstPortRange()); + rule.getStringDstPortRange(), rule.getSourceCidrListAsString()); rules.add(fwdRule); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java index cf3e43d1c018..1dc1cc855c4c 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/ForwardingRule.java @@ -26,18 +26,21 @@ public class ForwardingRule { private String sourcePortRange; private String destinationIpAddress; private String destinationPortRange; + private String sourceCidrList; public ForwardingRule() { // Empty constructor for (de)serialization } - public ForwardingRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress, String destinationPortRange) { + public ForwardingRule(boolean revoke, String protocol, String sourceIpAddress, String sourcePortRange, String destinationIpAddress, String destinationPortRange, + String sourceCidrList) { this.revoke = revoke; this.protocol = protocol; this.sourceIpAddress = sourceIpAddress; this.sourcePortRange = sourcePortRange; this.destinationIpAddress = destinationIpAddress; this.destinationPortRange = destinationPortRange; + this.sourceCidrList = sourceCidrList; } public boolean isRevoke() { @@ -88,4 +91,8 @@ public void setDestinationPortRange(String destinationPortRange) { this.destinationPortRange = destinationPortRange; } + public String getSourceCidrList() { + return sourceCidrList; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java index 55c454860efe..df5b6b5d647c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDao.java @@ -29,4 +29,6 @@ public interface FirewallRulesCidrsDao extends GenericDao listByFirewallRuleId(long firewallRuleId); + + void updateSourceCidrsForRule(Long firewallRuleId, List sourceCidrList); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java index f6185309972b..4630925ddee9 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsDaoImpl.java @@ -20,6 +20,7 @@ import java.util.List; +import org.apache.commons.collections4.CollectionUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -65,9 +66,27 @@ public List listByFirewallRuleId(long firewallRuleId) { return results; } + @Override + public void updateSourceCidrsForRule(Long firewallRuleId, List sourceCidrList) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + + SearchCriteria sc = CidrsSearch.create(); + sc.setParameters("firewallRuleId", firewallRuleId); + remove(sc); + + persist(firewallRuleId, sourceCidrList); + + txn.commit(); + } + @Override @DB public void persist(long firewallRuleId, List sourceCidrs) { + if (CollectionUtils.isEmpty(sourceCidrs)) { + return; + } + TransactionLegacy txn = TransactionLegacy.currentTxn(); txn.start(); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java index 3ac860b08c5f..f18fb98f3de4 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java @@ -243,9 +243,6 @@ public FirewallRuleVO persist(FirewallRuleVO firewallRule) { } public void saveSourceCidrs(FirewallRuleVO firewallRule, List cidrList) { - if (cidrList == null) { - return; - } _firewallRulesCidrsDao.persist(firewallRule.getId(), cidrList); } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java index e1a698881f33..28d12fb24b40 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java @@ -25,6 +25,7 @@ import javax.persistence.Enumerated; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; +import javax.persistence.Transient; import com.cloud.utils.net.Ip; @@ -47,21 +48,20 @@ public class PortForwardingRuleVO extends FirewallRuleVO implements PortForwardi @Column(name = "instance_id") private long virtualMachineId; + @Transient + List sourceCidrs; + public PortForwardingRuleVO() { } public PortForwardingRuleVO(String xId, long srcIpId, int srcPortStart, int srcPortEnd, Ip dstIp, int dstPortStart, int dstPortEnd, String protocol, long networkId, - long accountId, long domainId, long instanceId) { - super(xId, srcIpId, srcPortStart, srcPortEnd, protocol, networkId, accountId, domainId, Purpose.PortForwarding, null, null, null, null, null); + long accountId, long domainId, long instanceId, List sourceCidrs) { + super(xId, srcIpId, srcPortStart, srcPortEnd, protocol, networkId, accountId, domainId, Purpose.PortForwarding, sourceCidrs, null, null, null, null); this.destinationIpAddress = dstIp; this.virtualMachineId = instanceId; this.destinationPortStart = dstPortStart; this.destinationPortEnd = dstPortEnd; - } - - public PortForwardingRuleVO(String xId, long srcIpId, int srcPort, Ip dstIp, int dstPort, String protocol, List sourceCidrs, long networkId, long accountId, - long domainId, long instanceId) { - this(xId, srcIpId, srcPort, srcPort, dstIp, dstPort, dstPort, protocol.toLowerCase(), networkId, accountId, domainId, instanceId); + this.sourceCidrs = sourceCidrs; } @Override @@ -106,4 +106,13 @@ public Long getRelated() { return null; } + public void setSourceCidrList(List sourceCidrs) { + this.sourceCidrs = sourceCidrs; + } + + @Override + public List getSourceCidrList() { + return sourceCidrs; + } + } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java index 29cba516d720..1ddecaa8a178 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java @@ -20,6 +20,9 @@ import javax.inject.Inject; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionLegacy; import org.springframework.stereotype.Component; import com.cloud.network.dao.FirewallRulesCidrsDao; @@ -41,7 +44,7 @@ public class PortForwardingRulesDaoImpl extends GenericDaoBase ActiveRulesSearchByAccount; @Inject - protected FirewallRulesCidrsDao _portForwardingRulesCidrsDao; + protected FirewallRulesCidrsDao portForwardingRulesCidrsDao; protected PortForwardingRulesDaoImpl() { super(); @@ -170,4 +173,44 @@ public PortForwardingRuleVO findByIdAndIp(long id, String secondaryIp) { sc.setParameters("dstIp", secondaryIp); return findOneBy(sc); } + + @Override + public PortForwardingRuleVO persist(PortForwardingRuleVO portForwardingRule) { + return Transaction.execute((TransactionCallback) transactionStatus -> { + PortForwardingRuleVO dbPfRule = super.persist(portForwardingRule); + + portForwardingRulesCidrsDao.persist(portForwardingRule.getId(), portForwardingRule.getSourceCidrList()); + List cidrList = portForwardingRulesCidrsDao.getSourceCidrs(portForwardingRule.getId()); + portForwardingRule.setSourceCidrList(cidrList); + + return dbPfRule; + }); + + } + + @Override + public boolean update(Long id, PortForwardingRuleVO entity) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + + boolean success = super.update(id, entity); + if (!success) { + return false; + } + + portForwardingRulesCidrsDao.updateSourceCidrsForRule(entity.getId(), entity.getSourceCidrList()); + txn.commit(); + + return true; + } + + @Override + public PortForwardingRuleVO findById(Long id) { + PortForwardingRuleVO rule = super.findById(id); + + List sourceCidrList = portForwardingRulesCidrsDao.getSourceCidrs(id); + rule.setSourceCidrList(sourceCidrList); + + return rule; + } } diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java index d721db4b6b47..60cd9a2dff4e 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java +++ b/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/actionworkers/KubernetesClusterResourceModifierActionWorker.java @@ -472,7 +472,7 @@ protected void provisionPublicIpPortForwardingRule(IpAddress publicIp, Network n sourcePort, sourcePort, vmIp, destPort, destPort, - "tcp", networkId, accountId, domainId, vmId); + "tcp", networkId, accountId, domainId, vmId, null); newRule.setDisplay(true); newRule.setState(FirewallRule.State.Add); newRule = portForwardingRulesDao.persist(newRule); diff --git a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java index 4ed480ea68bd..4d4a8b30a567 100644 --- a/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java +++ b/server/src/main/java/com/cloud/network/firewall/FirewallManagerImpl.java @@ -33,6 +33,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.commons.lang3.ObjectUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -391,20 +392,34 @@ public void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflict ((rule.getPurpose() == Purpose.Firewall || newRule.getPurpose() == Purpose.Firewall) && ((newRule.getPurpose() != rule.getPurpose()) || (!newRule.getProtocol() .equalsIgnoreCase(rule.getProtocol())))); - // if both rules are firewall and their cidrs are different, we can skip port ranges verification - boolean bothRulesFirewall = (rule.getPurpose() == newRule.getPurpose() && rule.getPurpose() == Purpose.Firewall); + // if both rules are firewall/port forwarding and their cidrs are different, we can skip port ranges verification boolean duplicatedCidrs = false; + + boolean bothRulesFirewall = (rule.getPurpose() == newRule.getPurpose() && rule.getPurpose() == Purpose.Firewall); if (bothRulesFirewall) { _firewallDao.loadSourceCidrs(rule); _firewallDao.loadSourceCidrs((FirewallRuleVO)newRule); + if (ObjectUtils.anyNull(rule.getSourceCidrList(), newRule.getSourceCidrList())) { + continue; + } + _firewallDao.loadDestinationCidrs(rule); _firewallDao.loadDestinationCidrs((FirewallRuleVO) newRule); - if (rule.getSourceCidrList() == null || newRule.getSourceCidrList() == null) { + duplicatedCidrs = detectConflictingCidrs(rule.getSourceCidrList(), newRule.getSourceCidrList()) && detectConflictingCidrs(rule.getDestinationCidrList(), newRule.getDestinationCidrList()); + } + + boolean bothRulesPortForwarding = rule.getPurpose() == newRule.getPurpose() && rule.getPurpose() == Purpose.PortForwarding; + if (bothRulesPortForwarding) { + _firewallDao.loadSourceCidrs(rule); + _firewallDao.loadSourceCidrs((FirewallRuleVO) newRule); + + if (ObjectUtils.anyNull(rule.getSourceCidrList(), newRule.getSourceCidrList())) { continue; } - duplicatedCidrs = (detectConflictingCidrs(rule.getSourceCidrList(), newRule.getSourceCidrList()) && detectConflictingCidrs(rule.getDestinationCidrList(), newRule.getDestinationCidrList())); + + duplicatedCidrs = detectConflictingCidrs(rule.getSourceCidrList(), newRule.getSourceCidrList()); } if (!oneOfRulesIsFirewall) { @@ -440,18 +455,7 @@ public void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflict if (!notNullPorts) { continue; - } else if (!oneOfRulesIsFirewall && - !(bothRulesFirewall && !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()))) { - //Above else if conditions checks for the conflicting port ranges. - + } else if (checkIfRulesHaveConflictingPortRanges(newRule, rule, oneOfRulesIsFirewall, bothRulesFirewall, bothRulesPortForwarding, duplicatedCidrs)) { // we allow port forwarding rules with the same parameters but different protocols boolean allowPf = (rule.getPurpose() == Purpose.PortForwarding && newRule.getPurpose() == Purpose.PortForwarding && !newRule.getProtocol().equalsIgnoreCase( @@ -478,6 +482,45 @@ public void detectRulesConflict(FirewallRule newRule) throws NetworkRuleConflict } } + protected boolean checkIfRulesHaveConflictingPortRanges(FirewallRule newRule, FirewallRule rule, boolean oneOfRulesIsFirewall, boolean bothRulesFirewall, boolean bothRulesPortForwarding, boolean duplicatedCidrs) { + String rulesAsString = String.format("[%s] and [%s]", rule, newRule); + + if (oneOfRulesIsFirewall) { + s_logger.debug(String.format("Only one of the rules (%s) is firewall; therefore, their port ranges will not conflict.", + rulesAsString)); + return false; + } + + if ((bothRulesFirewall || bothRulesPortForwarding) && !duplicatedCidrs) { + s_logger.debug(String.format("Both rules (%s) are firewall/port forwarding, but they do not have duplicated CIDRs; therefore, their port ranges will not conflict.", + rulesAsString)); + return false; + } + + if (rule.getSourcePortStart() <= newRule.getSourcePortStart() && rule.getSourcePortEnd() >= newRule.getSourcePortStart()) { + s_logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString)); + return true; + } + + if (rule.getSourcePortStart() <= newRule.getSourcePortEnd() && rule.getSourcePortEnd() >= newRule.getSourcePortEnd()) { + s_logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString)); + return true; + } + + if (newRule.getSourcePortStart() <= rule.getSourcePortStart() && newRule.getSourcePortEnd() >= rule.getSourcePortStart()) { + s_logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString)); + return true; + } + + if (newRule.getSourcePortStart() <= rule.getSourcePortEnd() && newRule.getSourcePortEnd() >= rule.getSourcePortEnd()) { + s_logger.debug(String.format("Rules (%s) have conflicting port ranges.", rulesAsString)); + return true; + } + + s_logger.debug(String.format("Rules (%s) do not have conflicting port ranges.", rulesAsString)); + return false; + } + @Override public void validateFirewallRule(Account caller, IPAddressVO ipAddress, Integer portStart, Integer portEnd, String proto, Purpose purpose, FirewallRuleType type, Long networkId, FirewallRule.TrafficType trafficType) { diff --git a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java index a7ed6478efa2..0e3acb953c76 100644 --- a/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java +++ b/server/src/main/java/com/cloud/network/router/CommandSetupHelper.java @@ -27,6 +27,7 @@ import javax.inject.Inject; +import com.cloud.network.rules.PortForwardingRuleVO; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -396,6 +397,7 @@ public void createApplyPortForwardingRulesCommands(final List rulesTO = new ArrayList(); if (rules != null) { for (final PortForwardingRule rule : rules) { + _rulesDao.loadSourceCidrs((PortForwardingRuleVO) rule); final IpAddress sourceIp = _networkModel.getIp(rule.getSourceIpAddressId()); final PortForwardingRuleTO ruleTO = new PortForwardingRuleTO(rule, null, sourceIp.getAddress().addr()); rulesTO.add(ruleTO); diff --git a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java index 7686a8d78872..1b7177ec4168 100644 --- a/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java +++ b/server/src/main/java/com/cloud/network/rules/RulesManagerImpl.java @@ -33,6 +33,7 @@ import com.cloud.vm.VirtualMachineProfileImpl; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.command.user.firewall.ListPortForwardingRulesCmd; +import org.apache.cloudstack.api.command.user.firewall.UpdatePortForwardingRuleCmd; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.log4j.Logger; @@ -103,6 +104,7 @@ import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; +import org.apache.commons.collections.CollectionUtils; public class RulesManagerImpl extends ManagerBase implements RulesManager, RulesService { private static final Logger s_logger = Logger.getLogger(RulesManagerImpl.class); @@ -114,7 +116,7 @@ public class RulesManagerImpl extends ManagerBase implements RulesManager, Rules @Inject PortForwardingRulesDao _portForwardingDao; @Inject - FirewallRulesCidrsDao _firewallCidrsDao; + FirewallRulesCidrsDao firewallCidrsDao; @Inject FirewallRulesDao _firewallDao; @Inject @@ -249,6 +251,7 @@ public PortForwardingRule createPortForwardingRule(final PortForwardingRule rule final Long accountId = ipAddress.getAllocatedToAccountId(); final Long domainId = ipAddress.getAllocatedInDomainId(); + List sourceCidrList = rule.getSourceCidrList(); // start port can't be bigger than end port if (rule.getDestinationPortStart() > rule.getDestinationPortEnd()) { @@ -312,9 +315,8 @@ public PortForwardingRule createPortForwardingRule(final PortForwardingRule rule final IPAddressVO ipAddressFinal = ipAddress; return Transaction.execute((TransactionCallbackWithException) status -> { PortForwardingRuleVO newRule = - new PortForwardingRuleVO(rule.getXid(), rule.getSourceIpAddressId(), rule.getSourcePortStart(), rule.getSourcePortEnd(), dstIpFinal, - rule.getDestinationPortStart(), rule.getDestinationPortEnd(), rule.getProtocol().toLowerCase(), networkId, accountId, domainId, vmId); - + new PortForwardingRuleVO(rule.getXid(), rule.getSourceIpAddressId(), rule.getSourcePortStart(), rule.getSourcePortEnd(), dstIpFinal, + rule.getDestinationPortStart(), rule.getDestinationPortEnd(), rule.getProtocol().toLowerCase(), networkId, accountId, domainId, vmId, sourceCidrList); if (forDisplay != null) { newRule.setDisplay(forDisplay); } @@ -898,6 +900,10 @@ protected boolean applyPortForwardingRules(long ipId, boolean continueOnError, A _accountMgr.checkAccess(caller, null, true, rules.toArray(new PortForwardingRuleVO[rules.size()])); } + for (PortForwardingRuleVO rule : rules) { + rule.setSourceCidrList(firewallCidrsDao.getSourceCidrs(rule.getId())); + } + try { if (!_firewallMgr.applyRules(rules, continueOnError, true)) { return false; @@ -951,6 +957,10 @@ public boolean applyPortForwardingRulesForNetwork(long networkId, boolean contin _accountMgr.checkAccess(caller, null, true, rules.toArray(new PortForwardingRuleVO[rules.size()])); } + for (PortForwardingRuleVO rule: rules) { + rule.setSourceCidrList(firewallCidrsDao.getSourceCidrs(rule.getId())); + } + try { if (!_firewallMgr.applyRules(rules, continueOnError, true)) { return false; @@ -1570,12 +1580,22 @@ public List listAssociatedRulesForGuestNic(Nic nic) { @Override @ActionEvent(eventType = EventTypes.EVENT_NET_RULE_MODIFY, eventDescription = "updating forwarding rule", async = true) - public PortForwardingRule updatePortForwardingRule(long id, Integer privatePort, Integer privateEndPort, Long virtualMachineId, Ip vmGuestIp, String customId, Boolean forDisplay) { - Account caller = CallContext.current().getCallingAccount(); + public PortForwardingRule updatePortForwardingRule(UpdatePortForwardingRuleCmd cmd) { + long id = cmd.getId(); + Integer privatePort = cmd.getPrivatePort(); + Integer privateEndPort = cmd.getPrivateEndPort(); + Long virtualMachineId = cmd.getVirtualMachineId(); + Ip vmGuestIp = cmd.getVmGuestIp(); + String customId = cmd.getCustomId(); + Boolean forDisplay = cmd.getDisplay(); + List sourceCidrList = cmd.getSourceCidrList(); + PortForwardingRuleVO rule = _portForwardingDao.findById(id); if (rule == null) { throw new InvalidParameterValueException("Unable to find " + id); } + + Account caller = CallContext.current().getCallingAccount(); _accountMgr.checkAccess(caller, null, true, rule); if (customId != null) { @@ -1636,6 +1656,8 @@ public PortForwardingRule updatePortForwardingRule(long id, Integer privatePort, } } + validatePortForwardingSourceCidrList(sourceCidrList); + // revoke old rules at first List rules = new ArrayList(); rule.setState(State.Revoke); @@ -1663,6 +1685,11 @@ public PortForwardingRule updatePortForwardingRule(long id, Integer privatePort, rule.setVirtualMachineId(virtualMachineId); rule.setDestinationIpAddress(dstIp); } + + if (sourceCidrList != null) { + rule.setSourceCidrList(sourceCidrList); + } + _portForwardingDao.update(id, rule); //apply new rules @@ -1672,4 +1699,17 @@ public PortForwardingRule updatePortForwardingRule(long id, Integer privatePort, return _portForwardingDao.findById(id); } + + @Override + public void validatePortForwardingSourceCidrList(List sourceCidrList) { + if (CollectionUtils.isEmpty(sourceCidrList)) { + return; + } + + for (String cidr : sourceCidrList) { + if (!NetUtils.isValidCidrList(cidr) && !NetUtils.isValidIp6Cidr(cidr)) { + throw new InvalidParameterValueException(String.format("The given source CIDR [%s] is invalid.", cidr)); + } + } + } } diff --git a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java index 2200d6b125bf..e8f01576e4c4 100644 --- a/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java +++ b/server/src/test/java/com/cloud/network/firewall/FirewallManagerTest.java @@ -27,7 +27,6 @@ import com.cloud.network.element.FirewallServiceProvider; import com.cloud.network.element.VirtualRouterElement; import com.cloud.network.element.VpcVirtualRouterElement; -import com.cloud.network.rules.FirewallManager; import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.FirewallRule.Purpose; import com.cloud.network.rules.FirewallRuleVO; @@ -35,9 +34,9 @@ import com.cloud.user.AccountManager; import com.cloud.user.DomainManager; import com.cloud.utils.component.ComponentContext; -import junit.framework.Assert; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.log4j.Logger; +import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -45,7 +44,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; @@ -108,12 +108,35 @@ public void testInjected() { @Mock FirewallRulesDao _firewallDao; + @Spy @InjectMocks - FirewallManager _firewallMgr = new FirewallManagerImpl(); + FirewallManagerImpl _firewallMgr; + + FirewallRule fwRule50to150; + FirewallRule fwRule100to200; + FirewallRule fwRule151to200; + + FirewallRule pfRule50to150; + FirewallRule pfRule100to200; + FirewallRule pfRule151to200; + @Before public void initMocks() { MockitoAnnotations.initMocks(this); + + fwRule50to150 = createFirewallRule(50, 150, Purpose.Firewall); + fwRule100to200 = createFirewallRule(100, 150, Purpose.Firewall); + fwRule151to200 = createFirewallRule(151, 200, Purpose.Firewall); + + pfRule50to150 = createFirewallRule(50, 150, Purpose.PortForwarding); + pfRule100to200 = createFirewallRule(100, 150, Purpose.PortForwarding); + pfRule151to200 = createFirewallRule(151, 200, Purpose.PortForwarding); + } + + private FirewallRule createFirewallRule(int startPort, int endPort, Purpose purpose) { + return new FirewallRuleVO("xid", 1L, startPort, endPort, "TCP", 2, 3, 4, purpose, new ArrayList<>(), + new ArrayList<>(), 5, 6, null, FirewallRule.TrafficType.Ingress); } @Ignore("Requires database to be set up") @@ -210,6 +233,75 @@ public void testDetectRulesConflict() { } } + @Test + public void checkIfRulesHaveConflictingPortRangesTestOnlyOneRuleIsFirewallReturnsFalse() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(fwRule50to150, pfRule50to150, true, false, false, true); + + Assert.assertFalse(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesAreFirewallButNoDuplicateCidrsReturnsFalse() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(fwRule50to150, fwRule50to150, false, true, false, false); + + Assert.assertFalse(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesArePortForwardingButNoDuplicateCidrsReturnsFalse() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(pfRule50to150, pfRule50to150, false, false, true, false); + + Assert.assertFalse(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesAreFirewallAndDuplicatedCidrsAndNewRuleSourceStartPortIsInsideExistingRangeReturnsTrue() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(fwRule100to200, fwRule50to150, false, true, false, true); + + Assert.assertTrue(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesAreFirewallAndDuplicatedCidrsAndNewRuleSourceEndPortIsInsideExistingRangeReturnsTrue() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(fwRule50to150, fwRule100to200, false, true, false, true); + + Assert.assertTrue(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesArePortForwardingAndDuplicatedCidrsAndNewRuleSourceStartPortIsInsideExistingRangeReturnsTrue() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(pfRule50to150, pfRule100to200, false, false, true, true); + + Assert.assertTrue(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesArePortForwardingAndDuplicatedCidrsAndNewRuleSourceEndPortIsInsideExistingRangeReturnsTrue() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(pfRule50to150, pfRule100to200, false, false, true, true); + + Assert.assertTrue(result); + } + + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesAreFirewallAndDuplicatedCidrsAndNoRangeConflictReturnsFalse() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(fwRule50to150, fwRule151to200, false, true, false, true); + Assert.assertFalse(result); + } + @Test + public void checkIfRulesHaveConflictingPortRangesTestBothRulesArePortForwardingAndDuplicatedCidrsAndNoRangeConflictReturnsFalse() + { + boolean result = _firewallMgr.checkIfRulesHaveConflictingPortRanges(pfRule50to150, pfRule151to200, false, false, true, true); + + Assert.assertFalse(result); + } } diff --git a/systemvm/debian/opt/cloud/bin/configure.py b/systemvm/debian/opt/cloud/bin/configure.py index c261293af0fb..fd421fc268fa 100755 --- a/systemvm/debian/opt/cloud/bin/configure.py +++ b/systemvm/debian/opt/cloud/bin/configure.py @@ -1217,7 +1217,10 @@ def forward_vr(self, rule): self.fw.append(["filter", "", fw7]) def forward_vpc(self, rule): - fw_prerout_rule = "-A PREROUTING -d %s/32 " % (rule["public_ip"]) + fw_prerout_rule = "-A PREROUTING" + if "source_cidr_list" in rule and rule["source_cidr_list"]: + fw_prerout_rule += " -s %s" % rule["source_cidr_list"] + fw_prerout_rule += " -d %s/32" % rule["public_ip"] if not rule["protocol"] == "any": fw_prerout_rule += " -m %s -p %s" % (rule["protocol"], rule["protocol"]) if not rule["public_ports"] == "any": @@ -1226,7 +1229,10 @@ def forward_vpc(self, rule): if not rule["internal_ports"] == "any": fw_prerout_rule += ":" + self.portsToString(rule["internal_ports"], "-") - fw_output_rule = "-A OUTPUT -d %s/32" % rule["public_ip"] + fw_output_rule = "-A OUTPUT" + if "source_cidr_list" in rule and rule["source_cidr_list"]: + fw_output_rule += " -s %s" % rule["source_cidr_list"] + fw_output_rule += " -d %s/32" % rule["public_ip"] if not rule["protocol"] == "any": fw_output_rule += " -m %s -p %s" % (rule["protocol"], rule["protocol"]) if not rule["public_ports"] == "any": diff --git a/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py b/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py index a034034dc8bd..14a49feda762 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py @@ -327,7 +327,7 @@ def get_rule(self): return self.rule def to_str(self, delete=False): - """ Convert the rule back into aynactically correct iptables command """ + """ Convert the rule back into syntactically correct iptables command """ # Order is important order = ['-A', '-s', '-d', '!_-d', '-i', '!_-i', '-p', '-m', '-m2', '--icmp-type', '--state', '--dport', '--destination-port', '-o', '!_-o', '-j', '--set-xmark', '--checksum', diff --git a/systemvm/debian/opt/cloud/bin/cs_forwardingrules.py b/systemvm/debian/opt/cloud/bin/cs_forwardingrules.py index 974c468e8dce..c8b4c91ad754 100755 --- a/systemvm/debian/opt/cloud/bin/cs_forwardingrules.py +++ b/systemvm/debian/opt/cloud/bin/cs_forwardingrules.py @@ -16,6 +16,8 @@ # under the License. +import logging + def merge(dbag, rules): for rule in rules["rules"]: source_ip = rule["source_ip_address"] @@ -33,6 +35,8 @@ def merge(dbag, rules): newrule["public_ports"] = rule["source_port_range"] newrule["internal_ports"] = rule["destination_port_range"] newrule["protocol"] = rule["protocol"] + if "source_cidr_list" in rule: + newrule["source_cidr_list"] = rule["source_cidr_list"] if not revoke: if rules["type"] == "staticnatrules": @@ -59,7 +63,7 @@ def merge(dbag, rules): for forward in dbag[source_ip]: if ruleCompare(forward, newrule): index = dbag[source_ip].index(forward) - print "removing index %s" % str(index) + logging.info("Removing forwarding rule [%s] at index [%s].", forward, index) if not index == -1: del dbag[source_ip][index] @@ -74,4 +78,18 @@ def ruleCompare(ruleA, ruleB): return ruleA["public_ip"] == ruleB["public_ip"] elif ruleA["type"] == "forward": return ruleA["public_ip"] == ruleB["public_ip"] and ruleA["public_ports"] == ruleB["public_ports"] \ - and ruleA["protocol"] == ruleB["protocol"] + and ruleA["protocol"] == ruleB["protocol"] and cidrsConflict(ruleA.get("source_cidr_list"), ruleB.get("source_cidr_list")) + +# Same validation as in com.cloud.network.firewall.FirewallManagerImpl.detectConflictingCidrs +def cidrsConflict(cidrListA, cidrListB): + if not cidrListA and not cidrListB: + return True + if not cidrListA: + return False + if not cidrListB: + return False + + cidrListA = set(cidrListA.split(",")) + cidrListB = set(cidrListB.split(",")) + + return len(cidrListA.intersection(cidrListB)) > 0 diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 9e507a82023c..874e556f26ca 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -1945,6 +1945,7 @@ "label.source": "Select Import-Export Source Hypervisor", "label.source.based": "SourceBased", "label.sourcecidr": "Source CIDR", +"label.sourcecidrlist": "Source CIDR list", "label.sourcehost": "Source host", "label.sourceipaddress": "Source IP address", "label.sourceipaddressnetworkid": "Network ID of source IP address", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index c36a5c762a71..937caa69a59f 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -349,7 +349,7 @@ "label.choose.saml.identity": "Escolha um provedor de identidade SAML", "label.cidr": "CIDR", "label.cidr.destination.network": "CIDR da rede de destino", -"label.cidrlist": "Lista de CIDR ", +"label.cidrlist": "Lista de CIDRs", "label.cisco.nexus1000v.ip.address": "Endere\u00e7o IP do Nexus 1000v", "label.cisco.nexus1000v.password": "Senha do Nexus 1000v", "label.cisco.nexus1000v.username": "Usu\u00e1rio do Nexus 1000v", @@ -1494,6 +1494,7 @@ "label.sockettimeout": "Tempo limite no socket", "label.source.based": "SourceBased", "label.sourcecidr": "CIDR de origem", +"label.sourcecidrlist": "Lista de CIDRs de origem", "label.sourceipaddress": "Endere\u00e7o IP de origem", "label.sourcenat": "Source NAT", "label.sourcenatsupported": "Suporte \u00e0 source NAT", diff --git a/ui/src/views/network/LoadBalancing.vue b/ui/src/views/network/LoadBalancing.vue index eeec232cee53..09f9a39c4cfa 100644 --- a/ui/src/views/network/LoadBalancing.vue +++ b/ui/src/views/network/LoadBalancing.vue @@ -37,7 +37,7 @@
- +
@@ -126,7 +126,7 @@ :rowKey="record => record.id">