Skip to content

Commit a8f1e4a

Browse files
authored
import network acl rules using csv (#12013)
1 parent bd459a4 commit a8f1e4a

File tree

9 files changed

+699
-12
lines changed

9 files changed

+699
-12
lines changed

api/src/main/java/com/cloud/event/EventTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,7 @@ public class EventTypes {
582582

583583
// Network ACL
584584
public static final String EVENT_NETWORK_ACL_CREATE = "NETWORK.ACL.CREATE";
585+
public static final String EVENT_NETWORK_ACL_IMPORT = "NETWORK.ACL.IMPORT";
585586
public static final String EVENT_NETWORK_ACL_DELETE = "NETWORK.ACL.DELETE";
586587
public static final String EVENT_NETWORK_ACL_REPLACE = "NETWORK.ACL.REPLACE";
587588
public static final String EVENT_NETWORK_ACL_UPDATE = "NETWORK.ACL.UPDATE";

api/src/main/java/com/cloud/network/vpc/NetworkACLService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020

2121
import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
22+
import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd;
2223
import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
2324
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
2425
import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd;
@@ -98,4 +99,6 @@ public interface NetworkACLService {
9899
NetworkACLItem moveNetworkAclRuleToNewPosition(MoveNetworkAclItemCmd moveNetworkAclItemCmd);
99100

100101
NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved);
102+
103+
List<NetworkACLItem> importNetworkACLRules(ImportNetworkACLCmd cmd) throws ResourceUnavailableException;
101104
}

api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class CreateNetworkACLCmd extends BaseAsyncCreateCmd {
5858
private Integer publicEndPort;
5959

6060
@Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "The CIDR list to allow traffic from/to. Multiple entries must be separated by a single comma character (,).")
61-
private List<String> cidrlist;
61+
private List<String> cidrList;
6262

6363
@Parameter(name = ApiConstants.ICMP_TYPE, type = CommandType.INTEGER, description = "Type of the ICMP message being sent")
6464
private Integer icmpType;
@@ -118,8 +118,8 @@ public void setProtocol(String protocol) {
118118
}
119119

120120
public List<String> getSourceCidrList() {
121-
if (cidrlist != null) {
122-
return cidrlist;
121+
if (cidrList != null) {
122+
return cidrList;
123123
} else {
124124
List<String> oneCidrList = new ArrayList<String>();
125125
oneCidrList.add(NetUtils.ALL_IP4_CIDRS);
@@ -238,6 +238,30 @@ public String getReason() {
238238
return reason;
239239
}
240240

241+
public void setCidrList(List<String> cidrList) {
242+
this.cidrList = cidrList;
243+
}
244+
245+
public void setIcmpType(Integer icmpType) {
246+
this.icmpType = icmpType;
247+
}
248+
249+
public void setIcmpCode(Integer icmpCode) {
250+
this.icmpCode = icmpCode;
251+
}
252+
253+
public void setNumber(Integer number) {
254+
this.number = number;
255+
}
256+
257+
public void setDisplay(Boolean display) {
258+
this.display = display;
259+
}
260+
261+
public void setReason(String reason) {
262+
this.reason = reason;
263+
}
264+
241265
@Override
242266
public void create() {
243267
NetworkACLItem result = _networkACLService.createNetworkACLItem(this);
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.api.command.user.network;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
import org.apache.cloudstack.api.APICommand;
24+
import org.apache.cloudstack.api.ApiConstants;
25+
import org.apache.cloudstack.api.ApiErrorCode;
26+
import org.apache.cloudstack.api.BaseAsyncCmd;
27+
import org.apache.cloudstack.api.Parameter;
28+
import org.apache.cloudstack.api.ServerApiException;
29+
import org.apache.cloudstack.api.response.ListResponse;
30+
import org.apache.cloudstack.api.response.NetworkACLItemResponse;
31+
import org.apache.cloudstack.api.response.NetworkACLResponse;
32+
import org.apache.cloudstack.context.CallContext;
33+
import org.apache.commons.collections.MapUtils;
34+
35+
import com.cloud.event.EventTypes;
36+
import com.cloud.exception.ResourceUnavailableException;
37+
import com.cloud.network.vpc.NetworkACLItem;
38+
import com.cloud.user.Account;
39+
40+
@APICommand(name = "importNetworkACL", description = "Imports Network ACL rules.",
41+
responseObject = NetworkACLItemResponse.class,
42+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false,
43+
since = "4.22.1")
44+
public class ImportNetworkACLCmd extends BaseAsyncCmd {
45+
46+
// ///////////////////////////////////////////////////
47+
// ////////////// API parameters /////////////////////
48+
// ///////////////////////////////////////////////////
49+
50+
@Parameter(
51+
name = ApiConstants.ACL_ID,
52+
type = CommandType.UUID,
53+
entityType = NetworkACLResponse.class,
54+
required = true,
55+
description = "The ID of the Network ACL to which the rules will be imported"
56+
)
57+
private Long aclId;
58+
59+
@Parameter(name = ApiConstants.RULES, type = CommandType.MAP, required = true,
60+
description = "Rules param list, id and protocol are must. Invalid rules will be discarded. Example: " +
61+
"rules[0].id=101&rules[0].protocol=tcp&rules[0].traffictype=ingress&rules[0].state=active&rules[0].cidrlist=192.168.1.0/24" +
62+
"&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" +
63+
"&rules[0].description=allow%20web%20traffic&rules[1].id=102&rules[1].protocol=udp&rules[1].traffictype=egress&rules[1].state=enabled" +
64+
"&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" +
65+
"&rules[1].fordisplay=false&rules[1].description=deny%20database%20traffic")
66+
private Map rules;
67+
68+
69+
// ///////////////////////////////////////////////////
70+
// ///////////////// Accessors ///////////////////////
71+
// ///////////////////////////////////////////////////
72+
73+
// Returns map, corresponds to a rule with the details in the keys:
74+
// id, protocol, startport, endport, traffictype, state, cidrlist, tags, aclid, aclname, number, action, fordisplay, description
75+
public Map getRules() {
76+
return rules;
77+
}
78+
79+
public Long getAclId() {
80+
return aclId;
81+
}
82+
83+
// ///////////////////////////////////////////////////
84+
// ///////////// API Implementation///////////////////
85+
// ///////////////////////////////////////////////////
86+
87+
88+
@Override
89+
public void execute() throws ResourceUnavailableException {
90+
validateParams();
91+
List<NetworkACLItem> importedRules = _networkACLService.importNetworkACLRules(this);
92+
ListResponse<NetworkACLItemResponse> response = new ListResponse<>();
93+
List<NetworkACLItemResponse> aclResponse = new ArrayList<>();
94+
for (NetworkACLItem acl : importedRules) {
95+
NetworkACLItemResponse ruleData = _responseGenerator.createNetworkACLItemResponse(acl);
96+
aclResponse.add(ruleData);
97+
}
98+
response.setResponses(aclResponse, importedRules.size());
99+
response.setResponseName(getCommandName());
100+
setResponseObject(response);
101+
}
102+
103+
@Override
104+
public long getEntityOwnerId() {
105+
Account account = CallContext.current().getCallingAccount();
106+
if (account != null) {
107+
return account.getId();
108+
}
109+
return Account.ACCOUNT_ID_SYSTEM;
110+
}
111+
112+
@Override
113+
public String getEventType() {
114+
return EventTypes.EVENT_NETWORK_ACL_IMPORT;
115+
}
116+
117+
@Override
118+
public String getEventDescription() {
119+
return "Importing ACL rules for ACL ID: " + getAclId();
120+
}
121+
122+
123+
private void validateParams() {
124+
if(MapUtils.isEmpty(rules)) {
125+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Rules parameter is empty or null");
126+
}
127+
128+
if (getAclId() == null || _networkACLService.getNetworkACL(getAclId()) == null) {
129+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find Network ACL with provided ACL ID");
130+
}
131+
}
132+
}

server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,11 @@
2626

2727
import javax.inject.Inject;
2828

29-
import com.cloud.dc.DataCenter;
30-
import com.cloud.exception.PermissionDeniedException;
31-
import com.cloud.network.dao.NetrisProviderDao;
32-
import com.cloud.network.dao.NsxProviderDao;
33-
import com.cloud.network.element.NetrisProviderVO;
34-
import com.cloud.network.element.NsxProviderVO;
29+
import org.apache.cloudstack.api.ApiConstants;
3530
import org.apache.cloudstack.api.ApiErrorCode;
3631
import org.apache.cloudstack.api.ServerApiException;
3732
import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
33+
import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd;
3834
import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
3935
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
4036
import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd;
@@ -43,19 +39,26 @@
4339
import org.apache.cloudstack.context.CallContext;
4440
import org.apache.commons.codec.digest.DigestUtils;
4541
import org.apache.commons.collections.CollectionUtils;
42+
import org.apache.commons.lang3.BooleanUtils;
4643
import org.apache.commons.lang3.ObjectUtils;
4744
import org.apache.commons.lang3.StringUtils;
4845
import org.springframework.stereotype.Component;
4946

47+
import com.cloud.dc.DataCenter;
5048
import com.cloud.event.ActionEvent;
5149
import com.cloud.event.EventTypes;
5250
import com.cloud.exception.InvalidParameterValueException;
51+
import com.cloud.exception.PermissionDeniedException;
5352
import com.cloud.exception.ResourceUnavailableException;
5453
import com.cloud.network.Network;
5554
import com.cloud.network.NetworkModel;
5655
import com.cloud.network.Networks;
56+
import com.cloud.network.dao.NetrisProviderDao;
5757
import com.cloud.network.dao.NetworkDao;
5858
import com.cloud.network.dao.NetworkVO;
59+
import com.cloud.network.dao.NsxProviderDao;
60+
import com.cloud.network.element.NetrisProviderVO;
61+
import com.cloud.network.element.NsxProviderVO;
5962
import com.cloud.network.vpc.NetworkACLItem.Action;
6063
import com.cloud.network.vpc.NetworkACLItem.TrafficType;
6164
import com.cloud.network.vpc.dao.NetworkACLDao;
@@ -1070,6 +1073,111 @@ public NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved) {
10701073
return moveRuleToTheTop(ruleBeingMoved, allRules);
10711074
}
10721075

1076+
@Override
1077+
public List<NetworkACLItem> importNetworkACLRules(ImportNetworkACLCmd cmd) throws ResourceUnavailableException {
1078+
long aclId = cmd.getAclId();
1079+
Map<Object, Object> rules = cmd.getRules();
1080+
List<NetworkACLItem> createdRules = new ArrayList<>();
1081+
List<String> errors = new ArrayList<>();
1082+
for (Map.Entry<Object, Object> entry : rules.entrySet()) {
1083+
try {
1084+
Map<String, Object> ruleMap = (Map<String, Object>) entry.getValue();
1085+
NetworkACLItem item = createACLRuleFromMap(ruleMap, aclId);
1086+
createdRules.add(item);
1087+
} catch (Exception ex) {
1088+
String error = "Failed to import rule at index " + entry.getKey() + ": " + ex.getMessage();
1089+
errors.add(error);
1090+
logger.error(error, ex);
1091+
}
1092+
}
1093+
// no rules got imported
1094+
if (createdRules.isEmpty() && !errors.isEmpty()) {
1095+
logger.error("Failed to import any ACL rules. Errors: {}", String.join("; ", errors));
1096+
throw new CloudRuntimeException("Failed to import any ACL rules.");
1097+
}
1098+
1099+
// apply ACL to network
1100+
if (!createdRules.isEmpty()) {
1101+
applyNetworkACL(aclId);
1102+
}
1103+
return createdRules;
1104+
}
1105+
1106+
private NetworkACLItem createACLRuleFromMap(Map<String, Object> ruleMap, long aclId) {
1107+
String protocol = (String) ruleMap.get(ApiConstants.PROTOCOL);
1108+
if (protocol == null || protocol.trim().isEmpty()) {
1109+
throw new InvalidParameterValueException("Protocol is required");
1110+
}
1111+
String action = (String) ruleMap.getOrDefault(ApiConstants.ACTION, "deny");
1112+
String trafficType = (String) ruleMap.getOrDefault(ApiConstants.TRAFFIC_TYPE, NetworkACLItem.TrafficType.Ingress);
1113+
String forDisplay = (String) ruleMap.getOrDefault(ApiConstants.FOR_DISPLAY, "true");
1114+
1115+
// Create ACL rule using the service
1116+
CreateNetworkACLCmd cmd = new CreateNetworkACLCmd();
1117+
cmd.setAclId(aclId);
1118+
cmd.setProtocol(protocol.toLowerCase());
1119+
cmd.setAction(action.toLowerCase());
1120+
cmd.setTrafficType(trafficType.toLowerCase());
1121+
cmd.setDisplay(BooleanUtils.toBoolean(forDisplay));
1122+
1123+
// Optional parameters
1124+
if (ruleMap.containsKey(ApiConstants.CIDR_LIST)) {
1125+
Object cidrObj = ruleMap.get(ApiConstants.CIDR_LIST);
1126+
List<String> cidrList = new ArrayList<>();
1127+
if (cidrObj instanceof String) {
1128+
for (String cidr : ((String) cidrObj).split(",")) {
1129+
cidrList.add(cidr.trim());
1130+
}
1131+
} else if (cidrObj instanceof List) {
1132+
cidrList.addAll((List<String>) cidrObj);
1133+
}
1134+
cmd.setCidrList(cidrList);
1135+
}
1136+
1137+
if (ruleMap.containsKey(ApiConstants.START_PORT)) {
1138+
cmd.setPublicStartPort(parseInt(ruleMap.get(ApiConstants.START_PORT)));
1139+
}
1140+
1141+
if (ruleMap.containsKey(ApiConstants.END_PORT)) {
1142+
cmd.setPublicEndPort(parseInt(ruleMap.get(ApiConstants.END_PORT)));
1143+
}
1144+
1145+
if (ruleMap.containsKey(ApiConstants.NUMBER)) {
1146+
cmd.setNumber(parseInt(ruleMap.get(ApiConstants.NUMBER)));
1147+
}
1148+
1149+
if (ruleMap.containsKey(ApiConstants.ICMP_TYPE)) {
1150+
cmd.setIcmpType(parseInt(ruleMap.get(ApiConstants.ICMP_TYPE)));
1151+
}
1152+
1153+
if (ruleMap.containsKey(ApiConstants.ICMP_CODE)) {
1154+
cmd.setIcmpCode(parseInt(ruleMap.get(ApiConstants.ICMP_CODE)));
1155+
}
1156+
1157+
if (ruleMap.containsKey(ApiConstants.ACL_REASON)) {
1158+
cmd.setReason((String) ruleMap.get(ApiConstants.ACL_REASON));
1159+
}
1160+
1161+
return createNetworkACLItem(cmd);
1162+
}
1163+
1164+
private Integer parseInt(Object value) {
1165+
if (value == null) {
1166+
return null;
1167+
}
1168+
if (value instanceof Integer) {
1169+
return (Integer) value;
1170+
}
1171+
if (value instanceof String) {
1172+
try {
1173+
return Integer.parseInt((String) value);
1174+
} catch (NumberFormatException e) {
1175+
throw new InvalidParameterValueException("Invalid integer value: " + value);
1176+
}
1177+
}
1178+
throw new InvalidParameterValueException("Cannot convert to integer: " + value);
1179+
}
1180+
10731181
/**
10741182
* Validates the consistency of the ACL; the validation process is the following.
10751183
* <ul>

server/src/main/java/com/cloud/server/ManagementServerImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@
454454
import org.apache.cloudstack.api.command.user.network.DeleteNetworkACLCmd;
455455
import org.apache.cloudstack.api.command.user.network.DeleteNetworkACLListCmd;
456456
import org.apache.cloudstack.api.command.user.network.DeleteNetworkCmd;
457+
import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd;
457458
import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
458459
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
459460
import org.apache.cloudstack.api.command.user.network.ListNetworkOfferingsCmd;
@@ -4039,6 +4040,7 @@ public List<Class<?>> getCommands() {
40394040
cmdList.add(EnableStaticNatCmd.class);
40404041
cmdList.add(ListIpForwardingRulesCmd.class);
40414042
cmdList.add(CreateNetworkACLCmd.class);
4043+
cmdList.add(ImportNetworkACLCmd.class);
40424044
cmdList.add(CreateNetworkCmd.class);
40434045
cmdList.add(DeleteNetworkACLCmd.class);
40444046
cmdList.add(DeleteNetworkCmd.class);

0 commit comments

Comments
 (0)