Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ jobs:
smoke/test_reset_configuration_settings
smoke/test_reset_vm_on_reboot
smoke/test_resource_accounting
smoke/test_resource_detail",
smoke/test_resource_detail
smoke/test_global_acls",
"smoke/test_router_dhcphosts
smoke/test_router_dns
smoke/test_router_dnsservice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
// under the License.
package org.apache.cloudstack.api.command.user.network;

import com.cloud.exception.PermissionDeniedException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiCommandResourceType;
Expand All @@ -26,6 +27,7 @@
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.NetworkACLResponse;
import org.apache.cloudstack.api.response.VpcResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.log4j.Logger;

import com.cloud.event.EventTypes;
Expand All @@ -35,7 +37,8 @@
import com.cloud.network.vpc.Vpc;
import com.cloud.user.Account;

@APICommand(name = "createNetworkACLList", description = "Creates a network ACL for the given VPC", responseObject = NetworkACLResponse.class,
@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.",
responseObject = NetworkACLResponse.class,
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class CreateNetworkACLListCmd extends BaseAsyncCreateCmd {
public static final Logger s_logger = Logger.getLogger(CreateNetworkACLListCmd.class.getName());
Expand All @@ -53,7 +56,6 @@ public class CreateNetworkACLListCmd extends BaseAsyncCreateCmd {

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

public void setVpcId(Long vpcId) {
this.vpcId = vpcId;
}

@Override
public boolean isDisplay() {
if (display != null) {
Expand All @@ -92,6 +98,9 @@ public boolean isDisplay() {

@Override
public void create() {
if (getVpcId() == null) {
setVpcId(0L);
}
NetworkACL result = _networkACLService.createNetworkACL(getName(), getDescription(), getVpcId(), isDisplay());
setEntityId(result.getId());
setEntityUuid(result.getUuid());
Expand All @@ -111,12 +120,21 @@ public void execute() throws ResourceUnavailableException {

@Override
public long getEntityOwnerId() {
Vpc vpc = _entityMgr.findById(Vpc.class, getVpcId());
if (vpc == null) {
throw new InvalidParameterValueException("Invalid vpcId is given");
}
Account account;
if (isAclAttachedToVpc(this.vpcId)) {
Vpc vpc = _entityMgr.findById(Vpc.class, this.vpcId);
if (vpc == null) {
throw new InvalidParameterValueException(String.format("Invalid VPC ID [%s] provided.", this.vpcId));
}
account = _accountService.getAccount(vpc.getAccountId());
} else {
account = CallContext.current().getCallingAccount();
if (!Account.Type.ADMIN.equals(account.getType())) {
s_logger.warn(String.format("Only Root Admin can create global ACLs. Account [%s] cannot create any global ACL.", account));
throw new PermissionDeniedException("Only Root Admin can create global ACLs.");
}

Account account = _accountService.getAccount(vpc.getAccountId());
}
return account.getId();
}

Expand All @@ -139,4 +157,8 @@ public Long getApiResourceId() {
public ApiCommandResourceType getApiResourceType() {
return ApiCommandResourceType.NetworkAcl;
}

public boolean isAclAttachedToVpc(Long aclVpcId) {
return aclVpcId != null && aclVpcId != 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package com.cloud.network.vpc;

import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils;

import java.util.UUID;

import javax.persistence.Column;
Expand Down Expand Up @@ -85,6 +87,11 @@ public String getName() {
return name;
}

@Override
public String toString() {
return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "vpcId");
}

public void setUuid(String uuid) {
this.uuid = uuid;
}
Expand Down
17 changes: 11 additions & 6 deletions server/src/main/java/com/cloud/network/NetworkServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2100,12 +2100,9 @@ public Network doInTransaction(TransactionStatus status) throws InsufficientCapa
throw new InvalidParameterValueException("Unable to find specified NetworkACL");
}

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

public boolean isDefaultAcl(Long aclId) {
return aclId == NetworkACL.DEFAULT_DENY || aclId == NetworkACL.DEFAULT_ALLOW;
}

public boolean isAclAttachedToVpc(Long aclVpcId, Long vpcId) {
return aclVpcId != 0 && !vpcId.equals(aclVpcId);
}

@Override
public PublicIpQuarantine updatePublicIpAddressInQuarantine(UpdateQuarantinedIpCmd cmd) throws CloudRuntimeException {
Long ipId = cmd.getId();
Expand Down
131 changes: 85 additions & 46 deletions server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import javax.inject.Inject;

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

@Override
public NetworkACL createNetworkACL(final String name, final String description, final long vpcId, final Boolean forDisplay) {
final Account caller = CallContext.current().getCallingAccount();
final Vpc vpc = _entityMgr.findById(Vpc.class, vpcId);
if (vpc == null) {
throw new InvalidParameterValueException("Unable to find VPC");
if (vpcId != 0) {
final Account caller = CallContext.current().getCallingAccount();
final Vpc vpc = _vpcDao.findById(vpcId);
if (vpc == null) {
throw new InvalidParameterValueException(String.format("Unable to find VPC with ID [%s].", vpcId));
}
_accountMgr.checkAccess(caller, null, true, vpc);

}
_accountMgr.checkAccess(caller, null, true, vpc);
return _networkAclMgr.createNetworkACL(name, description, vpcId, forDisplay);
}

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

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

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

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

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

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

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

validateNetworkAcl(acl);
Account caller = CallContext.current().getCallingAccount();

if (isGlobalAcl(acl.getVpcId()) && !Account.Type.ADMIN.equals(caller.getType())) {
throw new PermissionDeniedException("Only Root Admins can create rules for a global ACL.");
}
validateAclRuleNumber(createNetworkACLCmd, acl);

NetworkACLItem.Action ruleAction = validateAndCreateNetworkAclRuleAction(action);
Expand Down Expand Up @@ -409,7 +407,8 @@ protected void validateAclRuleNumber(CreateNetworkACLCmd createNetworkAclCmd, Ne
* <ul>
* <li>If the parameter is null, we return an {@link InvalidParameterValueException};
* <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};
* <li>If the network does not have a VPC, we will throw an {@link InvalidParameterValueException}.
* <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
* {@link InvalidParameterValueException}.
* </ul>
*
* 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...)}.
Expand All @@ -419,16 +418,14 @@ protected void validateNetworkAcl(NetworkACL acl) {
throw new InvalidParameterValueException("Unable to find specified ACL.");
}

if (acl.getId() == NetworkACL.DEFAULT_DENY || acl.getId() == NetworkACL.DEFAULT_ALLOW) {
if (isDefaultAcl(acl.getId())) {
throw new InvalidParameterValueException("Default ACL cannot be modified");
}

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

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

final Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());

if (aclItem.getAclId() == NetworkACL.DEFAULT_ALLOW || aclItem.getAclId() == NetworkACL.DEFAULT_DENY) {
if (isDefaultAcl(aclItem.getAclId())) {
throw new InvalidParameterValueException("ACL Items in default ACL cannot be deleted");
}

final Account caller = CallContext.current().getCallingAccount();

_accountMgr.checkAccess(caller, null, true, vpc);

validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admin can delete global ACL rules.");
}
return _networkAclMgr.revokeNetworkACLItem(ruleId);
}
Expand All @@ -826,6 +818,9 @@ public NetworkACLItem updateNetworkACLItem(UpdateNetworkACLItemCmd updateNetwork
NetworkACL acl = _networkAclMgr.getNetworkACL(networkACLItemVo.getAclId());
validateNetworkAcl(acl);

Account account = CallContext.current().getCallingAccount();
validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account, "Only Root Admins can update global ACLs.");

transferDataToNetworkAclRulePojo(updateNetworkACLItemCmd, networkACLItemVo, acl);
validateNetworkACLItem(networkACLItemVo);
return _networkAclMgr.updateNetworkACLItem(networkACLItemVo);
Expand Down Expand Up @@ -912,14 +907,13 @@ protected NetworkACLItemVO validateNetworkAclRuleIdAndRetrieveIt(UpdateNetworkAC
}

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

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

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

if ((nextRule != null && nextRule.getAclId() != aclId) || (previousRule != null && previousRule.getAclId() != aclId)) {
throw new InvalidParameterValueException("Cannot use ACL rules from differenting ACLs. Rule being moved.");
throw new InvalidParameterValueException("Cannot use ACL rules from differentiating ACLs. Rule being moved.");
}
NetworkACLVO acl = _networkACLDao.findById(aclId);
Vpc vpc = _entityMgr.findById(Vpc.class, acl.getVpcId());
Account account = CallContext.current().getCallingAccount();

if (isDefaultAcl(aclId)) {
throw new InvalidParameterValueException("Default ACL rules cannot be moved.");
}

validateGlobalAclPermissionAndAclAssociatedToVpc(acl, account,"Must be Root Admin to move global ACL rules.");
}

/**
* 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
* does not have permission.
*/
protected void checkGlobalAclPermission(Long aclVpcId, Account account, String exceptionMessage) {
if (isGlobalAcl(aclVpcId) && !Account.Type.ADMIN.equals(account.getType())) {
throw new PermissionDeniedException(exceptionMessage);
}
}

protected void validateAclAssociatedToVpc(Long aclVpcId, Account account, String aclUuid) {
final Vpc vpc = _vpcDao.findById(aclVpcId);
if (vpc == null) {
throw new InvalidParameterValueException("Re-ordering rules for a default ACL is prohibited");
throw new InvalidParameterValueException(String.format("Unable to find specified VPC [%s] associated with the ACL [%s].", aclVpcId, aclUuid));
}
Account caller = CallContext.current().getCallingAccount();
_accountMgr.checkAccess(caller, null, true, vpc);
_accountMgr.checkAccess(account, null, true, vpc);
}

/**
* It performs two different verifications depending on if the ACL is global or not.
* <ul>
* <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.
* <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
* aclVpcId} is from a valid VPC.
* </ul>
*/
protected void validateGlobalAclPermissionAndAclAssociatedToVpc(NetworkACL acl, Account account, String exception){
if (isGlobalAcl(acl.getVpcId())) {
s_logger.info(String.format("Checking if account [%s] has permission to manipulate global ACL [%s].", account, acl));
checkGlobalAclPermission(acl.getVpcId(), account, exception);
} else {
s_logger.info(String.format("Validating ACL [%s] associated to VPC [%s] with account [%s].", acl, acl.getVpcId(), account));
validateAclAssociatedToVpc(acl.getVpcId(), account, acl.getUuid());
}
}

protected boolean isGlobalAcl(Long aclVpcId) {
return aclVpcId != null && aclVpcId == 0;
}

protected boolean isDefaultAcl(long aclId) {
return aclId == NetworkACL.DEFAULT_ALLOW || aclId == NetworkACL.DEFAULT_DENY;
}
}
Loading