Skip to content

Commit 2b7dc78

Browse files
committed
import network acl rules using csv
1 parent d160731 commit 2b7dc78

File tree

5 files changed

+270
-0
lines changed

5 files changed

+270
-0
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: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
import com.cloud.utils.Pair;
40+
41+
@APICommand(name = "importNetworkACL", description = "Imports network ACL rules.",
42+
responseObject = NetworkACLItemResponse.class,
43+
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
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+
since = "4.22.0"
57+
)
58+
private Long aclId;
59+
60+
@Parameter(name = ApiConstants.RULES, type = CommandType.MAP, required = true,
61+
description = "Rules param list, id and protocol are must. Example: " +
62+
"rules[0].id=101&rules[0].protocol=tcp&rules[0].traffictype=ingress&rules[0].state=active&rules[0].cidrlist=192.168.1.0/24" +
63+
"&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" +
64+
"&rules[0].description=allow%20web%20traffic&rules[1].id=102&rules[1].protocol=udp&rules[1].traffictype=egress&rules[1].state=enabled" +
65+
"&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" +
66+
"&rules[1].fordisplay=false&rules[1].description=deny%20database%20traffic",
67+
since = "4.22.0")
68+
private Map rules;
69+
70+
71+
// ///////////////////////////////////////////////////
72+
// ///////////////// Accessors ///////////////////////
73+
// ///////////////////////////////////////////////////
74+
75+
// Returns map, corresponds to a rule with the details in the keys:
76+
// id, protocol, startport, endport, traffictype, state, cidrlist, tags, aclid, aclname, number, action, fordisplay, description
77+
public Map getRules() {
78+
return rules;
79+
}
80+
81+
public Long getAclId() {
82+
return aclId;
83+
}
84+
85+
// ///////////////////////////////////////////////////
86+
// ///////////// API Implementation///////////////////
87+
// ///////////////////////////////////////////////////
88+
89+
90+
@Override
91+
public void execute() throws ResourceUnavailableException {
92+
validateParams();
93+
List<NetworkACLItem> importedRules = _networkACLService.importNetworkACLRules(this);
94+
ListResponse<NetworkACLItemResponse> response = new ListResponse<>();
95+
List<NetworkACLItemResponse> aclResponse = new ArrayList<>();
96+
for (NetworkACLItem acl : importedRules) {
97+
NetworkACLItemResponse ruleData = _responseGenerator.createNetworkACLItemResponse(acl);
98+
aclResponse.add(ruleData);
99+
}
100+
response.setResponses(aclResponse, importedRules.size());
101+
response.setResponseName(getCommandName());
102+
setResponseObject(response);
103+
}
104+
105+
@Override
106+
public long getEntityOwnerId() {
107+
Account account = CallContext.current().getCallingAccount();
108+
if (account != null) {
109+
return account.getId();
110+
}
111+
return Account.ACCOUNT_ID_SYSTEM;
112+
}
113+
114+
@Override
115+
public String getEventType() {
116+
return EventTypes.EVENT_NETWORK_ACL_CREATE;
117+
}
118+
119+
@Override
120+
public String getEventDescription() {
121+
return "Importing ACL rules for ACL ID: " + getAclId();
122+
}
123+
124+
125+
private void validateParams() {
126+
if(MapUtils.isEmpty(rules)) {
127+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Rules parameter is empty or null");
128+
}
129+
130+
if (getAclId() == null || _networkACLService.getNetworkACL(getAclId()) == null) {
131+
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find network ACL with provided aclid");
132+
}
133+
}
134+
}

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

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
import com.cloud.network.dao.NsxProviderDao;
3333
import com.cloud.network.element.NetrisProviderVO;
3434
import com.cloud.network.element.NsxProviderVO;
35+
36+
import org.apache.cloudstack.api.ApiConstants;
3537
import org.apache.cloudstack.api.ApiErrorCode;
3638
import org.apache.cloudstack.api.ServerApiException;
3739
import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
40+
import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd;
3841
import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd;
3942
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
4043
import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd;
@@ -79,6 +82,7 @@
7982
import com.cloud.utils.db.SearchCriteria.Op;
8083
import com.cloud.utils.exception.CloudRuntimeException;
8184
import com.cloud.utils.net.NetUtils;
85+
import com.google.protobuf.Api;
8286

8387
@Component
8488
public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLService {
@@ -1061,6 +1065,110 @@ public NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved) {
10611065
return moveRuleToTheTop(ruleBeingMoved, allRules);
10621066
}
10631067

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

0 commit comments

Comments
 (0)