Skip to content

Commit cb62ce6

Browse files
authored
Global ACL for VPCs (#7150)
1 parent 26b01f6 commit cb62ce6

8 files changed

Lines changed: 489 additions & 109 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ jobs:
111111
smoke/test_reset_configuration_settings
112112
smoke/test_reset_vm_on_reboot
113113
smoke/test_resource_accounting
114-
smoke/test_resource_detail",
114+
smoke/test_resource_detail
115+
smoke/test_global_acls",
115116
"smoke/test_router_dhcphosts
116117
smoke/test_router_dns
117118
smoke/test_router_dnsservice

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
// under the License.
1717
package org.apache.cloudstack.api.command.user.network;
1818

19+
import com.cloud.exception.PermissionDeniedException;
1920
import org.apache.cloudstack.acl.RoleType;
2021
import org.apache.cloudstack.api.APICommand;
2122
import org.apache.cloudstack.api.ApiCommandResourceType;
@@ -26,6 +27,7 @@
2627
import org.apache.cloudstack.api.ServerApiException;
2728
import org.apache.cloudstack.api.response.NetworkACLResponse;
2829
import org.apache.cloudstack.api.response.VpcResponse;
30+
import org.apache.cloudstack.context.CallContext;
2931
import org.apache.log4j.Logger;
3032

3133
import com.cloud.event.EventTypes;
@@ -35,7 +37,8 @@
3537
import com.cloud.network.vpc.Vpc;
3638
import com.cloud.user.Account;
3739

38-
@APICommand(name = "createNetworkACLList", description = "Creates a network ACL for the given VPC", responseObject = NetworkACLResponse.class,
40+
@APICommand(name = "createNetworkACLList", description = "Creates a network ACL. If no VPC is given, then it creates a global ACL that can be used by everyone.",
41+
responseObject = NetworkACLResponse.class,
3942
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
4043
public class CreateNetworkACLListCmd extends BaseAsyncCreateCmd {
4144
public static final Logger s_logger = Logger.getLogger(CreateNetworkACLListCmd.class.getName());
@@ -53,7 +56,6 @@ public class CreateNetworkACLListCmd extends BaseAsyncCreateCmd {
5356

5457
@Parameter(name = ApiConstants.VPC_ID,
5558
type = CommandType.UUID,
56-
required = true,
5759
entityType = VpcResponse.class,
5860
description = "ID of the VPC associated with this network ACL list")
5961
private Long vpcId;
@@ -77,6 +79,10 @@ public Long getVpcId() {
7779
return vpcId;
7880
}
7981

82+
public void setVpcId(Long vpcId) {
83+
this.vpcId = vpcId;
84+
}
85+
8086
@Override
8187
public boolean isDisplay() {
8288
if (display != null) {
@@ -92,6 +98,9 @@ public boolean isDisplay() {
9298

9399
@Override
94100
public void create() {
101+
if (getVpcId() == null) {
102+
setVpcId(0L);
103+
}
95104
NetworkACL result = _networkACLService.createNetworkACL(getName(), getDescription(), getVpcId(), isDisplay());
96105
setEntityId(result.getId());
97106
setEntityUuid(result.getUuid());
@@ -111,12 +120,21 @@ public void execute() throws ResourceUnavailableException {
111120

112121
@Override
113122
public long getEntityOwnerId() {
114-
Vpc vpc = _entityMgr.findById(Vpc.class, getVpcId());
115-
if (vpc == null) {
116-
throw new InvalidParameterValueException("Invalid vpcId is given");
117-
}
123+
Account account;
124+
if (isAclAttachedToVpc(this.vpcId)) {
125+
Vpc vpc = _entityMgr.findById(Vpc.class, this.vpcId);
126+
if (vpc == null) {
127+
throw new InvalidParameterValueException(String.format("Invalid VPC ID [%s] provided.", this.vpcId));
128+
}
129+
account = _accountService.getAccount(vpc.getAccountId());
130+
} else {
131+
account = CallContext.current().getCallingAccount();
132+
if (!Account.Type.ADMIN.equals(account.getType())) {
133+
s_logger.warn(String.format("Only Root Admin can create global ACLs. Account [%s] cannot create any global ACL.", account));
134+
throw new PermissionDeniedException("Only Root Admin can create global ACLs.");
135+
}
118136

119-
Account account = _accountService.getAccount(vpc.getAccountId());
137+
}
120138
return account.getId();
121139
}
122140

@@ -139,4 +157,8 @@ public Long getApiResourceId() {
139157
public ApiCommandResourceType getApiResourceType() {
140158
return ApiCommandResourceType.NetworkAcl;
141159
}
160+
161+
public boolean isAclAttachedToVpc(Long aclVpcId) {
162+
return aclVpcId != null && aclVpcId != 0;
163+
}
142164
}

engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
package com.cloud.network.vpc;
1919

20+
import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;
21+
2022
import java.util.UUID;
2123

2224
import javax.persistence.Column;
@@ -85,6 +87,11 @@ public String getName() {
8587
return name;
8688
}
8789

90+
@Override
91+
public String toString() {
92+
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "vpcId");
93+
}
94+
8895
public void setUuid(String uuid) {
8996
this.uuid = uuid;
9097
}

server/src/main/java/com/cloud/network/NetworkServiceImpl.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2100,12 +2100,9 @@ public Network doInTransaction(TransactionStatus status) throws InsufficientCapa
21002100
throw new InvalidParameterValueException("Unable to find specified NetworkACL");
21012101
}
21022102

2103-
if (aclId != NetworkACL.DEFAULT_DENY && aclId != NetworkACL.DEFAULT_ALLOW) {
2104-
// ACL is not default DENY/ALLOW
2105-
// ACL should be associated with a VPC
2106-
if (!vpcId.equals(acl.getVpcId())) {
2107-
throw new InvalidParameterValueException("ACL: " + aclId + " do not belong to the VPC");
2108-
}
2103+
Long aclVpcId = acl.getVpcId();
2104+
if (!isDefaultAcl(aclId) && isAclAttachedToVpc(aclVpcId, vpcId)) {
2105+
throw new InvalidParameterValueException(String.format("ACL [%s] does not belong to the VPC [%s].", aclId, aclVpcId));
21092106
}
21102107
}
21112108
network = _vpcMgr.createVpcGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType,
@@ -5950,6 +5947,14 @@ public ConfigKey<?>[] getConfigKeys() {
59505947
return new ConfigKey<?>[] {AllowDuplicateNetworkName, AllowEmptyStartEndIpAddress, VRPrivateInterfaceMtu, VRPublicInterfaceMtu, AllowUsersToSpecifyVRMtu};
59515948
}
59525949

5950+
public boolean isDefaultAcl(Long aclId) {
5951+
return aclId == NetworkACL.DEFAULT_DENY || aclId == NetworkACL.DEFAULT_ALLOW;
5952+
}
5953+
5954+
public boolean isAclAttachedToVpc(Long aclVpcId, Long vpcId) {
5955+
return aclVpcId != 0 && !vpcId.equals(aclVpcId);
5956+
}
5957+
59535958
@Override
59545959
public PublicIpQuarantine updatePublicIpAddressInQuarantine(UpdateQuarantinedIpCmd cmd) throws CloudRuntimeException {
59555960
Long ipId = cmd.getId();

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

Lines changed: 85 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import javax.inject.Inject;
2626

27+
import com.cloud.exception.PermissionDeniedException;
2728
import org.apache.cloudstack.api.ApiErrorCode;
2829
import org.apache.cloudstack.api.ServerApiException;
2930
import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
@@ -103,12 +104,15 @@ public class NetworkACLServiceImpl extends ManagerBase implements NetworkACLServ
103104

104105
@Override
105106
public NetworkACL createNetworkACL(final String name, final String description, final long vpcId, final Boolean forDisplay) {
106-
final Account caller = CallContext.current().getCallingAccount();
107-
final Vpc vpc = _entityMgr.findById(Vpc.class, vpcId);
108-
if (vpc == null) {
109-
throw new InvalidParameterValueException("Unable to find VPC");
107+
if (vpcId != 0) {
108+
final Account caller = CallContext.current().getCallingAccount();
109+
final Vpc vpc = _vpcDao.findById(vpcId);
110+
if (vpc == null) {
111+
throw new InvalidParameterValueException(String.format("Unable to find VPC with ID [%s].", vpcId));
112+
}
113+
_accountMgr.checkAccess(caller, null, true, vpc);
114+
110115
}
111-
_accountMgr.checkAccess(caller, null, true, vpc);
112116
return _networkAclMgr.createNetworkACL(name, description, vpcId, forDisplay);
113117
}
114118

@@ -212,22 +216,17 @@ public Pair<List<? extends NetworkACL>, Integer> listNetworkACLs(final ListNetwo
212216
@Override
213217
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_DELETE, eventDescription = "Deleting Network ACL List", async = true)
214218
public boolean deleteNetworkACL(final long id) {
215-
final Account caller = CallContext.current().getCallingAccount();
216219
final NetworkACL acl = _networkACLDao.findById(id);
220+
Account account = CallContext.current().getCallingAccount();
217221
if (acl == null) {
218222
throw new InvalidParameterValueException("Unable to find specified ACL");
219223
}
220224

221-
//Do not allow deletion of default ACLs
222-
if (acl.getId() == NetworkACL.DEFAULT_ALLOW || acl.getId() == NetworkACL.DEFAULT_DENY) {
225+
if (isDefaultAcl(acl.getId())) {
223226
throw new InvalidParameterValueException("Default ACL cannot be removed");
224227
}
225228

226-
final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
227-
if (vpc == null) {
228-
throw new InvalidParameterValueException("Unable to find specified VPC associated with the ACL");
229-
}
230-
_accountMgr.checkAccess(caller, null, true, vpc);
229+
validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACLs.");
231230
return _networkAclMgr.deleteNetworkACL(acl);
232231
}
233232

@@ -253,7 +252,7 @@ public boolean replaceNetworkACLonPrivateGw(final long aclId, final long private
253252
throw new InvalidParameterValueException("Unable to find specified vpc id");
254253
}
255254

256-
if (aclId != NetworkACL.DEFAULT_DENY && aclId != NetworkACL.DEFAULT_ALLOW) {
255+
if (!isDefaultAcl(aclId)) {
257256
final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
258257
if (vpc == null) {
259258
throw new InvalidParameterValueException("Unable to find Vpc associated with the NetworkACL");
@@ -293,15 +292,9 @@ public boolean replaceNetworkACL(final long aclId, final long networkId) throws
293292
throw new InvalidParameterValueException("Network ACL can be created just for networks of type " + Networks.TrafficType.Guest);
294293
}
295294

296-
if (aclId != NetworkACL.DEFAULT_DENY && aclId != NetworkACL.DEFAULT_ALLOW) {
297-
//ACL is not default DENY/ALLOW
298-
// ACL should be associated with a VPC
299-
final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
300-
if (vpc == null) {
301-
throw new InvalidParameterValueException("Unable to find Vpc associated with the NetworkACL");
302-
}
295+
if (!isDefaultAcl(aclId) && !isGlobalAcl(acl.getVpcId())) {
296+
validateAclAssociatedToVpc(acl.getVpcId(), caller, acl.getUuid());
303297

304-
_accountMgr.checkAccess(caller, null, true, vpc);
305298
if (!network.getVpcId().equals(acl.getVpcId())) {
306299
throw new InvalidParameterValueException("Network: " + networkId + " and ACL: " + aclId + " do not belong to the same VPC");
307300
}
@@ -340,6 +333,11 @@ public NetworkACLItem createNetworkACLItem(CreateNetworkACLCmd createNetworkACLC
340333
NetworkACL acl = _networkAclMgr.getNetworkACL(aclId);
341334

342335
validateNetworkAcl(acl);
336+
Account caller = CallContext.current().getCallingAccount();
337+
338+
if (isGlobalAcl(acl.getVpcId()) && !Account.Type.ADMIN.equals(caller.getType())) {
339+
throw new PermissionDeniedException("Only Root Admins can create rules for a global ACL.");
340+
}
343341
validateAclRuleNumber(createNetworkACLCmd, acl);
344342

345343
NetworkACLItem.Action ruleAction = validateAndCreateNetworkAclRuleAction(action);
@@ -409,7 +407,8 @@ protected void validateAclRuleNumber(CreateNetworkACLCmd createNetworkAclCmd, Ne
409407
* <ul>
410408
* <li>If the parameter is null, we return an {@link InvalidParameterValueException};
411409
* <li>Default ACLs {@link NetworkACL#DEFAULT_ALLOW} and {@link NetworkACL#DEFAULT_DENY} cannot be modified. Therefore, if any of them is provided we throw a {@link InvalidParameterValueException};
412-
* <li>If the network does not have a VPC, we will throw an {@link InvalidParameterValueException}.
410+
* <li>If no VPC is given, then it is a global ACL and there is no need to check any VPC ID. However, if a VPC is given and it does not exist, throws an
411+
* {@link InvalidParameterValueException}.
413412
* </ul>
414413
*
415414
* After all validations, we check if the user has access to the given network ACL using {@link AccountManager#checkAccess(Account, org.apache.cloudstack.acl.SecurityChecker.AccessType, boolean, org.apache.cloudstack.acl.ControlledEntity...)}.
@@ -419,16 +418,14 @@ protected void validateNetworkAcl(NetworkACL acl) {
419418
throw new InvalidParameterValueException("Unable to find specified ACL.");
420419
}
421420

422-
if (acl.getId() == NetworkACL.DEFAULT_DENY || acl.getId() == NetworkACL.DEFAULT_ALLOW) {
421+
if (isDefaultAcl(acl.getId())) {
423422
throw new InvalidParameterValueException("Default ACL cannot be modified");
424423
}
425424

426-
Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
427-
if (vpc == null) {
428-
throw new InvalidParameterValueException(String.format("Unable to find Vpc associated with the NetworkACL [%s]", acl.getUuid()));
425+
Long aclVpcId = acl.getVpcId();
426+
if (!isGlobalAcl(aclVpcId)) {
427+
validateAclAssociatedToVpc(aclVpcId, CallContext.current().getCallingAccount(), acl.getUuid());
429428
}
430-
Account caller = CallContext.current().getCallingAccount();
431-
_accountMgr.checkAccess(caller, null, true, vpc);
432429
}
433430

434431
/**
@@ -792,17 +789,12 @@ public boolean revokeNetworkACLItem(final long ruleId) {
792789
final NetworkACLItemVO aclItem = _networkACLItemDao.findById(ruleId);
793790
if (aclItem != null) {
794791
final NetworkACL acl = _networkAclMgr.getNetworkACL(aclItem.getAclId());
792+
final Account account = CallContext.current().getCallingAccount();
795793

796-
final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
797-
798-
if (aclItem.getAclId() == NetworkACL.DEFAULT_ALLOW || aclItem.getAclId() == NetworkACL.DEFAULT_DENY) {
794+
if (isDefaultAcl(aclItem.getAclId())) {
799795
throw new InvalidParameterValueException("ACL Items in default ACL cannot be deleted");
800796
}
801-
802-
final Account caller = CallContext.current().getCallingAccount();
803-
804-
_accountMgr.checkAccess(caller, null, true, vpc);
805-
797+
validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACL rules.");
806798
}
807799
return _networkAclMgr.revokeNetworkACLItem(ruleId);
808800
}
@@ -826,6 +818,9 @@ public NetworkACLItem updateNetworkACLItem(UpdateNetworkACLItemCmd updateNetwork
826818
NetworkACL acl = _networkAclMgr.getNetworkACL(networkACLItemVo.getAclId());
827819
validateNetworkAcl(acl);
828820

821+
Account account = CallContext.current().getCallingAccount();
822+
validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admins can update global ACLs.");
823+
829824
transferDataToNetworkAclRulePojo(updateNetworkACLItemCmd, networkACLItemVo, acl);
830825
validateNetworkACLItem(networkACLItemVo);
831826
return _networkAclMgr.updateNetworkACLItem(networkACLItemVo);
@@ -912,14 +907,13 @@ protected NetworkACLItemVO validateNetworkAclRuleIdAndRetrieveIt(UpdateNetworkAC
912907
}
913908

914909
@Override
915-
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_UPDATE, eventDescription = "updating network acl", async = true)
910+
@ActionEvent(eventType = EventTypes.EVENT_NETWORK_ACL_UPDATE, eventDescription = "Updating Network ACL", async = true)
916911
public NetworkACL updateNetworkACL(UpdateNetworkACLListCmd updateNetworkACLListCmd) {
917912
Long id = updateNetworkACLListCmd.getId();
918913
NetworkACLVO acl = _networkACLDao.findById(id);
919-
Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
914+
Account account = CallContext.current().getCallingAccount();
920915

921-
Account caller = CallContext.current().getCallingAccount();
922-
_accountMgr.checkAccess(caller, null, true, vpc);
916+
validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Must be Root Admin to update a global ACL.");
923917

924918
String name = updateNetworkACLListCmd.getName();
925919
if (StringUtils.isNotBlank(name)) {
@@ -1149,14 +1143,59 @@ protected void validateMoveAclRulesData(NetworkACLItemVO ruleBeingMoved, Network
11491143
long aclId = ruleBeingMoved.getAclId();
11501144

11511145
if ((nextRule != null && nextRule.getAclId() != aclId) || (previousRule != null && previousRule.getAclId() != aclId)) {
1152-
throw new InvalidParameterValueException("Cannot use ACL rules from differenting ACLs. Rule being moved.");
1146+
throw new InvalidParameterValueException("Cannot use ACL rules from differentiating ACLs. Rule being moved.");
11531147
}
11541148
NetworkACLVO acl = _networkACLDao.findById(aclId);
1155-
Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
1149+
Account account = CallContext.current().getCallingAccount();
1150+
1151+
if (isDefaultAcl(aclId)) {
1152+
throw new InvalidParameterValueException("Default ACL rules cannot be moved.");
1153+
}
1154+
1155+
validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account,"Must be Root Admin to move global ACL rules.");
1156+
}
1157+
1158+
/**
1159+
* Checks if the given ACL is a global ACL. If it is, then checks if the account is Root Admin, and throws an exception according to {@code exceptionMessage} param if it
1160+
* does not have permission.
1161+
*/
1162+
protected void checkGlobalAclPermission(Long aclVpcId, Account account, String exceptionMessage) {
1163+
if (isGlobalAcl(aclVpcId) && !Account.Type.ADMIN.equals(account.getType())) {
1164+
throw new PermissionDeniedException(exceptionMessage);
1165+
}
1166+
}
1167+
1168+
protected void validateAclAssociatedToVpc(Long aclVpcId, Account account, String aclUuid) {
1169+
final Vpc vpc = _vpcDao.findById(aclVpcId);
11561170
if (vpc == null) {
1157-
throw new InvalidParameterValueException("Re-ordering rules for a default ACL is prohibited");
1171+
throw new InvalidParameterValueException(String.format("Unable to find specified VPC [%s] associated with the ACL [%s].", aclVpcId, aclUuid));
11581172
}
1159-
Account caller = CallContext.current().getCallingAccount();
1160-
_accountMgr.checkAccess(caller, null, true, vpc);
1173+
_accountMgr.checkAccess(account, null, true, vpc);
1174+
}
1175+
1176+
/**
1177+
* It performs two different verifications depending on if the ACL is global or not.
1178+
* <ul>
1179+
* <li> If the given ACL is a Global ACL, i.e. has VPC ID = 0, then checks if the account is Root Admin, and throws an exception if it isn't.
1180+
* <li> If the given ACL is associated to a VPC, i.e. VPC ID != 0, then calls {@link #validateAclAssociatedToVpc(Long, Account, String)} and checks if the given {@code
1181+
* aclVpcId} is from a valid VPC.
1182+
* </ul>
1183+
*/
1184+
protected void validateGlobalAclPermissionAndAclAssociatedToVpc(NetworkACL acl, Account account, String exception){
1185+
if (isGlobalAcl(acl.getVpcId())) {
1186+
s_logger.info(String.format("Checking if account [%s] has permission to manipulate global ACL [%s].", account, acl));
1187+
checkGlobalAclPermission(acl.getVpcId(), account, exception);
1188+
} else {
1189+
s_logger.info(String.format("Validating ACL [%s] associated to VPC [%s] with account [%s].", acl, acl.getVpcId(), account));
1190+
validateAclAssociatedToVpc(acl.getVpcId(), account, acl.getUuid());
1191+
}
1192+
}
1193+
1194+
protected boolean isGlobalAcl(Long aclVpcId) {
1195+
return aclVpcId != null && aclVpcId == 0;
1196+
}
1197+
1198+
protected boolean isDefaultAcl(long aclId) {
1199+
return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY;
11611200
}
11621201
}

0 commit comments

Comments
 (0)