Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/event/EventTypes.java
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,7 @@ public class EventTypes {

// Network ACL
public static final String EVENT_NETWORK_ACL_CREATE = "NETWORK.ACL.CREATE";
public static final String EVENT_NETWORK_ACL_IMPORT = "NETWORK.ACL.IMPORT";
public static final String EVENT_NETWORK_ACL_DELETE = "NETWORK.ACL.DELETE";
public static final String EVENT_NETWORK_ACL_REPLACE = "NETWORK.ACL.REPLACE";
public static final String EVENT_NETWORK_ACL_UPDATE = "NETWORK.ACL.UPDATE";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.List;

import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd;
Expand Down Expand Up @@ -98,4 +99,6 @@ public interface NetworkACLService {
NetworkACLItem moveNetworkAclRuleToNewPosition(MoveNetworkAclItemCmd moveNetworkAclItemCmd);

NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved);

List<NetworkACLItem> importNetworkACLRules(ImportNetworkACLCmd cmd) throws ResourceUnavailableException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,30 @@ public String getReason() {
return reason;
}

public void setCidrlist(List<String> cidrlist) {
this.cidrlist = cidrlist;
}

public void setIcmpType(Integer icmpType) {
this.icmpType = icmpType;
}

public void setIcmpCode(Integer icmpCode) {
this.icmpCode = icmpCode;
}

public void setNumber(Integer number) {
this.number = number;
}

public void setDisplay(Boolean display) {
this.display = display;
}

public void setReason(String reason) {
this.reason = reason;
}

@Override
public void create() {
NetworkACLItem result = _networkACLService.createNetworkACLItem(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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.api.command.user.network;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseAsyncCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.NetworkACLItemResponse;
import org.apache.cloudstack.api.response.NetworkACLResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.commons.collections.MapUtils;

import com.cloud.event.EventTypes;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.vpc.NetworkACLItem;
import com.cloud.user.Account;

@APICommand(name = "importNetworkACL", description = "Imports network ACL rules.",
responseObject = NetworkACLItemResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class ImportNetworkACLCmd extends BaseAsyncCmd {

// ///////////////////////////////////////////////////
// ////////////// API parameters /////////////////////
// ///////////////////////////////////////////////////

@Parameter(
name = ApiConstants.ACL_ID,
type = CommandType.UUID,
entityType = NetworkACLResponse.class,
required = true,
description = "The ID of the network ACL to which the rules will be imported",
since = "4.22.0"
)
private Long aclId;

@Parameter(name = ApiConstants.RULES, type = CommandType.MAP, required = true,
description = "Rules param list, id and protocol are must. Example: " +
"rules[0].id=101&rules[0].protocol=tcp&rules[0].traffictype=ingress&rules[0].state=active&rules[0].cidrlist=192.168.1.0/24" +
"&rules[0].tags=web&rules[0].aclid=acl-001&rules[0].aclname=web-acl&rules[0].number=1&rules[0].action=allow&rules[0].fordisplay=true" +
"&rules[0].description=allow%20web%20traffic&rules[1].id=102&rules[1].protocol=udp&rules[1].traffictype=egress&rules[1].state=enabled" +
"&rules[1].cidrlist=10.0.0.0/8&rules[1].tags=db&rules[1].aclid=acl-002&rules[1].aclname=db-acl&rules[1].number=2&rules[1].action=deny" +
"&rules[1].fordisplay=false&rules[1].description=deny%20database%20traffic",
since = "4.22.0")
private Map rules;


// ///////////////////////////////////////////////////
// ///////////////// Accessors ///////////////////////
// ///////////////////////////////////////////////////

// Returns map, corresponds to a rule with the details in the keys:
// id, protocol, startport, endport, traffictype, state, cidrlist, tags, aclid, aclname, number, action, fordisplay, description
public Map getRules() {
return rules;
}

public Long getAclId() {
return aclId;
}

// ///////////////////////////////////////////////////
// ///////////// API Implementation///////////////////
// ///////////////////////////////////////////////////


@Override
public void execute() throws ResourceUnavailableException {
validateParams();
List<NetworkACLItem> importedRules = _networkACLService.importNetworkACLRules(this);
ListResponse<NetworkACLItemResponse> response = new ListResponse<>();
List<NetworkACLItemResponse> aclResponse = new ArrayList<>();
for (NetworkACLItem acl : importedRules) {
NetworkACLItemResponse ruleData = _responseGenerator.createNetworkACLItemResponse(acl);
aclResponse.add(ruleData);
}
response.setResponses(aclResponse, importedRules.size());
response.setResponseName(getCommandName());
setResponseObject(response);
}

@Override
public long getEntityOwnerId() {
Account account = CallContext.current().getCallingAccount();
if (account != null) {
return account.getId();
}
return Account.ACCOUNT_ID_SYSTEM;
}

@Override
public String getEventType() {
return EventTypes.EVENT_NETWORK_ACL_CREATE;
}

@Override
public String getEventDescription() {
return "Importing ACL rules for ACL ID: " + getAclId();
}


private void validateParams() {
if(MapUtils.isEmpty(rules)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Rules parameter is empty or null");
}

if (getAclId() == null || _networkACLService.getNetworkACL(getAclId()) == null) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find network ACL with provided aclid");
}
}
}
118 changes: 112 additions & 6 deletions server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,11 @@

import javax.inject.Inject;

import com.cloud.dc.DataCenter;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.network.dao.NetrisProviderDao;
import com.cloud.network.dao.NsxProviderDao;
import com.cloud.network.element.NetrisProviderVO;
import com.cloud.network.element.NsxProviderVO;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd;
Expand All @@ -47,15 +43,21 @@
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import com.cloud.dc.DataCenter;
import com.cloud.event.ActionEvent;
import com.cloud.event.EventTypes;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.network.Network;
import com.cloud.network.NetworkModel;
import com.cloud.network.Networks;
import com.cloud.network.dao.NetrisProviderDao;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.NsxProviderDao;
import com.cloud.network.element.NetrisProviderVO;
import com.cloud.network.element.NsxProviderVO;
import com.cloud.network.vpc.NetworkACLItem.Action;
import com.cloud.network.vpc.NetworkACLItem.TrafficType;
import com.cloud.network.vpc.dao.NetworkACLDao;
Expand Down Expand Up @@ -1061,6 +1063,110 @@ public NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved) {
return moveRuleToTheTop(ruleBeingMoved, allRules);
}

@Override
public List<NetworkACLItem> importNetworkACLRules(ImportNetworkACLCmd cmd) throws ResourceUnavailableException {
long aclId = cmd.getAclId();
Map<Object, Object> rules = cmd.getRules();
List<NetworkACLItem> createdRules = new ArrayList<>();
List<String> errors = new ArrayList<>();
for (Map.Entry<Object, Object> entry : rules.entrySet()) {
try {
Map<String, Object> ruleMap = (Map<String, Object>) entry.getValue();
NetworkACLItem item = createACLRuleFromMap(ruleMap, aclId);
createdRules.add(item);
} catch (Exception ex) {
String error = "Failed to import rule at index " + entry.getKey() + ": " + ex.getMessage();
errors.add(error);
logger.error(error, ex);
}
}
// no rules got imported
if (createdRules.isEmpty() && !errors.isEmpty()) {
logger.error("Failed to import any ACL rules. Errors: {}", String.join("; ", errors));
throw new CloudRuntimeException("Failed to import any ACL rules.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is very confusing to a user. the reason is probably because a rule already existed and it is counterintuitive to not allow it. We might add a flag allowing overwriting it.

}

// apply ACL to network
if (!createdRules.isEmpty()) {
applyNetworkACL(aclId);
}
return createdRules;
}

private NetworkACLItem createACLRuleFromMap(Map<String, Object> ruleMap, long aclId) {
String protocol = (String) ruleMap.get(ApiConstants.PROTOCOL);
if (protocol == null || protocol.trim().isEmpty()) {
throw new InvalidParameterValueException("Protocol is required");
}
String action = (String) ruleMap.getOrDefault(ApiConstants.ACTION, "deny");
String trafficType = (String) ruleMap.getOrDefault(ApiConstants.TRAFFIC_TYPE, NetworkACLItem.TrafficType.Ingress);

// Create ACL rule using the service
CreateNetworkACLCmd cmd = new CreateNetworkACLCmd();
cmd.setAclId(aclId);
cmd.setProtocol(protocol.toLowerCase());
cmd.setAction(action.toLowerCase());
cmd.setTrafficType(trafficType.toLowerCase());


// Optional parameters
if (ruleMap.containsKey(ApiConstants.CIDR_LIST)) {
Object cidrObj = ruleMap.get(ApiConstants.CIDR_LIST);
List<String> cidrList = new ArrayList<>();
if (cidrObj instanceof String) {
for (String cidr : ((String) cidrObj).split(",")) {
cidrList.add(cidr.trim());
}
} else if (cidrObj instanceof List) {
cidrList.addAll((List<String>) cidrObj);
}
cmd.setCidrlist(cidrList);
}

if (ruleMap.containsKey(ApiConstants.START_PORT)) {
cmd.setPublicStartPort(parseInt(ruleMap.get(ApiConstants.START_PORT)));
}

if (ruleMap.containsKey(ApiConstants.END_PORT)) {
cmd.setPublicEndPort(parseInt(ruleMap.get(ApiConstants.END_PORT)));
}

if (ruleMap.containsKey(ApiConstants.NUMBER)) {
cmd.setNumber(parseInt(ruleMap.get(ApiConstants.NUMBER)));
}

if (ruleMap.containsKey(ApiConstants.ICMP_TYPE)) {
cmd.setIcmpType(parseInt(ruleMap.get(ApiConstants.ICMP_TYPE)));
}

if (ruleMap.containsKey(ApiConstants.ICMP_CODE)) {
cmd.setIcmpCode(parseInt(ruleMap.get(ApiConstants.ICMP_CODE)));
}

if (ruleMap.containsKey(ApiConstants.ACL_REASON)) {
cmd.setReason((String) ruleMap.get(ApiConstants.ACL_REASON));
}

return createNetworkACLItem(cmd);
}

private Integer parseInt(Object value) {
if (value == null) {
return null;
}
if (value instanceof Integer) {
return (Integer) value;
}
if (value instanceof String) {
try {
return Integer.parseInt((String) value);
} catch (NumberFormatException e) {
throw new InvalidParameterValueException("Invalid integer value: " + value);
}
}
throw new InvalidParameterValueException("Cannot convert to integer: " + value);
}

/**
* Validates the consistency of the ACL; the validation process is the following.
* <ul>
Expand Down
9 changes: 7 additions & 2 deletions ui/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
"label.accounts": "Accounts",
"label.accountstate": "Account state",
"label.accounttype": "Account type",
"label.acl.export": "Export ACL rules",
"label.import": "Import",
"label.acl.import": "Import rules",
"label.acl.export": "Export rules",
"label.acl.id": "ACL ID",
"label.acl.rules": "ACL rules",
"label.acl.reason.description": "Enter the reason behind an ACL rule.",
Expand Down Expand Up @@ -251,7 +253,7 @@
"label.activeviewersessions": "Active sessions",
"label.add": "Add",
"label.add.account": "Add Account",
"label.add.acl.rule": "Add ACL rule",
"label.add.acl.rule": "Add rule",
"label.add.acl": "Add ACL",
"label.add.affinity.group": "Add new Affinity Group",
"label.add.backup.schedule": "Add Backup Schedule",
Expand Down Expand Up @@ -699,6 +701,7 @@
"label.cron.mode": "Cron mode",
"label.crosszones": "Cross Zones",
"label.csienabled": "CSI Enabled",
"label.csv.preview": "Data preview",
"label.currency": "Currency",
"label.current": "Current",
"label.currentstep": "Current step",
Expand Down Expand Up @@ -2895,6 +2898,8 @@
"message.action.create.snapshot.from.vmsnapshot": "Please confirm that you want to create Snapshot from Instance Snapshot",
"message.action.create.instance.from.backup": "Please confirm that you want to create a new Instance from the given Backup.<br>Click on configure to edit the parameters for the new Instance before creation.",
"message.create.instance.from.backup.different.zone": "Creating Instance from Backup on a different Zone. Please ensure that the backup repository is accessible in the selected Zone.",
"message.csv.empty": "Empty CSV File",
"message.csv.missing.headers": "Columns are missing from headers in CSV",
"message.template.ostype.different.from.backup": "Selected Template has a different OS type than the Backup. Please proceed with caution.",
"message.iso.ostype.different.from.backup": "Selected ISO has a different OS type than the Backup. Please proceed with caution.",
"message.action.delete.asnrange": "Please confirm the AS range that you want to delete",
Expand Down
Loading
Loading