[NSX] Add ACL types support (#8224)

* NSX: Create segment group on segment creation

* Add unit tests

* Remove group for segment before removing segment

* Create Distributed Firewall rules

* Remove distributed firewall policy on segment deletion

* Fix policy rule ID and add more unit tests

* Fix DROP action rules and transform tests

* Add new ACL rules

* Fixes

* associate security policies with groups and not to DFW and add deletion of rules

* Fix name convention

---------

Co-authored-by: Pearl Dsilva <pearl1594@gmail.com>
This commit is contained in:
Nicolas Vazquez 2023-11-30 20:32:48 -03:00 committed by GitHub
parent 053521077c
commit 8a87eaaec9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 647 additions and 26 deletions

View File

@ -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;
}

View File

@ -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<NsxNetworkRule> rules;
public CreateNsxDistributedFirewallRulesCommand(long domainId, long accountId, long zoneId,
Long vpcId, long networkId,
List<NsxNetworkRule> 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<NsxNetworkRule> getRules() {
return rules;
}
}

View File

@ -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<NsxNetworkRule> rules) {
super(domainId, accountId, zoneId, vpcId, networkId, rules);
}
}

View File

@ -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<NsxLoadBalancerMember> memberList;
private NsxRuleAction aclAction;
private List<String> 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<String> getCidrList() {
return cidrList;
}
public void setCidrList(List<String> 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<NsxLoadBalancerMember> memberList;
private NsxRuleAction aclAction;
private List<String> 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<String> 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;
}
}

View File

@ -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<NsxNetworkRule> 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<NsxNetworkRule> 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;

View File

@ -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<Class<? extends Service>, Service> nsxService;
protected Function<Class<? extends Service>, 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<Structure> 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<String> 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<NsxNetworkRule> 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<Rule> 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<NsxNetworkRule> 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<Rule> getRulesForDistributedFirewall(String segmentName, List<NsxNetworkRule> nsxRules) {
List<Rule> 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<String> getServicesListForDistributedFirewallRule(NsxNetworkRule rule, String segmentName) {
List<String> 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<String> getGroupsForTraffic(NsxNetworkRule rule,
String segmentName, boolean source) {
List<String> segmentGroup = List.of(String.format("%s/%s", GROUPS_PATH_PREFIX, segmentName));
List<String> 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<Group> 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<Group> groups = listNsxGroups();
Optional<Group> matchingGroup = groups.stream().filter(group -> group.getDisplayName().equals(segmentName)).findFirst();
return matchingGroup.map(Group::getPath).orElse(null);
}
}

View File

@ -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<Network.Capability, String> 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<Network.Capability, String> 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<LoadBalancingRule> 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<? extends NetworkACLItem> rules) throws ResourceUnavailableException {
if (!canHandle(network, Network.Service.NetworkACL)) {
return false;
}
List<NsxNetworkRule> nsxAddNetworkRules = new ArrayList<>();
List<NsxNetworkRule> 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<String> transformCidrListValues(List<String> sourceCidrList) {
List<String> 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;
}
}

View File

@ -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<NsxNetworkRule> 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<NsxNetworkRule> 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();
}
}

View File

@ -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) {

View File

@ -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<Class<? extends Service>, 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<Group> ignored = Mockito.mockConstruction(Group.class, (mock, context) -> {
groups[0] = mock;
}); MockedConstruction<PathExpression> 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<String> sourceGroups = client.getGroupsForTraffic(rule, segmentName, true);
List<String> 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<String> sourceGroups = client.getGroupsForTraffic(rule, segmentName, true);
List<String> 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);
}
}

View File

@ -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<String> values = nsxElement.transformCidrListValues(null);
Assert.assertNotNull(values);
Assert.assertTrue(values.isEmpty());
}
@Test
public void testTransformCidrListValuesList() {
List<String> values = nsxElement.transformCidrListValues(List.of("0.0.0.0/0"));
Assert.assertEquals(1, values.size());
Assert.assertEquals("ANY", values.get(0));
}
}

View File

@ -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;
}