diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 7a2c3456376..204efdf1a1d 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -1735,8 +1735,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra } //apply network ACLs - // TODO: remove check for NSX - if (!offering.isForNsx() && !_networkACLMgr.applyACLToNetwork(networkId)) { + if (!_networkACLMgr.applyACLToNetwork(networkId)) { s_logger.warn("Failed to reapply network ACLs as a part of of network id=" + networkId + " restart"); success = false; } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxDistributedFirewallRulesCommand.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxDistributedFirewallRulesCommand.java new file mode 100644 index 00000000000..9283d1292cd --- /dev/null +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/CreateNsxDistributedFirewallRulesCommand.java @@ -0,0 +1,49 @@ +// 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 org.apache.cloudstack.agent.api; + +import org.apache.cloudstack.resource.NsxNetworkRule; + +import java.util.List; + +public class CreateNsxDistributedFirewallRulesCommand extends NsxCommand { + + private Long vpcId; + private long networkId; + private List rules; + + public CreateNsxDistributedFirewallRulesCommand(long domainId, long accountId, long zoneId, + Long vpcId, long networkId, + List rules) { + super(domainId, accountId, zoneId); + this.vpcId = vpcId; + this.networkId = networkId; + this.rules = rules; + } + + public Long getVpcId() { + return vpcId; + } + + public long getNetworkId() { + return networkId; + } + + public List getRules() { + return rules; + } +} diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeletedNsxDistributedFirewallRulesCommand.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeletedNsxDistributedFirewallRulesCommand.java new file mode 100644 index 00000000000..11ae52439f3 --- /dev/null +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/agent/api/DeletedNsxDistributedFirewallRulesCommand.java @@ -0,0 +1,28 @@ + +// 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 org.apache.cloudstack.agent.api; + +import org.apache.cloudstack.resource.NsxNetworkRule; + +import java.util.List; + +public class DeletedNsxDistributedFirewallRulesCommand extends CreateNsxDistributedFirewallRulesCommand { + public DeletedNsxDistributedFirewallRulesCommand(long domainId, long accountId, long zoneId, Long vpcId, long networkId, List rules) { + super(domainId, accountId, zoneId, vpcId, networkId, rules); + } +} \ No newline at end of file diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java index 5d830f1347a..fccbe3b55d8 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxNetworkRule.java @@ -16,9 +16,16 @@ // under the License. package org.apache.cloudstack.resource; +import com.cloud.network.Network; + import java.util.List; public class NsxNetworkRule { + + public enum NsxRuleAction { + ALLOW, DROP + } + private long domainId; private long accountId; private long zoneId; @@ -34,6 +41,36 @@ public class NsxNetworkRule { private String protocol; private String algorithm; private List memberList; + private NsxRuleAction aclAction; + private List cidrList; + private String trafficType; + private Integer icmpCode; + private Integer icmpType; + private Network.Service service; + + public Integer getIcmpCode() { + return icmpCode; + } + + public void setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + } + + public Integer getIcmpType() { + return icmpType; + } + + public void setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + } + + public Network.Service getService() { + return service; + } + + public void setService(Network.Service service) { + this.service = service; + } public long getDomainId() { return domainId; @@ -155,6 +192,30 @@ public class NsxNetworkRule { this.memberList = memberList; } + public NsxRuleAction getAclAction() { + return aclAction; + } + + public void setAclAction(NsxRuleAction aclAction) { + this.aclAction = aclAction; + } + + public List getCidrList() { + return cidrList; + } + + public void setCidrList(List cidrList) { + this.cidrList = cidrList; + } + + public String getTrafficType() { + return trafficType; + } + + public void setTrafficType(String trafficType) { + this.trafficType = trafficType; + } + public static final class Builder { private long domainId; private long accountId; @@ -172,6 +233,12 @@ public class NsxNetworkRule { private String protocol; private String algorithm; private List memberList; + private NsxRuleAction aclAction; + private List cidrList; + private String trafficType; + private Integer icmpType; + private Integer icmpCode; + private Network.Service service; public Builder() { } @@ -252,6 +319,36 @@ public class NsxNetworkRule { return this; } + public Builder setAclAction(NsxRuleAction aclAction) { + this.aclAction = aclAction; + return this; + } + + public Builder setCidrList(List cidrList) { + this.cidrList = cidrList; + return this; + } + + public Builder setTrafficType(String trafficType) { + this.trafficType = trafficType; + return this; + } + + public Builder setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + return this; + } + + public Builder setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + return this; + } + + public Builder setService(Network.Service service) { + this.service = service; + return this; + } + public NsxNetworkRule build() { NsxNetworkRule rule = new NsxNetworkRule(); rule.setDomainId(this.domainId); @@ -269,6 +366,12 @@ public class NsxNetworkRule { rule.setRuleId(this.ruleId); rule.setAlgorithm(this.algorithm); rule.setMemberList(this.memberList); + rule.setAclAction(this.aclAction); + rule.setCidrList(this.cidrList); + rule.setTrafficType(this.trafficType); + rule.setIcmpType(this.icmpType); + rule.setIcmpCode(this.icmpCode); + rule.setService(this.service); return rule; } } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java index 7ca38bb748c..04035a545df 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/resource/NsxResource.java @@ -36,6 +36,7 @@ import com.vmware.nsx_policy.model.SiteListResult; import org.apache.cloudstack.NsxAnswer; import org.apache.cloudstack.StartupNsxCommand; import org.apache.cloudstack.agent.api.CreateNsxDhcpRelayConfigCommand; +import org.apache.cloudstack.agent.api.CreateNsxDistributedFirewallRulesCommand; import org.apache.cloudstack.agent.api.CreateNsxLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.CreateNsxPortForwardRuleCommand; import org.apache.cloudstack.agent.api.CreateNsxSegmentCommand; @@ -46,6 +47,7 @@ import org.apache.cloudstack.agent.api.DeleteNsxLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNsxSegmentCommand; import org.apache.cloudstack.agent.api.DeleteNsxNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNsxTier1GatewayCommand; +import org.apache.cloudstack.agent.api.DeletedNsxDistributedFirewallRulesCommand; import org.apache.cloudstack.service.NsxApiClient; import org.apache.cloudstack.utils.NsxControllerUtils; import org.apache.commons.collections.CollectionUtils; @@ -123,6 +125,10 @@ public class NsxResource implements ServerResource { return executeRequest((CreateNsxLoadBalancerRuleCommand) cmd); } else if (cmd instanceof DeleteNsxLoadBalancerRuleCommand) { return executeRequest((DeleteNsxLoadBalancerRuleCommand) cmd); + } else if (cmd instanceof DeletedNsxDistributedFirewallRulesCommand) { + return executeRequest((DeletedNsxDistributedFirewallRulesCommand) cmd); + } else if (cmd instanceof CreateNsxDistributedFirewallRulesCommand) { + return executeRequest((CreateNsxDistributedFirewallRulesCommand) cmd); } else { return Answer.createUnsupportedCommandAnswer(cmd); } @@ -353,6 +359,7 @@ public class NsxResource implements ServerResource { String tier1GatewayName = NsxControllerUtils.getTier1GatewayName(cmd.getDomainId(), cmd.getAccountId(), cmd.getZoneId(), networkResourceId, isResourceVpc); nsxApiClient.createSegment(segmentName, tier1GatewayName, gatewayAddress, enforcementPointPath, transportZones); + nsxApiClient.createGroupForSegment(segmentName); } catch (Exception e) { LOGGER.error(String.format("Failed to create network: %s", cmd.getNetworkName())); return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); @@ -394,8 +401,8 @@ public class NsxResource implements ServerResource { cmd.getNetworkResourceId(), cmd.isResourceVpc()); try { String privatePort = cmd.getPrivatePort(); - String service = privatePort.contains("-") ? nsxApiClient.createNsxInfraService(ruleName, privatePort, cmd.getProtocol()) : - nsxApiClient.getNsxInfraServices(ruleName, privatePort, cmd.getProtocol()); + String service = privatePort.contains("-") ? nsxApiClient.getServicePath(ruleName, privatePort, cmd.getProtocol(), null, null) : + nsxApiClient.getNsxInfraServices(ruleName, privatePort, cmd.getProtocol(), null, null); nsxApiClient.createPortForwardingRule(ruleName, tier1GatewayName, cmd.getNetworkResourceName(), cmd.getPublicIp(), cmd.getVmIp(), cmd.getPublicPort(), service); @@ -454,6 +461,32 @@ public class NsxResource implements ServerResource { return new NsxAnswer(cmd, true, null); } + private NsxAnswer executeRequest(CreateNsxDistributedFirewallRulesCommand cmd) { + String segmentName = NsxControllerUtils.getNsxSegmentId(cmd.getDomainId(), cmd.getAccountId(), + cmd.getZoneId(), cmd.getVpcId(), cmd.getNetworkId()); + List rules = cmd.getRules(); + try { + nsxApiClient.createSegmentDistributedFirewall(segmentName, rules); + } catch (Exception e) { + LOGGER.error(String.format("Failed to create NSX distributed firewall %s: %s", segmentName, e.getMessage()), e); + return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); + } + return new NsxAnswer(cmd, true, null); + } + + private NsxAnswer executeRequest(DeletedNsxDistributedFirewallRulesCommand cmd) { + String segmentName = NsxControllerUtils.getNsxSegmentId(cmd.getDomainId(), cmd.getAccountId(), + cmd.getZoneId(), cmd.getVpcId(), cmd.getNetworkId()); + List rules = cmd.getRules(); + try { + nsxApiClient.deleteDistributedFirewallRules(segmentName, rules); + } catch (Exception e) { + LOGGER.error(String.format("Failed to create NSX distributed firewall %s: %s", segmentName, e.getMessage()), e); + return new NsxAnswer(cmd, new CloudRuntimeException(e.getMessage())); + } + return new NsxAnswer(cmd, true, null); + } + @Override public boolean start() { return true; diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java index 3f4203c37ea..e60a7373aac 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxApiClient.java @@ -29,12 +29,18 @@ import com.vmware.nsx_policy.infra.Segments; import com.vmware.nsx_policy.infra.Services; import com.vmware.nsx_policy.infra.Sites; import com.vmware.nsx_policy.infra.Tier1s; +import com.vmware.nsx_policy.infra.domains.Groups; +import com.vmware.nsx_policy.infra.domains.SecurityPolicies; +import com.vmware.nsx_policy.infra.domains.security_policies.Rules; import com.vmware.nsx_policy.infra.sites.EnforcementPoints; import com.vmware.nsx_policy.infra.tier_0s.LocaleServices; import com.vmware.nsx_policy.infra.tier_1s.nat.NatRules; import com.vmware.nsx_policy.model.ApiError; import com.vmware.nsx_policy.model.DhcpRelayConfig; import com.vmware.nsx_policy.model.EnforcementPointListResult; +import com.vmware.nsx_policy.model.Group; +import com.vmware.nsx_policy.model.GroupListResult; +import com.vmware.nsx_policy.model.ICMPTypeServiceEntry; import com.vmware.nsx_policy.model.L4PortSetServiceEntry; import com.vmware.nsx_policy.model.LBAppProfileListResult; import com.vmware.nsx_policy.model.LBPool; @@ -44,8 +50,11 @@ import com.vmware.nsx_policy.model.LBService; import com.vmware.nsx_policy.model.LBVirtualServer; import com.vmware.nsx_policy.model.LBVirtualServerListResult; import com.vmware.nsx_policy.model.LocaleServicesListResult; +import com.vmware.nsx_policy.model.PathExpression; import com.vmware.nsx_policy.model.PolicyNatRule; import com.vmware.nsx_policy.model.PolicyNatRuleListResult; +import com.vmware.nsx_policy.model.Rule; +import com.vmware.nsx_policy.model.SecurityPolicy; import com.vmware.nsx_policy.model.Segment; import com.vmware.nsx_policy.model.SegmentSubnet; import com.vmware.nsx_policy.model.ServiceListResult; @@ -64,6 +73,7 @@ import com.vmware.vapi.internal.protocol.client.rest.authn.BasicAuthenticationAp import com.vmware.vapi.protocol.HttpConfiguration; import com.vmware.vapi.std.errors.Error; import org.apache.cloudstack.resource.NsxLoadBalancerMember; +import org.apache.cloudstack.resource.NsxNetworkRule; import org.apache.cloudstack.utils.NsxControllerUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.log4j.Logger; @@ -86,7 +96,7 @@ import static org.apache.cloudstack.utils.NsxControllerUtils.getLoadBalancerAlgo public class NsxApiClient { - private final Function, Service> nsxService; + protected Function, Service> nsxService; public static final int RESPONSE_TIMEOUT_SECONDS = 60; private static final Logger LOGGER = Logger.getLogger(NsxApiClient.class); @@ -97,6 +107,9 @@ public class NsxApiClient { private static final String SEGMENT_RESOURCE_TYPE = "Segment"; private static final String TIER_0_GATEWAY_PATH_PREFIX = "/infra/tier-0s/"; private static final String TIER_1_GATEWAY_PATH_PREFIX = "/infra/tier-1s/"; + protected static final String SEGMENTS_PATH = "/infra/segments"; + protected static final String DEFAULT_DOMAIN = "default"; + protected static final String GROUPS_PATH_PREFIX = "/infra/domains/default/groups"; private enum PoolAllocation { ROUTING, LB_SMALL, LB_MEDIUM, LB_LARGE, LB_XLARGE } @@ -137,6 +150,9 @@ public class NsxApiClient { TIER1_LB_VIP, TIER1_LB_SNAT, TIER1_DNS_FORWARDER_IP, TIER1_IPSEC_LOCAL_ENDPOINT } + protected NsxApiClient() { + } + public NsxApiClient(String hostname, String port, String username, char[] password) { String controllerUrl = String.format("https://%s:%s", hostname, port); HttpConfiguration.SslConfiguration.Builder sslConfigBuilder = new HttpConfiguration.SslConfiguration.Builder(); @@ -376,6 +392,8 @@ public class NsxApiClient { public void deleteSegment(long zoneId, long domainId, long accountId, Long vpcId, long networkId, String segmentName) { try { Segments segmentService = (Segments) nsxService.apply(Segments.class); + removeSegmentDistributedFirewallRules(segmentName); + removeGroupForSegment(segmentName); LOGGER.debug(String.format("Removing the segment with ID %s", segmentName)); segmentService.delete(segmentName); DhcpRelayConfigs dhcpRelayConfig = (DhcpRelayConfigs) nsxService.apply(DhcpRelayConfigs.class); @@ -422,7 +440,7 @@ public class NsxApiClient { // delete NAT rule natService.delete(tier1GatewayName, NatId.USER.name(), ruleName); if (service == Network.Service.PortForwarding) { - String svcName = getServiceName(ruleName, privatePort, protocol); + String svcName = getServiceName(ruleName, privatePort, protocol, null, null); // Delete service Services services = (Services) nsxService.apply(Services.class); services.delete(svcName); @@ -635,7 +653,7 @@ public class NsxApiClient { } } - public String getNsxInfraServices(String ruleName, String port, String protocol) { + public String getNsxInfraServices(String ruleName, String port, String protocol, Integer icmpType, Integer icmpCode) { try { Services service = (Services) nsxService.apply(Services.class); @@ -660,7 +678,7 @@ public class NsxApiClient { } // Else, create a service entry - return createNsxInfraService(ruleName, port, protocol); + return getServicePath(ruleName, port, protocol, icmpType, icmpCode); } catch (Error error) { ApiError ae = error.getData()._convertTo(ApiError.class); String msg = String.format("Failed to list NSX infra service, due to: %s", ae.getErrorMessage()); @@ -669,28 +687,47 @@ public class NsxApiClient { } } - public String createNsxInfraService(String ruleName, String port, String protocol) { + private com.vmware.nsx_policy.model.Service getInfraService(String ruleName, String port, String protocol, Integer icmpType, Integer icmpCode) { + Services service = (Services) nsxService.apply(Services.class); + String serviceName = getServiceName(ruleName, port, protocol, icmpType, icmpCode); + createNsxInfraService(service, serviceName, ruleName, port, protocol, icmpType, icmpCode); + return service.get(serviceName); + } + + public String getServicePath(String ruleName, String port, String protocol, Integer icmpType, Integer icmpCode) { + com.vmware.nsx_policy.model.Service svc = getInfraService(ruleName, port, protocol, icmpType, icmpCode); + return svc.getServiceEntries().get(0)._getDataValue().getField("parent_path").toString(); + } + + public void createNsxInfraService(Services service, String serviceName, String ruleName, String port, String protocol, + Integer icmpType, Integer icmpCode) { try { + List serviceEntries = new ArrayList<>(); + protocol = "ICMP".equalsIgnoreCase(protocol) ? "ICMPv4" : protocol; String serviceEntryName = getServiceEntryName(ruleName, port, protocol); - String serviceName = getServiceName(ruleName, port, protocol); - Services service = (Services) nsxService.apply(Services.class); + if (protocol.equals("ICMPv4")) { + serviceEntries.add(new ICMPTypeServiceEntry.Builder() + .setId(serviceEntryName) + .setDisplayName(serviceEntryName) +// .setIcmpCode(Long.valueOf(icmpCode)) + .setIcmpType(Long.valueOf(icmpType)) + .setProtocol(protocol) + .build() + ); + } else { + serviceEntries.add(new L4PortSetServiceEntry.Builder() + .setId(serviceEntryName) + .setDisplayName(serviceEntryName) + .setDestinationPorts(List.of(port)) + .setL4Protocol(protocol) + .build()); + } com.vmware.nsx_policy.model.Service infraService = new com.vmware.nsx_policy.model.Service.Builder() - .setServiceEntries(List.of( - new L4PortSetServiceEntry.Builder() - .setId(serviceEntryName) - .setDisplayName(serviceEntryName) - .setDestinationPorts(List.of(port)) - .setL4Protocol(protocol) - .build() - )) + .setServiceEntries(serviceEntries) .setId(serviceName) .setDisplayName(serviceName) .build(); service.patch(serviceName, infraService); - - com.vmware.nsx_policy.model.Service svc = service.get(serviceName); - return svc.getServiceEntries().get(0)._getDataValue().getField("parent_path").toString(); - } catch (Error error) { ApiError ae = error.getData()._convertTo(ApiError.class); String msg = String.format("Failed to create NSX infra service, due to: %s", ae.getErrorMessage()); @@ -711,4 +748,152 @@ public class NsxApiClient { } return null; } + + /** + * Create a Group for the Segment on the Inventory, with the same name as the segment and being the segment the only member of the group + */ + public void createGroupForSegment(String segmentName) { + LOGGER.info(String.format("Creating Group for Segment %s", segmentName)); + + PathExpression pathExpression = new PathExpression(); + List paths = List.of(String.format("%s/%s", SEGMENTS_PATH, segmentName)); + pathExpression.setPaths(paths); + + Groups service = (Groups) nsxService.apply(Groups.class); + Group group = new Group.Builder() + .setId(segmentName) + .setDisplayName(segmentName) + .setExpression(List.of(pathExpression)) + .build(); + service.patch(DEFAULT_DOMAIN, segmentName, group); + } + + /** + * Remove Segment Group from the Inventory + */ + private void removeGroupForSegment(String segmentName) { + LOGGER.info(String.format("Removing Group for Segment %s", segmentName)); + Groups service = (Groups) nsxService.apply(Groups.class); + service.delete(DEFAULT_DOMAIN, segmentName, true, false); + } + + private void removeSegmentDistributedFirewallRules(String segmentName) { + try { + SecurityPolicies services = (SecurityPolicies) nsxService.apply(SecurityPolicies.class); + services.delete(DEFAULT_DOMAIN, segmentName); + } catch (Error error) { + ApiError ae = error.getData()._convertTo(ApiError.class); + String msg = String.format("Failed to remove NSX distributed firewall policy for segment %s, due to: %s", segmentName, ae.getErrorMessage()); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + } + + public void createSegmentDistributedFirewall(String segmentName, List nsxRules) { + try { + String groupPath = getGroupPath(segmentName); + if (Objects.isNull(groupPath)) { + throw new CloudRuntimeException(String.format("Failed to find group for segment %s", segmentName)); + } + SecurityPolicies services = (SecurityPolicies) nsxService.apply(SecurityPolicies.class); + List rules = getRulesForDistributedFirewall(segmentName, nsxRules); + SecurityPolicy policy = new SecurityPolicy.Builder() + .setDisplayName(segmentName) + .setId(segmentName) + .setCategory("Application") + .setRules(rules) + .setScope(List.of(groupPath)) + .build(); + services.patch(DEFAULT_DOMAIN, segmentName, policy); + } catch (Error error) { + ApiError ae = error.getData()._convertTo(ApiError.class); + String msg = String.format("Failed to create NSX distributed firewall policy for segment %s, due to: %s", segmentName, ae.getErrorMessage()); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + } + + public void deleteDistributedFirewallRules(String segmentName, List nsxRules) { + for(NsxNetworkRule rule : nsxRules) { + String ruleId = NsxControllerUtils.getNsxDistributedFirewallPolicyRuleId(segmentName, rule.getRuleId()); + String svcName = getServiceName(ruleId, rule.getPrivatePort(), rule.getProtocol(), rule.getIcmpType(), rule.getIcmpCode()); + // delete rules + Rules rules = (Rules) nsxService.apply(Rules.class); + rules.delete(DEFAULT_DOMAIN, segmentName, ruleId); + // delete service - if any + Services services = (Services) nsxService.apply(Services.class); + services.delete(svcName); + } + } + + private List getRulesForDistributedFirewall(String segmentName, List nsxRules) { + List rules = new ArrayList<>(); + String groupPath = getGroupPath(segmentName); + if (Objects.isNull(groupPath)) { + throw new CloudRuntimeException(String.format("Failed to find group for segment %s", segmentName)); + } + for (NsxNetworkRule rule : nsxRules) { + String ruleId = NsxControllerUtils.getNsxDistributedFirewallPolicyRuleId(segmentName, rule.getRuleId()); + Rule ruleToAdd = new Rule.Builder() + .setAction(rule.getAclAction().toString()) + .setId(ruleId) + .setDisplayName(ruleId) + .setResourceType("SecurityPolicy") + .setSourceGroups(getGroupsForTraffic(rule, segmentName, true)) + .setDestinationGroups(getGroupsForTraffic(rule, segmentName, false)) + .setServices(getServicesListForDistributedFirewallRule(rule, segmentName)) + .setScope(List.of(groupPath)) + .build(); + rules.add(ruleToAdd); + } + return rules; + } + + private List getServicesListForDistributedFirewallRule(NsxNetworkRule rule, String segmentName) { + List services = List.of("ANY"); + if (!rule.getProtocol().equalsIgnoreCase("all")) { + String ruleName = String.format("%s-R%s", segmentName, rule.getRuleId()); + String serviceName = getNsxInfraServices(ruleName, rule.getPrivatePort(), rule.getProtocol(), + rule.getIcmpType(), rule.getIcmpCode()); + services = List.of(serviceName); + } + return services; + } + + protected List getGroupsForTraffic(NsxNetworkRule rule, + String segmentName, boolean source) { + List segmentGroup = List.of(String.format("%s/%s", GROUPS_PATH_PREFIX, segmentName)); + List ruleCidrList = rule.getCidrList(); + + String trafficType = rule.getTrafficType(); + if (trafficType.equalsIgnoreCase("ingress")) { + return source ? ruleCidrList : segmentGroup; + } else if (trafficType.equalsIgnoreCase("egress")) { + return source ? segmentGroup : ruleCidrList; + } + String err = String.format("Unsupported traffic type %s", trafficType); + LOGGER.error(err); + throw new CloudRuntimeException(err); + } + + private List listNsxGroups() { + try { + Groups groups = (Groups) nsxService.apply(Groups.class); + GroupListResult result = groups.list(DEFAULT_DOMAIN, null, false, null, null, null, null, null); + return result.getResults(); + } catch (Error error) { + ApiError ae = error.getData()._convertTo(ApiError.class); + String msg = String.format("Failed to list NSX groups, due to: %s", ae.getErrorMessage()); + LOGGER.error(msg); + throw new CloudRuntimeException(msg); + } + } + + private String getGroupPath(String segmentName) { + List groups = listNsxGroups(); + Optional matchingGroup = groups.stream().filter(group -> group.getDisplayName().equals(segmentName)).findFirst(); + return matchingGroup.map(Group::getPath).orElse(null); + + } + } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java index ed078bdaa4a..36fcdc37a66 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxElement.java @@ -56,6 +56,7 @@ import com.cloud.network.element.DhcpServiceProvider; import com.cloud.network.element.DnsServiceProvider; import com.cloud.network.element.IpDeployer; import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.NetworkACLServiceProvider; import com.cloud.network.element.PortForwardingServiceProvider; import com.cloud.network.element.StaticNatServiceProvider; import com.cloud.network.element.VpcProvider; @@ -107,7 +108,7 @@ import java.util.function.LongFunction; @Component public class NsxElement extends AdapterBase implements DhcpServiceProvider, DnsServiceProvider, VpcProvider, - StaticNatServiceProvider, IpDeployer, PortForwardingServiceProvider, + StaticNatServiceProvider, IpDeployer, PortForwardingServiceProvider, NetworkACLServiceProvider, LoadBalancingServiceProvider, ResourceStateAdapter, Listener { @@ -159,6 +160,15 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns capabilities.put(Network.Service.Lb, null); capabilities.put(Network.Service.PortForwarding, null); capabilities.put(Network.Service.NetworkACL, null); + + Map firewallCapabilities = new HashMap<>(); + firewallCapabilities.put(Network.Capability.SupportedProtocols, "tcp,udp,icmp"); + firewallCapabilities.put(Network.Capability.SupportedEgressProtocols, "tcp,udp,icmp,all"); + firewallCapabilities.put(Network.Capability.MultipleIps, "true"); + firewallCapabilities.put(Network.Capability.TrafficStatistics, "per public ip"); + firewallCapabilities.put(Network.Capability.SupportedTrafficDirection, "ingress, egress"); + capabilities.put(Network.Service.Firewall, firewallCapabilities); + Map sourceNatCapabilities = new HashMap<>(); sourceNatCapabilities.put(Network.Capability.RedundantRouter, "true"); sourceNatCapabilities.put(Network.Capability.SupportedSourceNatTypes, "peraccount"); @@ -574,6 +584,12 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd())); } + private static String getPrivatePortRangeForACLRule(NetworkACLItem rule) { + return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ? + String.valueOf(rule.getSourcePortStart()) : + String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd())); + } + @Override public boolean applyLBRules(Network network, List rules) throws ResourceUnavailableException { for (LoadBalancingRule loadBalancingRule : rules) { @@ -645,4 +661,70 @@ public class NsxElement extends AdapterBase implements DhcpServiceProvider, Dns } return lbMembers; } + + @Override + public boolean applyNetworkACLs(Network network, List rules) throws ResourceUnavailableException { + if (!canHandle(network, Network.Service.NetworkACL)) { + return false; + } + List nsxAddNetworkRules = new ArrayList<>(); + List nsxDelNetworkRules = new ArrayList<>(); + for (NetworkACLItem rule : rules) { + String privatePort = getPrivatePortRangeForACLRule(rule); + + NsxNetworkRule networkRule = new NsxNetworkRule.Builder() + .setRuleId(rule.getId()) + .setCidrList(transformCidrListValues(rule.getSourceCidrList())) + .setAclAction(transformActionValue(rule.getAction())) + .setTrafficType(rule.getTrafficType().toString()) + .setProtocol(rule.getProtocol().toUpperCase()) + .setPublicPort(String.valueOf(rule.getSourcePortStart())) + .setPrivatePort(privatePort) + .setIcmpCode(rule.getIcmpCode()) + .setIcmpType(rule.getIcmpType()) + .setService(Network.Service.NetworkACL) + .build(); + if (NetworkACLItem.State.Add == rule.getState()) { + nsxAddNetworkRules.add(networkRule); + } else if (NetworkACLItem.State.Revoke == rule.getState()) { + nsxDelNetworkRules.add(networkRule); + } + } + boolean success = true; + if (!nsxDelNetworkRules.isEmpty()) { + success = nsxService.deleteFirewallRules(network, nsxDelNetworkRules); + if (!success) { + LOGGER.warn("Not all firewall rules were successfully deleted"); + } + } + return success && nsxService.addFirewallRules(network, nsxAddNetworkRules); + } + + protected NsxNetworkRule.NsxRuleAction transformActionValue(NetworkACLItem.Action action) { + if (action == NetworkACLItem.Action.Allow) { + return NsxNetworkRule.NsxRuleAction.ALLOW; + } else if (action == NetworkACLItem.Action.Deny) { + return NsxNetworkRule.NsxRuleAction.DROP; + } + String err = String.format("Unsupported action %s", action.toString()); + LOGGER.error(err); + throw new CloudRuntimeException(err); + } + + /** + * Replace 0.0.0.0/0 to ANY on each occurrence + */ + protected List transformCidrListValues(List sourceCidrList) { + List list = new ArrayList<>(); + if (org.apache.commons.collections.CollectionUtils.isNotEmpty(sourceCidrList)) { + for (String cidr : sourceCidrList) { + if (cidr.equals("0.0.0.0/0")) { + list.add("ANY"); + } else { + list.add(cidr); + } + } + } + return list; + } } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java index 6fe5285231c..233f7b9e7bc 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/service/NsxServiceImpl.java @@ -26,6 +26,7 @@ import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.NsxAnswer; +import org.apache.cloudstack.agent.api.CreateNsxDistributedFirewallRulesCommand; import org.apache.cloudstack.agent.api.CreateNsxLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.CreateNsxPortForwardRuleCommand; import org.apache.cloudstack.agent.api.CreateNsxStaticNatCommand; @@ -35,12 +36,14 @@ import org.apache.cloudstack.agent.api.DeleteNsxLoadBalancerRuleCommand; import org.apache.cloudstack.agent.api.DeleteNsxSegmentCommand; import org.apache.cloudstack.agent.api.DeleteNsxNatRuleCommand; import org.apache.cloudstack.agent.api.DeleteNsxTier1GatewayCommand; +import org.apache.cloudstack.agent.api.DeletedNsxDistributedFirewallRulesCommand; import org.apache.cloudstack.resource.NsxNetworkRule; import org.apache.cloudstack.utils.NsxControllerUtils; import org.apache.cloudstack.utils.NsxHelper; import org.apache.log4j.Logger; import javax.inject.Inject; +import java.util.List; import java.util.Objects; public class NsxServiceImpl implements NsxService { @@ -172,4 +175,18 @@ public class NsxServiceImpl implements NsxService { NsxAnswer result = nsxControllerUtils.sendNsxCommand(command, netRule.getZoneId()); return result.getResult(); } + + public boolean addFirewallRules(Network network, List netRules) { + CreateNsxDistributedFirewallRulesCommand command = new CreateNsxDistributedFirewallRulesCommand(network.getDomainId(), + network.getAccountId(), network.getDataCenterId(), network.getVpcId(), network.getId(), netRules); + NsxAnswer result = nsxControllerUtils.sendNsxCommand(command, network.getDataCenterId()); + return result.getResult(); + } + + public boolean deleteFirewallRules(Network network, List netRules) { + DeletedNsxDistributedFirewallRulesCommand command = new DeletedNsxDistributedFirewallRulesCommand(network.getDomainId(), + network.getAccountId(), network.getDataCenterId(), network.getVpcId(), network.getId(), netRules); + NsxAnswer result = nsxControllerUtils.sendNsxCommand(command, network.getDataCenterId()); + return result.getResult(); + } } diff --git a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java index ce6e7992533..eec19e3e075 100644 --- a/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java +++ b/plugins/network-elements/nsx/src/main/java/org/apache/cloudstack/utils/NsxControllerUtils.java @@ -45,6 +45,10 @@ public class NsxControllerUtils { return String.format("D%s-A%s-Z%s-%s%s-NAT", domainId, accountId, dataCenterId, resourcePrefix, resourceId); } + public static String getNsxDistributedFirewallPolicyRuleId(String segmentName, long ruleId) { + return String.format("%s-R%s", segmentName, ruleId); + } + public NsxAnswer sendNsxCommand(NsxCommand cmd, long zoneId) throws IllegalArgumentException { NsxProviderVO nsxProviderVO = nsxProviderDao.findByZoneId(zoneId); @@ -97,8 +101,10 @@ public class NsxControllerUtils { return getTier1GatewayName(domainId, accountId, zoneId, networkResourceId, isVpcResource) + suffix + ruleId; } - public static String getServiceName(String ruleName, String port, String protocol) { - return ruleName + "-SVC-" + port + "-" +protocol; + public static String getServiceName(String ruleName, String port, String protocol, Integer icmpType, Integer icmpCode) { + return protocol.equalsIgnoreCase("icmp") ? + String.format("%s-SVC-%s-%s-%s", ruleName, icmpType, icmpCode, protocol) : + String.format("%s-SVC-%s-%s", ruleName, port, protocol); } public static String getServiceEntryName(String ruleName, String port, String protocol) { diff --git a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java new file mode 100644 index 00000000000..2408b6f1e43 --- /dev/null +++ b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxApiClientTest.java @@ -0,0 +1,93 @@ +// 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 org.apache.cloudstack.service; + +import com.vmware.nsx_policy.infra.domains.Groups; +import com.vmware.nsx_policy.model.Group; +import com.vmware.nsx_policy.model.PathExpression; +import com.vmware.vapi.bindings.Service; +import org.apache.cloudstack.resource.NsxNetworkRule; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.function.Function; + +public class NsxApiClientTest { + + @Mock + private Function, Service> nsxService; + @Mock + private Groups groupService; + + private NsxApiClient client = new NsxApiClient(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + client.nsxService = nsxService; + Mockito.when(nsxService.apply(Groups.class)).thenReturn(groupService); + } + + @Test + public void testCreateGroupForSegment() { + final Group[] groups = new Group[1]; + final PathExpression[] pathExpressions = new PathExpression[1]; + try (MockedConstruction ignored = Mockito.mockConstruction(Group.class, (mock, context) -> { + groups[0] = mock; + }); MockedConstruction ignoredExp = Mockito.mockConstruction(PathExpression.class, (mock, context) -> { + pathExpressions[0] = mock; + }) + ) { + String segmentName = "segment1"; + client.createGroupForSegment(segmentName); + Mockito.verify(groupService).patch(Mockito.eq(NsxApiClient.DEFAULT_DOMAIN), Mockito.eq(segmentName), Mockito.eq(groups[0])); + String segmentPath = String.format("%s/%s", NsxApiClient.SEGMENTS_PATH, segmentName); + Mockito.verify(groups[0]).setExpression(Mockito.eq(List.of(pathExpressions[0]))); + Mockito.verify(pathExpressions[0]).setPaths(List.of(segmentPath)); + } + } + + @Test + public void testGetGroupsForTrafficIngress() { + NsxNetworkRule rule = Mockito.mock(NsxNetworkRule.class); + Mockito.when(rule.getCidrList()).thenReturn(List.of("ANY")); + Mockito.when(rule.getTrafficType()).thenReturn("Ingress"); + String segmentName = "segment"; + List sourceGroups = client.getGroupsForTraffic(rule, segmentName, true); + List destinationGroups = client.getGroupsForTraffic(rule, segmentName, false); + Assert.assertEquals(List.of("ANY"), sourceGroups); + Assert.assertEquals(List.of(String.format("%s/%s", NsxApiClient.GROUPS_PATH_PREFIX, segmentName)), destinationGroups); + } + + @Test + public void testGetGroupsForTrafficEgress() { + NsxNetworkRule rule = Mockito.mock(NsxNetworkRule.class); + Mockito.when(rule.getCidrList()).thenReturn(List.of("ANY")); + Mockito.when(rule.getTrafficType()).thenReturn("Egress"); + String segmentName = "segment"; + List sourceGroups = client.getGroupsForTraffic(rule, segmentName, true); + List destinationGroups = client.getGroupsForTraffic(rule, segmentName, false); + Assert.assertEquals(List.of(String.format("%s/%s", NsxApiClient.GROUPS_PATH_PREFIX, segmentName)), sourceGroups); + Assert.assertEquals(List.of("ANY"), destinationGroups); + } +} diff --git a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java index dc5bea4cf42..e614421954d 100644 --- a/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java +++ b/plugins/network-elements/nsx/src/test/java/org/apache/cloudstack/service/NsxElementTest.java @@ -28,12 +28,15 @@ import com.cloud.network.Networks; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.network.vpc.NetworkACLItem; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.dao.VpcOfferingServiceMapDao; import com.cloud.resource.ResourceManager; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.vm.ReservationContext; +import org.apache.cloudstack.resource.NsxNetworkRule; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -129,6 +132,24 @@ public class NsxElementTest { assertTrue(nsxElement.shutdownVpc(vpc, reservationContext)); } + @Test + public void testTransformActionValue() { + NsxNetworkRule.NsxRuleAction action = nsxElement.transformActionValue(NetworkACLItem.Action.Deny); + Assert.assertEquals(NsxNetworkRule.NsxRuleAction.DROP, action); + } + @Test + public void testTransformCidrListValuesEmptyList() { + List values = nsxElement.transformCidrListValues(null); + Assert.assertNotNull(values); + Assert.assertTrue(values.isEmpty()); + } + + @Test + public void testTransformCidrListValuesList() { + List values = nsxElement.transformCidrListValues(List.of("0.0.0.0/0")); + Assert.assertEquals(1, values.size()); + Assert.assertEquals("ANY", values.get(0)); + } } diff --git a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java index 69574b29ff7..e8049a2271e 100644 --- a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java +++ b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java @@ -1251,6 +1251,11 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio serviceProviderMap.put(Service.StaticNat, Provider.Nsx); serviceProviderMap.put(Service.PortForwarding, Provider.Nsx); serviceProviderMap.put(Service.Lb, Provider.Nsx); + if (forVpc) { + serviceProviderMap.put(Service.NetworkACL, Provider.Nsx); + } else { + serviceProviderMap.put(Service.Firewall, Provider.Nsx); + } } return serviceProviderMap; }