Merge branch '4.20'

This commit is contained in:
Daan Hoogland 2025-05-27 18:18:39 +02:00
commit 650b5ec3da
27 changed files with 790 additions and 185 deletions

View File

@ -18,6 +18,7 @@ package com.cloud.kubernetes.cluster;
import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity;
import com.cloud.user.Account;
import com.cloud.uservm.UserVm; import com.cloud.uservm.UserVm;
import com.cloud.utils.component.Adapter; import com.cloud.utils.component.Adapter;
@ -26,4 +27,5 @@ public interface KubernetesServiceHelper extends Adapter {
ControlledEntity findByUuid(String uuid); ControlledEntity findByUuid(String uuid);
ControlledEntity findByVmId(long vmId); ControlledEntity findByVmId(long vmId);
void checkVmCanBeDestroyed(UserVm userVm); void checkVmCanBeDestroyed(UserVm userVm);
void cleanupForAccount(Account account);
} }

View File

@ -71,6 +71,7 @@ public interface Account extends ControlledEntity, InternalIdentity, Identity {
} }
public static final long ACCOUNT_ID_SYSTEM = 1; public static final long ACCOUNT_ID_SYSTEM = 1;
public static final long ACCOUNT_ID_ADMIN = 2;
public String getAccountName(); public String getAccountName();

View File

@ -596,6 +596,7 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks, offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks,
"Offering for Isolated VPC networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Optional, null, "Offering for Isolated VPC networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Optional, null,
defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false); defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false);
} }
//#6 - default vpc offering with no LB service //#6 - default vpc offering with no LB service

View File

@ -16,6 +16,9 @@
// under the License. // under the License.
package com.cloud.user.dao; package com.cloud.user.dao;
import java.util.Date;
import java.util.List;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountVO; import com.cloud.user.AccountVO;
import com.cloud.user.User; import com.cloud.user.User;
@ -23,9 +26,6 @@ import com.cloud.utils.Pair;
import com.cloud.utils.db.Filter; import com.cloud.utils.db.Filter;
import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.GenericDao;
import java.util.Date;
import java.util.List;
public interface AccountDao extends GenericDao<AccountVO, Long> { public interface AccountDao extends GenericDao<AccountVO, Long> {
Pair<User, Account> findUserAccountByApiKey(String apiKey); Pair<User, Account> findUserAccountByApiKey(String apiKey);
@ -33,6 +33,8 @@ public interface AccountDao extends GenericDao<AccountVO, Long> {
Pair<List<AccountVO>, Integer> findAccountsLike(String accountName, Filter filter); Pair<List<AccountVO>, Integer> findAccountsLike(String accountName, Filter filter);
List<AccountVO> findAccountsByName(String accountName);
List<AccountVO> findActiveAccounts(Long maxAccountId, Filter filter); List<AccountVO> findActiveAccounts(Long maxAccountId, Filter filter);
List<AccountVO> findRecentlyDeletedAccounts(Long maxAccountId, Date earliestRemovedDate, Filter filter); List<AccountVO> findRecentlyDeletedAccounts(Long maxAccountId, Date earliestRemovedDate, Filter filter);

View File

@ -16,6 +16,14 @@
// under the License. // under the License.
package com.cloud.user.dao; package com.cloud.user.dao;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.Account.State; import com.cloud.user.Account.State;
import com.cloud.user.AccountVO; import com.cloud.user.AccountVO;
@ -30,14 +38,7 @@ import com.cloud.utils.db.SearchBuilder;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Func;
import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.SearchCriteria.Op;
import org.apache.commons.lang3.StringUtils;
import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.TransactionLegacy;
import org.springframework.stereotype.Component;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Date;
import java.util.List;
@Component @Component
public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements AccountDao { public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements AccountDao {
@ -190,6 +191,16 @@ public class AccountDaoImpl extends GenericDaoBase<AccountVO, Long> implements A
return searchAndCount(sc, filter); return searchAndCount(sc, filter);
} }
@Override
public List<AccountVO> findAccountsByName(String accountName) {
SearchBuilder<AccountVO> sb = createSearchBuilder();
sb.and("accountName", sb.entity().getAccountName(), SearchCriteria.Op.EQ);
sb.done();
SearchCriteria<AccountVO> sc = sb.create();
sc.setParameters("accountName", accountName);
return search(sc, null);
}
@Override @Override
public Account findEnabledAccount(String accountName, Long domainId) { public Account findEnabledAccount(String accountName, Long domainId) {
SearchCriteria<AccountVO> sc = AllFieldsSearch.create("accountName", accountName); SearchCriteria<AccountVO> sc = AllFieldsSearch.create("accountName", accountName);

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.api.command; package org.apache.cloudstack.api.command;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd;
@ -32,6 +33,7 @@ import javax.inject.Inject;
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class QuotaConfigureEmailCmd extends BaseCmd { public class QuotaConfigureEmailCmd extends BaseCmd {
@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, required = true, @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, required = true,
description = "Account ID for which to configure quota template email or min balance") description = "Account ID for which to configure quota template email or min balance")
private long accountId; private long accountId;

View File

@ -16,7 +16,7 @@
//under the License. //under the License.
package org.apache.cloudstack.api.command; package org.apache.cloudstack.api.command;
import com.cloud.user.Account; import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd;
@ -32,6 +32,7 @@ import javax.inject.Inject;
requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) requestHasSensitiveInfo = false, responseHasSensitiveInfo = false)
public class QuotaListEmailConfigurationCmd extends BaseCmd { public class QuotaListEmailConfigurationCmd extends BaseCmd {
@ACL
@Parameter(name = ApiConstants.ACCOUNT_ID, type = BaseCmd.CommandType.UUID, entityType = AccountResponse.class, required = true, @Parameter(name = ApiConstants.ACCOUNT_ID, type = BaseCmd.CommandType.UUID, entityType = AccountResponse.class, required = true,
description = "Account ID for which to list quota template email configurations") description = "Account ID for which to list quota template email configurations")
private long accountId; private long accountId;
@ -49,6 +50,6 @@ public class QuotaListEmailConfigurationCmd extends BaseCmd {
@Override @Override
public long getEntityOwnerId() { public long getEntityOwnerId() {
return Account.ACCOUNT_ID_SYSTEM; return accountId;
} }
} }

View File

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import com.cloud.agent.resource.virtualnetwork.VRScripts; import com.cloud.agent.resource.virtualnetwork.VRScripts;
@ -247,7 +248,7 @@ public final class CitrixStartCommandWrapper extends CommandWrapper<StartCommand
List<DiskTO> disks = new ArrayList<DiskTO>(vmSpec.getDisks().length); List<DiskTO> disks = new ArrayList<DiskTO>(vmSpec.getDisks().length);
int index = 0; int index = 0;
for (final DiskTO disk : vmSpec.getDisks()) { for (final DiskTO disk : vmSpec.getDisks()) {
if (Volume.Type.ISO.equals(disk.getType())) { if (Volume.Type.ISO.equals(disk.getType()) && Objects.nonNull(disk.getPath())) {
disks.add(0, disk); disks.add(0, disk);
} else { } else {
disks.add(index, disk); disks.add(index, disk);

View File

@ -44,6 +44,11 @@ import javax.naming.ConfigurationException;
import com.cloud.uservm.UserVm; import com.cloud.uservm.UserVm;
import com.cloud.vm.UserVmService; import com.cloud.vm.UserVmService;
import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.Role;
import org.apache.cloudstack.acl.RolePermissionEntity;
import org.apache.cloudstack.acl.RoleService;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.Rule;
import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.AnnotationService;
import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.annotation.dao.AnnotationDao;
@ -52,6 +57,14 @@ import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails;
import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ResponseObject.ResponseView;
import org.apache.cloudstack.api.command.user.address.AssociateIPAddrCmd;
import org.apache.cloudstack.api.command.user.address.DisassociateIPAddrCmd;
import org.apache.cloudstack.api.command.user.address.ListPublicIpAddressesCmd;
import org.apache.cloudstack.api.command.user.firewall.CreateFirewallRuleCmd;
import org.apache.cloudstack.api.command.user.firewall.DeleteFirewallRuleCmd;
import org.apache.cloudstack.api.command.user.firewall.ListFirewallRulesCmd;
import org.apache.cloudstack.api.command.user.firewall.UpdateFirewallRuleCmd;
import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.CreateKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.CreateKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.DeleteKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.DeleteKubernetesClusterCmd;
@ -62,6 +75,18 @@ import org.apache.cloudstack.api.command.user.kubernetes.cluster.ScaleKubernetes
import org.apache.cloudstack.api.command.user.kubernetes.cluster.StartKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.StartKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.StopKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.UpgradeKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.AssignToLoadBalancerRuleCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.CreateLoadBalancerRuleCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.DeleteLoadBalancerRuleCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.ListLoadBalancerRuleInstancesCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.ListLoadBalancerRulesCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.RemoveFromLoadBalancerRuleCmd;
import org.apache.cloudstack.api.command.user.loadbalancer.UpdateLoadBalancerRuleCmd;
import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.DeleteNetworkACLCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd;
import org.apache.cloudstack.api.command.user.network.ListNetworksCmd;
import org.apache.cloudstack.api.command.user.vm.ListVMsCmd;
import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse; import org.apache.cloudstack.api.response.KubernetesClusterConfigResponse;
import org.apache.cloudstack.api.response.KubernetesClusterResponse; import org.apache.cloudstack.api.response.KubernetesClusterResponse;
import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ListResponse;
@ -148,6 +173,8 @@ import com.cloud.offerings.dao.NetworkOfferingServiceMapDao;
import com.cloud.org.Cluster; import com.cloud.org.Cluster;
import com.cloud.org.Grouping; import com.cloud.org.Grouping;
import com.cloud.projects.Project; import com.cloud.projects.Project;
import com.cloud.projects.ProjectAccount;
import com.cloud.projects.ProjectManager;
import com.cloud.resource.ResourceManager; import com.cloud.resource.ResourceManager;
import com.cloud.service.ServiceOfferingVO; import com.cloud.service.ServiceOfferingVO;
import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.service.dao.ServiceOfferingDao;
@ -156,14 +183,17 @@ import com.cloud.storage.dao.VMTemplateDao;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountManager; import com.cloud.user.AccountManager;
import com.cloud.user.AccountService; import com.cloud.user.AccountService;
import com.cloud.user.AccountVO;
import com.cloud.user.SSHKeyPairVO; import com.cloud.user.SSHKeyPairVO;
import com.cloud.user.User; import com.cloud.user.User;
import com.cloud.user.UserAccount; import com.cloud.user.UserAccount;
import com.cloud.user.UserVO; import com.cloud.user.UserVO;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.SSHKeyPairDao; import com.cloud.user.dao.SSHKeyPairDao;
import com.cloud.user.dao.UserDao; import com.cloud.user.dao.UserDao;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.Ternary; import com.cloud.utils.Ternary;
import com.cloud.utils.UuidUtils;
import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.concurrency.NamedThreadFactory;
@ -187,6 +217,32 @@ import org.apache.logging.log4j.Level;
public class KubernetesClusterManagerImpl extends ManagerBase implements KubernetesClusterService { public class KubernetesClusterManagerImpl extends ManagerBase implements KubernetesClusterService {
private static final String DEFAULT_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNetworkOfferingforKubernetesService"; private static final String DEFAULT_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNetworkOfferingforKubernetesService";
private static final List<Class<?>> PROJECT_KUBERNETES_ACCOUNT_ROLE_ALLOWED_APIS = Arrays.asList(
QueryAsyncJobResultCmd.class,
ListVMsCmd.class,
ListNetworksCmd.class,
ListPublicIpAddressesCmd.class,
AssociateIPAddrCmd.class,
DisassociateIPAddrCmd.class,
ListLoadBalancerRulesCmd.class,
CreateLoadBalancerRuleCmd.class,
UpdateLoadBalancerRuleCmd.class,
DeleteLoadBalancerRuleCmd.class,
AssignToLoadBalancerRuleCmd.class,
RemoveFromLoadBalancerRuleCmd.class,
ListLoadBalancerRuleInstancesCmd.class,
ListFirewallRulesCmd.class,
CreateFirewallRuleCmd.class,
UpdateFirewallRuleCmd.class,
DeleteFirewallRuleCmd.class,
ListNetworkACLsCmd.class,
CreateNetworkACLCmd.class,
DeleteNetworkACLCmd.class,
ListKubernetesClustersCmd.class,
ScaleKubernetesClusterCmd.class
);
private static final String PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME = "Kubernetes";
private static final String PROJECT_KUBERNETES_ACCOUNT_LAST_NAME = "Service User";
private static final String DEFAULT_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_DISPLAY_TEXT = "Network Offering used for CloudStack Kubernetes service"; private static final String DEFAULT_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_DISPLAY_TEXT = "Network Offering used for CloudStack Kubernetes service";
private static final String DEFAULT_NSX_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNSXNetworkOfferingforKubernetesService"; private static final String DEFAULT_NSX_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNSXNetworkOfferingforKubernetesService";
private static final String DEFAULT_NSX_VPC_TIER_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNSXVPCNetworkOfferingforKubernetesService"; private static final String DEFAULT_NSX_VPC_TIER_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME = "DefaultNSXVPCNetworkOfferingforKubernetesService";
@ -264,11 +320,16 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
public SecurityGroupService securityGroupService; public SecurityGroupService securityGroupService;
@Inject @Inject
public NetworkHelper networkHelper; public NetworkHelper networkHelper;
@Inject @Inject
private UserVmService userVmService; private UserVmService userVmService;
@Inject @Inject
RoutedIpv4Manager routedIpv4Manager; RoutedIpv4Manager routedIpv4Manager;
@Inject
public AccountDao accountDao;
@Inject
public ProjectManager projectManager;
@Inject
RoleService roleService;
private void logMessage(final Level logLevel, final String message, final Exception e) { private void logMessage(final Level logLevel, final String message, final Exception e) {
if (logLevel == Level.WARN) { if (logLevel == Level.WARN) {
@ -1381,23 +1442,31 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
} }
} }
private String[] getServiceUserKeys(KubernetesClusterVO kubernetesCluster) { protected String[] createUserApiKeyAndSecretKey(long userId) {
Account owner = accountService.getActiveAccountById(kubernetesCluster.getAccountId()); CallContext.register(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM);
if (owner == null || owner.getType() == Account.Type.PROJECT) { try {
owner = CallContext.current().getCallingAccount(); return accountService.createApiKeyAndSecretKey(userId);
} finally {
CallContext.unregister();
}
}
protected String[] getServiceUserKeys(Account owner) {
String username = owner.getAccountName();
if (!username.startsWith(KUBEADMIN_ACCOUNT_NAME + "-")) {
username += "-" + KUBEADMIN_ACCOUNT_NAME;
} }
String username = owner.getAccountName() + "-" + KUBEADMIN_ACCOUNT_NAME;
UserAccount kubeadmin = accountService.getActiveUserAccount(username, owner.getDomainId()); UserAccount kubeadmin = accountService.getActiveUserAccount(username, owner.getDomainId());
String[] keys = null; String[] keys;
if (kubeadmin == null) { if (kubeadmin == null) {
User kube = userDao.persist(new UserVO(owner.getAccountId(), username, UUID.randomUUID().toString(), owner.getAccountName(), User kube = userDao.persist(new UserVO(owner.getAccountId(), username, UUID.randomUUID().toString(), owner.getAccountName(),
KUBEADMIN_ACCOUNT_NAME, "kubeadmin", null, UUID.randomUUID().toString(), User.Source.UNKNOWN)); KUBEADMIN_ACCOUNT_NAME, "kubeadmin", null, UUID.randomUUID().toString(), User.Source.UNKNOWN));
keys = accountService.createApiKeyAndSecretKey(kube.getId()); keys = createUserApiKeyAndSecretKey(kube.getId());
} else { } else {
String apiKey = kubeadmin.getApiKey(); String apiKey = kubeadmin.getApiKey();
String secretKey = kubeadmin.getSecretKey(); String secretKey = kubeadmin.getSecretKey();
if (StringUtils.isAnyEmpty(apiKey, secretKey)) { if (StringUtils.isAnyEmpty(apiKey, secretKey)) {
keys = accountService.createApiKeyAndSecretKey(kubeadmin.getId()); keys = createUserApiKeyAndSecretKey(kubeadmin.getId());
} else { } else {
keys = new String[]{apiKey, secretKey}; keys = new String[]{apiKey, secretKey};
} }
@ -1405,6 +1474,76 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return keys; return keys;
} }
protected Role createProjectKubernetesAccountRole() {
Role role = roleService.createRole(PROJECT_KUBEADMIN_ACCOUNT_ROLE_NAME, RoleType.User,
PROJECT_KUBEADMIN_ACCOUNT_ROLE_NAME, false);
for (Class<?> allowedApi : PROJECT_KUBERNETES_ACCOUNT_ROLE_ALLOWED_APIS) {
final String apiName = BaseCmd.getCommandNameByClass(allowedApi);
roleService.createRolePermission(role, new Rule(apiName), RolePermissionEntity.Permission.ALLOW,
String.format("Allow %s", apiName));
}
roleService.createRolePermission(role, new Rule("*"), RolePermissionEntity.Permission.DENY,
"Deny all");
logger.debug(String.format("Created default role for Kubernetes service account in projects: %s", role));
return role;
}
public Role getProjectKubernetesAccountRole() {
List<Role> roles = roleService.findRolesByName(PROJECT_KUBEADMIN_ACCOUNT_ROLE_NAME);
if (CollectionUtils.isNotEmpty(roles)) {
Role role = roles.get(0);
logger.debug(String.format("Found default role for Kubernetes service account in projects: %s", role));
return role;
}
return createProjectKubernetesAccountRole();
}
protected Account createProjectKubernetesAccount(final Project project, final String accountName) {
CallContext.register(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM);
try {
Role role = getProjectKubernetesAccountRole();
UserAccount userAccount = accountService.createUserAccount(accountName,
UuidUtils.first(UUID.randomUUID().toString()), PROJECT_KUBERNETES_ACCOUNT_FIRST_NAME,
PROJECT_KUBERNETES_ACCOUNT_LAST_NAME, null, null, accountName, Account.Type.NORMAL, role.getId(),
project.getDomainId(), null, null, null, null, User.Source.NATIVE);
projectManager.assignAccountToProject(project, userAccount.getAccountId(), ProjectAccount.Role.Regular,
userAccount.getId(), null);
Account account = accountService.getAccount(userAccount.getAccountId());
logger.debug(String.format("Created Kubernetes service account in project %s: %s", project, account));
return account;
} finally {
CallContext.unregister();
}
}
protected Account getProjectKubernetesAccount(final Account callerAccount, final boolean create) {
Project project = ApiDBUtils.findProjectByProjectAccountId(callerAccount.getId());
final String accountName = String.format("%s-%s", KUBEADMIN_ACCOUNT_NAME, UuidUtils.first(project.getUuid()));
List<AccountVO> accounts = accountDao.findAccountsByName(accountName);
for (AccountVO account : accounts) {
if (projectManager.canAccessProjectAccount(account, project.getProjectAccountId())) {
logger.debug(String.format("Created Kubernetes service account in project %s: %s", project, account));
return account;
}
}
return create ? createProjectKubernetesAccount(project, accountName) : null;
}
protected Account getProjectKubernetesAccount(final Account callerAccount) {
return getProjectKubernetesAccount(callerAccount, true);
}
private String[] getServiceUserKeys(KubernetesClusterVO kubernetesCluster) {
Account owner = accountService.getActiveAccountById(kubernetesCluster.getAccountId());
if (owner == null) {
owner = CallContext.current().getCallingAccount();
}
if (owner.getType() == Account.Type.PROJECT) {
owner = getProjectKubernetesAccount(owner);
}
return getServiceUserKeys(owner);
}
@Override @Override
@ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_STOP, @ActionEvent(eventType = KubernetesClusterEventTypes.EVENT_KUBERNETES_CLUSTER_STOP,
eventDescription = "stopping Kubernetes cluster", async = true) eventDescription = "stopping Kubernetes cluster", async = true)
@ -1449,15 +1588,13 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled"); logAndThrow(Level.ERROR, "Kubernetes Service plugin is disabled");
} }
Long kubernetesClusterId = cmd.getId(); Long kubernetesClusterId = cmd.getId();
KubernetesClusterVO cluster = kubernetesClusterDao.findById(kubernetesClusterId); final KubernetesClusterVO cluster = kubernetesClusterDao.findById(kubernetesClusterId);
if (cluster == null) { if (cluster == null) {
throw new InvalidParameterValueException("Invalid cluster id specified"); throw new InvalidParameterValueException("Invalid cluster id specified");
} }
accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, cluster); accountManager.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry, false, cluster);
if (cluster.getClusterType() == KubernetesCluster.ClusterType.CloudManaged) { if (cluster.getClusterType() == KubernetesCluster.ClusterType.CloudManaged) {
KubernetesClusterDestroyWorker destroyWorker = new KubernetesClusterDestroyWorker(cluster, this); return destroyKubernetesCluster(cluster);
destroyWorker = ComponentContext.inject(destroyWorker);
return destroyWorker.destroy();
} else { } else {
boolean cleanup = cmd.getCleanup(); boolean cleanup = cmd.getCleanup();
boolean expunge = cmd.getExpunge(); boolean expunge = cmd.getExpunge();
@ -1484,13 +1621,14 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
} }
} }
} }
return Transaction.execute(new TransactionCallback<Boolean>() { return Transaction.execute((TransactionCallback<Boolean>) status -> {
@Override kubernetesClusterDetailsDao.removeDetails(kubernetesClusterId);
public Boolean doInTransaction(TransactionStatus status) { kubernetesClusterVmMapDao.removeByClusterId(kubernetesClusterId);
kubernetesClusterDetailsDao.removeDetails(kubernetesClusterId); if (kubernetesClusterDao.remove(kubernetesClusterId)) {
kubernetesClusterVmMapDao.removeByClusterId(kubernetesClusterId); deleteProjectKubernetesAccountIfNeeded(cluster);
return kubernetesClusterDao.remove(kubernetesClusterId); return true;
} }
return false;
}); });
} }
} }
@ -1753,6 +1891,66 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
return responseList; return responseList;
} }
protected void deleteProjectKubernetesAccount(Account projectAccount) {
CallContext.register(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM);
try {
Account serviceAccount = getProjectKubernetesAccount(projectAccount, false);
if (serviceAccount != null) {
accountManager.deleteAccount(accountDao.findById(serviceAccount.getId()), User.UID_SYSTEM,
accountService.getSystemAccount());
}
} finally {
CallContext.unregister();
}
}
protected void deleteProjectKubernetesAccountIfNeeded(final KubernetesCluster kubernetesCluster) {
Account owner = accountService.getAccount(kubernetesCluster.getAccountId());
if (owner == null) {
return;
}
if (Account.Type.PROJECT.equals(owner.getType()) &&
kubernetesClusterDao.countNotForGCByAccount(owner.getAccountId()) == 0) {
deleteProjectKubernetesAccount(owner);
}
}
protected boolean destroyKubernetesCluster(KubernetesCluster kubernetesCluster, boolean deleteProjectAccount) {
KubernetesClusterDestroyWorker destroyWorker = new KubernetesClusterDestroyWorker(kubernetesCluster,
KubernetesClusterManagerImpl.this);
destroyWorker = ComponentContext.inject(destroyWorker);
boolean result = destroyWorker.destroy();
if (deleteProjectAccount) {
deleteProjectKubernetesAccountIfNeeded(kubernetesCluster);
}
return result;
}
protected boolean destroyKubernetesCluster(KubernetesCluster kubernetesCluster) {
return destroyKubernetesCluster(kubernetesCluster, true);
}
@Override
public void cleanupForAccount(Account account) {
List<KubernetesClusterVO> clusters = kubernetesClusterDao.listForCleanupByAccount(account.getId());
if (CollectionUtils.isEmpty(clusters)) {
return;
}
logger.debug(String.format("Cleaning up %d Kubernetes cluster for %s", clusters.size(), account));
for (KubernetesClusterVO cluster : clusters) {
try {
destroyKubernetesCluster(cluster, false);
} catch (CloudRuntimeException e) {
logger.warn(String.format("Failed to destroy Kubernetes cluster: %s during cleanup for %s",
cluster.getName(), account), e);
}
}
if (!Account.Type.PROJECT.equals(account.getType())) {
return;
}
deleteProjectKubernetesAccount(account);
}
@Override @Override
public List<Class<?>> getCommands() { public List<Class<?>> getCommands() {
List<Class<?>> cmdList = new ArrayList<Class<?>>(); List<Class<?>> cmdList = new ArrayList<Class<?>>();
@ -1804,12 +2002,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
logger.info("Running Kubernetes cluster garbage collector on Kubernetes cluster: {}", kubernetesCluster); logger.info("Running Kubernetes cluster garbage collector on Kubernetes cluster: {}", kubernetesCluster);
} }
try { try {
KubernetesClusterDestroyWorker destroyWorker = new KubernetesClusterDestroyWorker(kubernetesCluster, KubernetesClusterManagerImpl.this); if (destroyKubernetesCluster(kubernetesCluster)) {
destroyWorker = ComponentContext.inject(destroyWorker); logger.info("Garbage collection complete for Kubernetes cluster: {}", kubernetesCluster);
if (destroyWorker.destroy()) {
if (logger.isInfoEnabled()) {
logger.info("Garbage collection complete for Kubernetes cluster: {}", kubernetesCluster);
}
} else { } else {
logger.warn("Garbage collection failed for Kubernetes cluster : {}, it will be attempted to garbage collected in next run", kubernetesCluster); logger.warn("Garbage collection failed for Kubernetes cluster : {}, it will be attempted to garbage collected in next run", kubernetesCluster);
} }
@ -1932,9 +2126,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
logger.info("Running Kubernetes cluster state scanner on Kubernetes cluster: {} for state: {}", kubernetesCluster, KubernetesCluster.State.Destroying.toString()); logger.info("Running Kubernetes cluster state scanner on Kubernetes cluster: {} for state: {}", kubernetesCluster, KubernetesCluster.State.Destroying.toString());
} }
try { try {
KubernetesClusterDestroyWorker destroyWorker = new KubernetesClusterDestroyWorker(kubernetesCluster, KubernetesClusterManagerImpl.this); destroyKubernetesCluster(kubernetesCluster);
destroyWorker = ComponentContext.inject(destroyWorker);
destroyWorker.destroy();
} catch (Exception e) { } catch (Exception e) {
logger.warn("Failed to run Kubernetes cluster Destroying state scanner on Kubernetes cluster : {} status scanner", kubernetesCluster, e); logger.warn("Failed to run Kubernetes cluster Destroying state scanner on Kubernetes cluster : {} status scanner", kubernetesCluster, e);
} }
@ -1948,7 +2140,7 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
} }
// checks if Kubernetes cluster is in desired state // checks if Kubernetes cluster is in desired state
boolean isClusterVMsInDesiredState(KubernetesCluster kubernetesCluster, VirtualMachine.State state) { private boolean isClusterVMsInDesiredState(KubernetesCluster kubernetesCluster, VirtualMachine.State state) {
List<KubernetesClusterVmMapVO> clusterVMs = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId()); List<KubernetesClusterVmMapVO> clusterVMs = kubernetesClusterVmMapDao.listByClusterId(kubernetesCluster.getId());
// check cluster is running at desired capacity include control nodes as well // check cluster is running at desired capacity include control nodes as well
@ -1986,6 +2178,8 @@ public class KubernetesClusterManagerImpl extends ManagerBase implements Kuberne
createNetworkOfferingForKubernetes(DEFAULT_NSX_VPC_TIER_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME, createNetworkOfferingForKubernetes(DEFAULT_NSX_VPC_TIER_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_NAME,
DEFAULT_NSX_VPC_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_DISPLAY_TEXT , true, true); DEFAULT_NSX_VPC_NETWORK_OFFERING_FOR_KUBERNETES_SERVICE_DISPLAY_TEXT , true, true);
getProjectKubernetesAccountRole();
_gcExecutor.scheduleWithFixedDelay(new KubernetesClusterGarbageCollector(), 300, 300, TimeUnit.SECONDS); _gcExecutor.scheduleWithFixedDelay(new KubernetesClusterGarbageCollector(), 300, 300, TimeUnit.SECONDS);
_stateScanner.scheduleWithFixedDelay(new KubernetesClusterStatusScanner(), 300, 30, TimeUnit.SECONDS); _stateScanner.scheduleWithFixedDelay(new KubernetesClusterStatusScanner(), 300, 30, TimeUnit.SECONDS);

View File

@ -16,6 +16,8 @@
// under the License. // under the License.
package com.cloud.kubernetes.cluster; package com.cloud.kubernetes.cluster;
import java.util.List;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.AddVirtualMachinesToKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.CreateKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.CreateKubernetesClusterCmd;
import org.apache.cloudstack.api.command.user.kubernetes.cluster.DeleteKubernetesClusterCmd; import org.apache.cloudstack.api.command.user.kubernetes.cluster.DeleteKubernetesClusterCmd;
@ -34,16 +36,16 @@ import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.Configurable;
import com.cloud.network.Network; import com.cloud.network.Network;
import com.cloud.user.Account;
import com.cloud.utils.component.PluggableService; import com.cloud.utils.component.PluggableService;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import java.util.List;
public interface KubernetesClusterService extends PluggableService, Configurable { public interface KubernetesClusterService extends PluggableService, Configurable {
static final String MIN_KUBERNETES_VERSION_HA_SUPPORT = "1.16.0"; static final String MIN_KUBERNETES_VERSION_HA_SUPPORT = "1.16.0";
static final int MIN_KUBERNETES_CLUSTER_NODE_CPU = 2; static final int MIN_KUBERNETES_CLUSTER_NODE_CPU = 2;
static final int MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE = 2048; static final int MIN_KUBERNETES_CLUSTER_NODE_RAM_SIZE = 2048;
static final String KUBEADMIN_ACCOUNT_NAME = "kubeadmin"; static final String KUBEADMIN_ACCOUNT_NAME = "kubeadmin";
String PROJECT_KUBEADMIN_ACCOUNT_ROLE_NAME = "Project Kubernetes Service Role";
static final ConfigKey<Boolean> KubernetesServiceEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class, static final ConfigKey<Boolean> KubernetesServiceEnabled = new ConfigKey<Boolean>("Advanced", Boolean.class,
"cloud.kubernetes.service.enabled", "cloud.kubernetes.service.enabled",
@ -125,4 +127,6 @@ public interface KubernetesClusterService extends PluggableService, Configurable
List<RemoveVirtualMachinesFromKubernetesClusterResponse> removeVmsFromCluster(RemoveVirtualMachinesFromKubernetesClusterCmd cmd); List<RemoveVirtualMachinesFromKubernetesClusterResponse> removeVmsFromCluster(RemoveVirtualMachinesFromKubernetesClusterCmd cmd);
boolean isDirectAccess(Network network); boolean isDirectAccess(Network network);
void cleanupForAccount(Account account);
} }

View File

@ -32,6 +32,7 @@ import com.cloud.kubernetes.cluster.dao.KubernetesClusterDao;
import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao; import com.cloud.kubernetes.cluster.dao.KubernetesClusterVmMapDao;
import com.cloud.kubernetes.version.KubernetesSupportedVersion; import com.cloud.kubernetes.version.KubernetesSupportedVersion;
import com.cloud.kubernetes.version.KubernetesVersionEventTypes; import com.cloud.kubernetes.version.KubernetesVersionEventTypes;
import com.cloud.user.Account;
import com.cloud.uservm.UserVm; import com.cloud.uservm.UserVm;
import com.cloud.utils.component.AdapterBase; import com.cloud.utils.component.AdapterBase;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
@ -50,6 +51,8 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
private KubernetesClusterDao kubernetesClusterDao; private KubernetesClusterDao kubernetesClusterDao;
@Inject @Inject
private KubernetesClusterVmMapDao kubernetesClusterVmMapDao; private KubernetesClusterVmMapDao kubernetesClusterVmMapDao;
@Inject
KubernetesClusterService kubernetesClusterService;
protected void setEventTypeEntityDetails(Class<?> eventTypeDefinedClass, Class<?> entityClass) { protected void setEventTypeEntityDetails(Class<?> eventTypeDefinedClass, Class<?> entityClass) {
Field[] declaredFields = eventTypeDefinedClass.getDeclaredFields(); Field[] declaredFields = eventTypeDefinedClass.getDeclaredFields();
@ -106,6 +109,11 @@ public class KubernetesServiceHelperImpl extends AdapterBase implements Kubernet
throw new CloudRuntimeException(msg); throw new CloudRuntimeException(msg);
} }
@Override
public void cleanupForAccount(Account account) {
kubernetesClusterService.cleanupForAccount(account);
}
@Override @Override
public String getConfigComponentName() { public String getConfigComponentName() {
return KubernetesServiceHelper.class.getSimpleName(); return KubernetesServiceHelper.class.getSimpleName();

View File

@ -25,8 +25,8 @@ import com.cloud.utils.fsm.StateDao;
public interface KubernetesClusterDao extends GenericDao<KubernetesClusterVO, Long>, public interface KubernetesClusterDao extends GenericDao<KubernetesClusterVO, Long>,
StateDao<KubernetesCluster.State, KubernetesCluster.Event, KubernetesCluster> { StateDao<KubernetesCluster.State, KubernetesCluster.Event, KubernetesCluster> {
List<KubernetesClusterVO> listForCleanupByAccount(long accountId);
List<KubernetesClusterVO> listByAccount(long accountId); int countNotForGCByAccount(long accountId);
List<KubernetesClusterVO> findKubernetesClustersToGarbageCollect(); List<KubernetesClusterVO> findKubernetesClustersToGarbageCollect();
List<KubernetesClusterVO> findManagedKubernetesClustersInState(KubernetesCluster.State state); List<KubernetesClusterVO> findManagedKubernetesClustersInState(KubernetesCluster.State state);
List<KubernetesClusterVO> listByNetworkId(long networkId); List<KubernetesClusterVO> listByNetworkId(long networkId);

View File

@ -30,16 +30,25 @@ import com.cloud.utils.db.TransactionLegacy;
@Component @Component
public class KubernetesClusterDaoImpl extends GenericDaoBase<KubernetesClusterVO, Long> implements KubernetesClusterDao { public class KubernetesClusterDaoImpl extends GenericDaoBase<KubernetesClusterVO, Long> implements KubernetesClusterDao {
private final SearchBuilder<KubernetesClusterVO> AccountIdSearch; private final SearchBuilder<KubernetesClusterVO> CleanupAccountIdSearch;
private final SearchBuilder<KubernetesClusterVO> NotForGCByAccountIDCount;
private final SearchBuilder<KubernetesClusterVO> GarbageCollectedSearch; private final SearchBuilder<KubernetesClusterVO> GarbageCollectedSearch;
private final SearchBuilder<KubernetesClusterVO> ManagedStateSearch; private final SearchBuilder<KubernetesClusterVO> ManagedStateSearch;
private final SearchBuilder<KubernetesClusterVO> SameNetworkSearch; private final SearchBuilder<KubernetesClusterVO> SameNetworkSearch;
private final SearchBuilder<KubernetesClusterVO> KubernetesVersionSearch; private final SearchBuilder<KubernetesClusterVO> KubernetesVersionSearch;
public KubernetesClusterDaoImpl() { public KubernetesClusterDaoImpl() {
AccountIdSearch = createSearchBuilder(); CleanupAccountIdSearch = createSearchBuilder();
AccountIdSearch.and("account", AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); CleanupAccountIdSearch.and("account", CleanupAccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ);
AccountIdSearch.done(); CleanupAccountIdSearch.and("cluster_type", CleanupAccountIdSearch.entity().getClusterType(), SearchCriteria.Op.EQ);
CleanupAccountIdSearch.done();
NotForGCByAccountIDCount = createSearchBuilder();
NotForGCByAccountIDCount.and("gc", NotForGCByAccountIDCount.entity().isCheckForGc(), SearchCriteria.Op.EQ);
NotForGCByAccountIDCount.and("account", NotForGCByAccountIDCount.entity().getAccountId(), SearchCriteria.Op.EQ);
NotForGCByAccountIDCount.and("cluster_type", NotForGCByAccountIDCount.entity().getClusterType(), SearchCriteria.Op.EQ);
NotForGCByAccountIDCount.select(null, SearchCriteria.Func.COUNT, null);
NotForGCByAccountIDCount.done();
GarbageCollectedSearch = createSearchBuilder(); GarbageCollectedSearch = createSearchBuilder();
GarbageCollectedSearch.and("gc", GarbageCollectedSearch.entity().isCheckForGc(), SearchCriteria.Op.EQ); GarbageCollectedSearch.and("gc", GarbageCollectedSearch.entity().isCheckForGc(), SearchCriteria.Op.EQ);
@ -62,10 +71,20 @@ public class KubernetesClusterDaoImpl extends GenericDaoBase<KubernetesClusterVO
} }
@Override @Override
public List<KubernetesClusterVO> listByAccount(long accountId) { public List<KubernetesClusterVO> listForCleanupByAccount(long accountId) {
SearchCriteria<KubernetesClusterVO> sc = AccountIdSearch.create(); SearchCriteria<KubernetesClusterVO> sc = CleanupAccountIdSearch.create();
sc.setParameters("cluster_type", KubernetesCluster.ClusterType.CloudManaged);
sc.setParameters("account", accountId); sc.setParameters("account", accountId);
return listBy(sc, null); return listBy(sc);
}
@Override
public int countNotForGCByAccount(long accountId) {
SearchCriteria<KubernetesClusterVO> sc = NotForGCByAccountIDCount.create();
sc.setParameters("cluster_type", KubernetesCluster.ClusterType.CloudManaged);
sc.setParameters("account", accountId);
sc.setParameters("gc", false);
return getCount(sc);
} }
@Override @Override

View File

@ -533,4 +533,8 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) { public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) {
return null; return null;
} }
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount) {
}
} }

View File

@ -76,6 +76,9 @@ public class VxlanGuestNetworkGuru extends GuestNetworkGuru {
network.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(vxlan)); network.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(vxlan));
} }
network.setBroadcastDomainType(BroadcastDomainType.Vxlan); network.setBroadcastDomainType(BroadcastDomainType.Vxlan);
getOrCreateIpv4SubnetForGuestNetwork(offering, network, userSpecified, owner);
return updateNetworkDesignForIPv6IfNeeded(network, userSpecified); return updateNetworkDesignForIPv6IfNeeded(network, userSpecified);
} }

View File

@ -3723,7 +3723,9 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
SearchCriteria<DiskOfferingVO> sc = diskOfferingSearch.create(); SearchCriteria<DiskOfferingVO> sc = diskOfferingSearch.create();
sc.setParameters("computeOnly", false); sc.setParameters("computeOnly", false);
sc.setParameters("activeState", DiskOffering.State.Active); if (state != null) {
sc.setParameters("state", state);
}
sc.setJoinParameters("domainDetailsSearch", "domainId", domainId); sc.setJoinParameters("domainDetailsSearch", "domainId", domainId);
@ -4870,7 +4872,7 @@ public class QueryManagerImpl extends MutualExclusiveIdsManagerBase implements Q
if (!permittedAccounts.isEmpty()) { if (!permittedAccounts.isEmpty()) {
domain = _domainDao.findById(permittedAccounts.get(0).getDomainId()); domain = _domainDao.findById(permittedAccounts.get(0).getDomainId());
} else { } else {
domain = _domainDao.findById(Domain.ROOT_DOMAIN); domain = _domainDao.findById(caller.getDomainId());
} }
setIdsListToSearchCriteria(sc, ids); setIdsListToSearchCriteria(sc, ids);

View File

@ -50,7 +50,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import com.cloud.resource.ResourceManager; import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.acl.SecurityChecker;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.AffinityGroupService;
@ -254,6 +254,7 @@ import com.cloud.org.Grouping;
import com.cloud.org.Grouping.AllocationState; import com.cloud.org.Grouping.AllocationState;
import com.cloud.projects.Project; import com.cloud.projects.Project;
import com.cloud.projects.ProjectManager; import com.cloud.projects.ProjectManager;
import com.cloud.resource.ResourceManager;
import com.cloud.server.ConfigurationServer; import com.cloud.server.ConfigurationServer;
import com.cloud.server.ManagementService; import com.cloud.server.ManagementService;
import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.ServiceOfferingDetailsVO;
@ -277,6 +278,7 @@ import com.cloud.user.Account;
import com.cloud.user.AccountDetailVO; import com.cloud.user.AccountDetailVO;
import com.cloud.user.AccountDetailsDao; import com.cloud.user.AccountDetailsDao;
import com.cloud.user.AccountManager; import com.cloud.user.AccountManager;
import com.cloud.user.AccountManagerImpl;
import com.cloud.user.AccountVO; import com.cloud.user.AccountVO;
import com.cloud.user.ResourceLimitService; import com.cloud.user.ResourceLimitService;
import com.cloud.user.User; import com.cloud.user.User;
@ -484,6 +486,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
private long _defaultPageSize = Long.parseLong(Config.DefaultPageSize.getDefaultValue()); private long _defaultPageSize = Long.parseLong(Config.DefaultPageSize.getDefaultValue());
private static final String DOMAIN_NAME_PATTERN = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{1,63}$"; private static final String DOMAIN_NAME_PATTERN = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{1,63}$";
private Set<String> configValuesForValidation = new HashSet<>(); private Set<String> configValuesForValidation = new HashSet<>();
private Set<String> configKeysAllowedOnlyForDefaultAdmin = new HashSet<>();
private Set<String> weightBasedParametersForValidation = new HashSet<>(); private Set<String> weightBasedParametersForValidation = new HashSet<>();
private Set<String> overprovisioningFactorsForValidation = new HashSet<>(); private Set<String> overprovisioningFactorsForValidation = new HashSet<>();
@ -548,6 +551,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
populateConfigValuesForValidationSet(); populateConfigValuesForValidationSet();
weightBasedParametersForValidation(); weightBasedParametersForValidation();
overProvisioningFactorsForValidation(); overProvisioningFactorsForValidation();
populateConfigKeysAllowedOnlyForDefaultAdmin();
initMessageBusListener(); initMessageBusListener();
return true; return true;
} }
@ -612,6 +616,11 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
overprovisioningFactorsForValidation.add(CapacityManager.StorageOverprovisioningFactor.key()); overprovisioningFactorsForValidation.add(CapacityManager.StorageOverprovisioningFactor.key());
} }
protected void populateConfigKeysAllowedOnlyForDefaultAdmin() {
configKeysAllowedOnlyForDefaultAdmin.add(AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key());
configKeysAllowedOnlyForDefaultAdmin.add(AccountManagerImpl.allowOperationsOnUsersInSameAccount.key());
}
private void initMessageBusListener() { private void initMessageBusListener() {
messageBus.subscribe(EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, new MessageSubscriber() { messageBus.subscribe(EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, new MessageSubscriber() {
@Override @Override
@ -1227,6 +1236,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
logger.error("Missing configuration variable " + name + " in configuration table"); logger.error("Missing configuration variable " + name + " in configuration table");
return "Invalid configuration variable."; return "Invalid configuration variable.";
} }
validateConfigurationAllowedOnlyForDefaultAdmin(name, value);
List<ConfigKey.Scope> configScope = cfg.getScopes(); List<ConfigKey.Scope> configScope = cfg.getScopes();
if (scope != null) { if (scope != null) {
@ -1262,6 +1272,33 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
return validateValueRange(name, value, type, configuration); return validateValueRange(name, value, type, configuration);
} }
protected void validateConfigurationAllowedOnlyForDefaultAdmin(String configName, String value) {
if (configKeysAllowedOnlyForDefaultAdmin.contains(configName)) {
final Long userId = CallContext.current().getCallingUserId();
if (userId != User.UID_ADMIN) {
throw new CloudRuntimeException("Only default admin is allowed to change this setting");
}
if (AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key().equals(configName)) {
if (value != null && !value.isBlank()) {
List<String> validRoleTypes = Arrays.stream(RoleType.values())
.map(Enum::name)
.collect(Collectors.toList());
boolean allValid = Arrays.stream(value.split(","))
.map(String::trim)
.allMatch(validRoleTypes::contains);
if (!allValid) {
throw new CloudRuntimeException("Invalid role types provided in value");
}
} else {
throw new CloudRuntimeException("Value for role types must not be empty");
}
}
}
}
/** /**
* Returns whether a value is valid for a configuration of the provided type. * Returns whether a value is valid for a configuration of the provided type.
* Valid configuration values are: * Valid configuration values are:

View File

@ -24,8 +24,6 @@ import javax.inject.Inject;
import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap;
import org.apache.cloudstack.network.RoutedIpv4Manager;
import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter;
import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.DataCenter.NetworkType;
@ -37,7 +35,6 @@ import com.cloud.event.EventTypes;
import com.cloud.event.EventVO; import com.cloud.event.EventVO;
import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientAddressCapacityException;
import com.cloud.exception.InsufficientVirtualNetworkCapacityException; import com.cloud.exception.InsufficientVirtualNetworkCapacityException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.network.IpAddressManager; import com.cloud.network.IpAddressManager;
import com.cloud.network.Network; import com.cloud.network.Network;
import com.cloud.network.Network.GuestType; import com.cloud.network.Network.GuestType;
@ -90,8 +87,6 @@ public class ExternalGuestNetworkGuru extends GuestNetworkGuru {
FirewallRulesDao _fwRulesDao; FirewallRulesDao _fwRulesDao;
@Inject @Inject
FirewallRulesCidrsDao _fwRulesCidrDao; FirewallRulesCidrsDao _fwRulesCidrDao;
@Inject
RoutedIpv4Manager routedIpv4Manager;
public ExternalGuestNetworkGuru() { public ExternalGuestNetworkGuru() {
super(); super();
@ -126,23 +121,9 @@ public class ExternalGuestNetworkGuru extends GuestNetworkGuru {
/* In order to revert userSpecified network setup */ /* In order to revert userSpecified network setup */
config.setState(State.Allocated); config.setState(State.Allocated);
} }
if (NetworkOffering.NetworkMode.ROUTED.equals(offering.getNetworkMode()) && !offering.isForVpc()) {
if (userSpecified.getCidr() != null) { getOrCreateIpv4SubnetForGuestNetwork(offering, config, userSpecified, owner);
routedIpv4Manager.getOrCreateIpv4SubnetForGuestNetwork(config, userSpecified.getCidr());
} else {
if (userSpecified.getNetworkCidrSize() == null) {
throw new InvalidParameterValueException("The network CIDR or CIDR size must be specified.");
}
Ipv4GuestSubnetNetworkMap subnet = routedIpv4Manager.getOrCreateIpv4SubnetForGuestNetwork(owner.getDomainId(), owner.getAccountId(), config.getDataCenterId(), userSpecified.getNetworkCidrSize());
if (subnet != null) {
final String[] cidrTuple = subnet.getSubnet().split("\\/");
config.setGateway(NetUtils.getIpRangeStartIpFromCidr(cidrTuple[0], Long.parseLong(cidrTuple[1])));
config.setCidr(subnet.getSubnet());
} else {
throw new InvalidParameterValueException("Failed to allocate a CIDR with requested size.");
}
}
}
return updateNetworkDesignForIPv6IfNeeded(config, userSpecified); return updateNetworkDesignForIPv6IfNeeded(config, userSpecified);
} }

View File

@ -28,6 +28,8 @@ import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationSe
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap;
import org.apache.cloudstack.network.RoutedIpv4Manager;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import com.cloud.configuration.Config; import com.cloud.configuration.Config;
@ -121,6 +123,8 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur
Ipv6AddressManager ipv6AddressManager; Ipv6AddressManager ipv6AddressManager;
@Inject @Inject
DomainRouterDao domainRouterDao; DomainRouterDao domainRouterDao;
@Inject
RoutedIpv4Manager routedIpv4Manager;
Random _rand = new Random(System.currentTimeMillis()); Random _rand = new Random(System.currentTimeMillis());
@ -571,4 +575,24 @@ public abstract class GuestNetworkGuru extends AdapterBase implements NetworkGur
} }
return network; return network;
} }
public void getOrCreateIpv4SubnetForGuestNetwork(NetworkOffering offering, NetworkVO config, Network userSpecified, Account owner) {
if (NetworkOffering.NetworkMode.ROUTED.equals(offering.getNetworkMode()) && !offering.isForVpc()) {
if (userSpecified.getCidr() != null) {
routedIpv4Manager.getOrCreateIpv4SubnetForGuestNetwork(config, userSpecified.getCidr());
} else {
if (userSpecified.getNetworkCidrSize() == null) {
throw new InvalidParameterValueException("The network CIDR or CIDR size must be specified.");
}
Ipv4GuestSubnetNetworkMap subnet = routedIpv4Manager.getOrCreateIpv4SubnetForGuestNetwork(owner.getDomainId(), owner.getAccountId(), config.getDataCenterId(), userSpecified.getNetworkCidrSize());
if (subnet != null) {
final String[] cidrTuple = subnet.getSubnet().split("\\/");
config.setGateway(NetUtils.getIpRangeStartIpFromCidr(cidrTuple[0], Long.parseLong(cidrTuple[1])));
config.setCidr(subnet.getSubnet());
} else {
throw new InvalidParameterValueException("Failed to allocate a CIDR with requested size.");
}
}
}
}
} }

View File

@ -964,6 +964,7 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
} else { } else {
_accountMgr.checkAccess(caller, null, true, account); _accountMgr.checkAccess(caller, null, true, account);
} }
_accountMgr.verifyCallerPrivilegeForUserOrAccountOperations(account);
ownerType = ResourceOwnerType.Account; ownerType = ResourceOwnerType.Account;
ownerId = accountId; ownerId = accountId;
@ -1133,6 +1134,11 @@ public class ResourceLimitManagerImpl extends ManagerBase implements ResourceLim
throw new InvalidParameterValueException("Please specify a valid domain ID."); throw new InvalidParameterValueException("Please specify a valid domain ID.");
} }
_accountMgr.checkAccess(callerAccount, domain); _accountMgr.checkAccess(callerAccount, domain);
Account account = _entityMgr.findById(Account.class, accountId);
if (account == null) {
throw new InvalidParameterValueException("Unable to find account " + accountId);
}
_accountMgr.verifyCallerPrivilegeForUserOrAccountOperations(account);
if (resourceType != null) { if (resourceType != null) {
resourceTypes.add(resourceType); resourceTypes.add(resourceType);

View File

@ -206,4 +206,5 @@ public interface AccountManager extends AccountService, Configurable {
UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user); UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user);
void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount);
} }

View File

@ -20,6 +20,7 @@ import java.net.InetAddress;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
@ -120,6 +121,7 @@ import com.cloud.exception.OperationTimedoutException;
import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.PermissionDeniedException;
import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.ResourceUnavailableException;
import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDao;
import com.cloud.kubernetes.cluster.KubernetesServiceHelper;
import com.cloud.network.IpAddress; import com.cloud.network.IpAddress;
import com.cloud.network.IpAddressManager; import com.cloud.network.IpAddressManager;
import com.cloud.network.Network; import com.cloud.network.Network;
@ -350,6 +352,9 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
private List<UserTwoFactorAuthenticator> userTwoFactorAuthenticationProviders; private List<UserTwoFactorAuthenticator> userTwoFactorAuthenticationProviders;
private long validUserLastAuthTimeDurationInMs = 0L;
private static final long DEFAULT_USER_AUTH_TIME_DURATION_MS = 350L;
public static ConfigKey<Boolean> enableUserTwoFactorAuthentication = new ConfigKey<>("Advanced", public static ConfigKey<Boolean> enableUserTwoFactorAuthentication = new ConfigKey<>("Advanced",
Boolean.class, Boolean.class,
"enable.user.2fa", "enable.user.2fa",
@ -394,6 +399,22 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
true, true,
ConfigKey.Scope.Domain); ConfigKey.Scope.Domain);
public static ConfigKey<String> listOfRoleTypesAllowedForOperationsOfSameRoleType = new ConfigKey<>("Hidden",
String.class,
"role.types.allowed.for.operations.on.accounts.of.same.role.type",
"Admin, ResourceAdmin, DomainAdmin",
"Comma separated list of role types that are allowed to do operations on accounts or users of the same role type within a domain.",
true,
ConfigKey.Scope.Domain);
public static ConfigKey<Boolean> allowOperationsOnUsersInSameAccount = new ConfigKey<>("Hidden",
Boolean.class,
"allow.operations.on.users.in.same.account",
"true",
"Allow operations on users among them in the same account",
true,
ConfigKey.Scope.Domain);
protected AccountManagerImpl() { protected AccountManagerImpl() {
super(); super();
} }
@ -884,6 +905,16 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return cleanupAccount(account, callerUserId, caller); return cleanupAccount(account, callerUserId, caller);
} }
protected void cleanupPluginsResourcesIfNeeded(Account account) {
try {
KubernetesServiceHelper kubernetesServiceHelper =
ComponentContext.getDelegateComponentOfType(KubernetesServiceHelper.class);
kubernetesServiceHelper.cleanupForAccount(account);
} catch (NoSuchBeanDefinitionException ignored) {
logger.debug("No KubernetesServiceHelper bean found");
}
}
protected boolean cleanupAccount(AccountVO account, long callerUserId, Account caller) { protected boolean cleanupAccount(AccountVO account, long callerUserId, Account caller) {
long accountId = account.getId(); long accountId = account.getId();
boolean accountCleanupNeeded = false; boolean accountCleanupNeeded = false;
@ -965,6 +996,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
} }
cleanupPluginsResourcesIfNeeded(account);
// Destroy the account's VMs // Destroy the account's VMs
List<UserVmVO> vms = _userVmDao.listByAccountId(accountId); List<UserVmVO> vms = _userVmDao.listByAccountId(accountId);
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -1388,7 +1421,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
/** /**
* if there is any permission under the requested role that is not permitted for the caller, refuse * if there is any permission under the requested role that is not permitted for the caller, refuse
*/ */
private void checkRoleEscalation(Account caller, Account requested) { protected void checkRoleEscalation(Account caller, Account requested) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Checking if user of account %s [%s] with role-id [%d] can create an account of type %s [%s] with role-id [%d]", logger.debug(String.format("Checking if user of account %s [%s] with role-id [%d] can create an account of type %s [%s] with role-id [%d]",
caller.getAccountName(), caller.getAccountName(),
@ -1494,6 +1527,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
assertUserNotAlreadyInAccount(duplicatedUser, account); assertUserNotAlreadyInAccount(duplicatedUser, account);
} }
verifyCallerPrivilegeForUserOrAccountOperations(account);
UserVO user; UserVO user;
user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source); user = createUser(account.getId(), userName, password, firstName, lastName, email, timeZone, userUUID, source);
return user; return user;
@ -1510,11 +1544,15 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@ActionEvent(eventType = EventTypes.EVENT_USER_UPDATE, eventDescription = "Updating User") @ActionEvent(eventType = EventTypes.EVENT_USER_UPDATE, eventDescription = "Updating User")
public UserAccount updateUser(UpdateUserCmd updateUserCmd) { public UserAccount updateUser(UpdateUserCmd updateUserCmd) {
UserVO user = retrieveAndValidateUser(updateUserCmd); UserVO user = retrieveAndValidateUser(updateUserCmd);
Account account = retrieveAndValidateAccount(user);
User caller = CallContext.current().getCallingUser();
checkAccess(caller, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
logger.debug("Updating user {}", user); logger.debug("Updating user {}", user);
validateAndUpdateApiAndSecretKeyIfNeeded(updateUserCmd, user); validateAndUpdateApiAndSecretKeyIfNeeded(updateUserCmd, user);
validateAndUpdateUserApiKeyAccess(updateUserCmd, user); validateAndUpdateUserApiKeyAccess(updateUserCmd, user);
Account account = retrieveAndValidateAccount(user);
validateAndUpdateFirstNameIfNeeded(updateUserCmd, user); validateAndUpdateFirstNameIfNeeded(updateUserCmd, user);
validateAndUpdateLastNameIfNeeded(updateUserCmd, user); validateAndUpdateLastNameIfNeeded(updateUserCmd, user);
@ -1537,6 +1575,85 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return _userAccountDao.findById(user.getId()); return _userAccountDao.findById(user.getId());
} }
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount) {
logger.debug(String.format("Verifying whether the caller has the correct privileges based on the user's role type and API permissions: %s", userAccount));
checkCallerRoleTypeAllowedForUserOrAccountOperations(userAccount, null);
checkCallerApiPermissionsForUserOrAccountOperations(userAccount);
}
protected void verifyCallerPrivilegeForUserOrAccountOperations(User user) {
logger.debug(String.format("Verifying whether the caller has the correct privileges based on the user's role type and API permissions: %s", user));
Account userAccount = getAccount(user.getAccountId());
checkCallerRoleTypeAllowedForUserOrAccountOperations(userAccount, user);
checkCallerApiPermissionsForUserOrAccountOperations(userAccount);
}
protected void checkCallerRoleTypeAllowedForUserOrAccountOperations(Account userAccount, User user) {
Account callingAccount = getCurrentCallingAccount();
RoleType callerRoleType = getRoleType(callingAccount);
RoleType userAccountRoleType = getRoleType(userAccount);
if (RoleType.Unknown == callerRoleType || RoleType.Unknown == userAccountRoleType) {
String errMsg = String.format("The role type of account [%s, %s] or [%s, %s] is unknown",
callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid());
throw new PermissionDeniedException(errMsg);
}
boolean isCallerSystemOrDefaultAdmin = callingAccount.getId() == Account.ACCOUNT_ID_SYSTEM || callingAccount.getId() == Account.ACCOUNT_ID_ADMIN;
if (isCallerSystemOrDefaultAdmin) {
logger.trace(String.format("Admin account [%s, %s] performing this operation for user account [%s, %s] ", callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid()));
} else if (callerRoleType.getId() < userAccountRoleType.getId()) {
logger.trace(String.format("The calling account [%s, %s] has a higher role type than the user account [%s, %s]",
callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid()));
} else if (callerRoleType.getId() == userAccountRoleType.getId()) {
if (callingAccount.getId() != userAccount.getId()) {
String allowedRoleTypes = listOfRoleTypesAllowedForOperationsOfSameRoleType.valueInScope(ConfigKey.Scope.Domain, callingAccount.getDomainId());
boolean updateAllowed = allowedRoleTypes != null &&
Arrays.stream(allowedRoleTypes.split(","))
.map(String::trim)
.anyMatch(role -> role.equals(callerRoleType.toString()));
if (BooleanUtils.isFalse(updateAllowed)) {
String errMsg = String.format("The calling account [%s, %s] is not allowed to perform this operation on users from other accounts " +
"of the same role type within the domain", callingAccount.getName(), callingAccount.getUuid());
logger.error(errMsg);
throw new PermissionDeniedException(errMsg);
}
} else if ((callingAccount.getId() == userAccount.getId()) && user != null) {
Boolean allowOperationOnUsersinSameAccount = allowOperationsOnUsersInSameAccount.valueInScope(ConfigKey.Scope.Domain, callingAccount.getDomainId());
User callingUser = CallContext.current().getCallingUser();
if (callingUser.getId() != user.getId() && BooleanUtils.isFalse(allowOperationOnUsersinSameAccount)) {
String errMsg = "The user operations are not allowed by the users in the same account";
logger.error(errMsg);
throw new PermissionDeniedException(errMsg);
}
}
} else {
String errMsg = String.format("The calling account [%s, %s] has a lower role type than the user account [%s, %s]",
callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid());
throw new PermissionDeniedException(errMsg);
}
}
protected void checkCallerApiPermissionsForUserOrAccountOperations(Account userAccount) {
Account callingAccount = getCurrentCallingAccount();
boolean isCallerRootAdmin = callingAccount.getId() == Account.ACCOUNT_ID_SYSTEM || isRootAdmin(callingAccount.getId());
if (isCallerRootAdmin) {
logger.trace(String.format("Admin account [%s, %s] performing this operation for user account [%s, %s] ", callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid()));
} else if (isRootAdmin(userAccount.getAccountId())) {
String errMsg = String.format("Account [%s, %s] cannot perform this operation for user account [%s, %s] ", callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid());
logger.error(errMsg);
throw new PermissionDeniedException(errMsg);
} else {
logger.debug(String.format("Checking calling account [%s, %s] permission to perform this operation for user account [%s, %s] ", callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid()));
checkRoleEscalation(callingAccount, userAccount);
logger.debug(String.format("Calling account [%s, %s] is allowed to perform this operation for user account [%s, %s] ", callingAccount.getName(), callingAccount.getUuid(), userAccount.getName(), userAccount.getUuid()));
}
}
/** /**
* Updates the password in the user POJO if needed. If no password is provided, then the password is not updated. * Updates the password in the user POJO if needed. If no password is provided, then the password is not updated.
* The following validations are executed if 'password' is not null. Admins (root admins or domain admins) can execute password updates without entering the current password. * The following validations are executed if 'password' is not null. Admins (root admins or domain admins) can execute password updates without entering the current password.
@ -1599,7 +1716,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
for (UserAuthenticator userAuthenticator : _userPasswordEncoders) { for (UserAuthenticator userAuthenticator : _userPasswordEncoders) {
Pair<Boolean, ActionOnFailedAuthentication> authenticationResult = userAuthenticator.authenticate(user.getUsername(), currentPassword, userAccount.getDomainId(), null); Pair<Boolean, ActionOnFailedAuthentication> authenticationResult = userAuthenticator.authenticate(user.getUsername(), currentPassword, userAccount.getDomainId(), null);
if (authenticationResult == null) { if (authenticationResult == null) {
logger.trace(String.format("Authenticator [%s] is returning null for the authenticate mehtod.", userAuthenticator.getClass())); logger.trace(String.format("Authenticator [%s] is returning null for the authenticate method.", userAuthenticator.getClass()));
continue; continue;
} }
if (BooleanUtils.toBoolean(authenticationResult.first())) { if (BooleanUtils.toBoolean(authenticationResult.first())) {
@ -1816,6 +1933,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
checkAccess(caller, AccessType.OperateEntry, true, account); checkAccess(caller, AccessType.OperateEntry, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
boolean success = doSetUserStatus(userId, State.DISABLED); boolean success = doSetUserStatus(userId, State.DISABLED);
if (success) { if (success) {
@ -1857,6 +1975,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
checkAccess(caller, AccessType.OperateEntry, true, account); checkAccess(caller, AccessType.OperateEntry, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
boolean success = Transaction.execute(new TransactionCallback<>() { boolean success = Transaction.execute(new TransactionCallback<>() {
@Override @Override
@ -1909,6 +2028,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
checkAccess(caller, AccessType.OperateEntry, true, account); checkAccess(caller, AccessType.OperateEntry, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
// make sure the account is enabled too // make sure the account is enabled too
// if the user is either locked already or disabled already, don't change state...only lock currently enabled // if the user is either locked already or disabled already, don't change state...only lock currently enabled
@ -1972,6 +2092,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
checkIfAccountManagesProjects(accountId); checkIfAccountManagesProjects(accountId);
verifyCallerPrivilegeForUserOrAccountOperations(account);
CallContext.current().putContextParameter(Account.class, account.getUuid()); CallContext.current().putContextParameter(Account.class, account.getUuid());
@ -2034,6 +2155,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// Check if user performing the action is allowed to modify this account // Check if user performing the action is allowed to modify this account
Account caller = getCurrentCallingAccount(); Account caller = getCurrentCallingAccount();
checkAccess(caller, AccessType.OperateEntry, true, account); checkAccess(caller, AccessType.OperateEntry, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(account);
boolean success = enableAccount(account.getId()); boolean success = enableAccount(account.getId());
if (success) { if (success) {
@ -2067,6 +2189,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
checkAccess(caller, AccessType.OperateEntry, true, account); checkAccess(caller, AccessType.OperateEntry, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(account);
if (lockAccount(account.getId())) { if (lockAccount(account.getId())) {
CallContext.current().putContextParameter(Account.class, account.getUuid()); CallContext.current().putContextParameter(Account.class, account.getUuid());
@ -2097,6 +2220,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
checkAccess(caller, AccessType.OperateEntry, true, account); checkAccess(caller, AccessType.OperateEntry, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(account);
if (disableAccount(account.getId())) { if (disableAccount(account.getId())) {
CallContext.current().putContextParameter(Account.class, account.getUuid()); CallContext.current().putContextParameter(Account.class, account.getUuid());
@ -2142,6 +2266,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// Check if user performing the action is allowed to modify this account // Check if user performing the action is allowed to modify this account
Account caller = getCurrentCallingAccount(); Account caller = getCurrentCallingAccount();
checkAccess(caller, _domainMgr.getDomain(account.getDomainId())); checkAccess(caller, _domainMgr.getDomain(account.getDomainId()));
verifyCallerPrivilegeForUserOrAccountOperations(account);
validateAndUpdateAccountApiKeyAccess(cmd, acctForUpdate); validateAndUpdateAccountApiKeyAccess(cmd, acctForUpdate);
@ -2233,6 +2358,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
// don't allow to delete the user from the account of type Project // don't allow to delete the user from the account of type Project
checkAccountAndAccess(user, account); checkAccountAndAccess(user, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
return _userDao.remove(id); return _userDao.remove(id);
} }
@ -2243,6 +2369,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
UserVO user = getValidUserVO(id); UserVO user = getValidUserVO(id);
Account oldAccount = _accountDao.findById(user.getAccountId()); Account oldAccount = _accountDao.findById(user.getAccountId());
checkAccountAndAccess(user, oldAccount); checkAccountAndAccess(user, oldAccount);
verifyCallerPrivilegeForUserOrAccountOperations(user);
long domainId = oldAccount.getDomainId(); long domainId = oldAccount.getDomainId();
long newAccountId = getNewAccountId(domainId, cmd.getAccountName(), cmd.getAccountId()); long newAccountId = getNewAccountId(domainId, cmd.getAccountName(), cmd.getAccountId());
@ -2580,6 +2707,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
} }
if (!Account.Type.PROJECT.equals(accountType)) {
AccountVO newAccount = new AccountVO(accountName, domainId, networkDomain, accountType, roleId, uuid);
verifyCallerPrivilegeForUserOrAccountOperations(newAccount);
}
// Create the account // Create the account
return Transaction.execute(new TransactionCallback<>() { return Transaction.execute(new TransactionCallback<>() {
@Override @Override
@ -2644,107 +2776,17 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
@Override @Override
public UserAccount authenticateUser(final String username, final String password, final Long domainId, final InetAddress loginIpAddress, final Map<String, Object[]> requestParameters) { public UserAccount authenticateUser(final String username, final String password, final Long domainId, final InetAddress loginIpAddress, final Map<String, Object[]> requestParameters) {
long authStartTimeInMs = System.currentTimeMillis();
UserAccount user = null; UserAccount user = null;
final String[] oAuthProviderArray = (String[])requestParameters.get(ApiConstants.PROVIDER); final String[] oAuthProviderArray = (String[])requestParameters.get(ApiConstants.PROVIDER);
final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE); final String[] secretCodeArray = (String[])requestParameters.get(ApiConstants.SECRET_CODE);
String oauthProvider = ((oAuthProviderArray == null) ? null : oAuthProviderArray[0]); String oauthProvider = ((oAuthProviderArray == null) ? null : oAuthProviderArray[0]);
String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]); String secretCode = ((secretCodeArray == null) ? null : secretCodeArray[0]);
if ((password != null && !password.isEmpty()) || (oauthProvider != null && secretCode != null)) { if ((password != null && !password.isEmpty()) || (oauthProvider != null && secretCode != null)) {
user = getUserAccount(username, password, domainId, requestParameters); user = getUserAccount(username, password, domainId, requestParameters);
} else { } else {
String key = _configDao.getValue("security.singlesignon.key"); user = getUserAccountForSSO(username, domainId, requestParameters);
if (key == null) {
// the SSO key is gone, don't authenticate
return null;
}
String singleSignOnTolerance = _configDao.getValue("security.singlesignon.tolerance.millis");
if (singleSignOnTolerance == null) {
// the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be
// valid),
// don't authenticate
return null;
}
long tolerance = Long.parseLong(singleSignOnTolerance);
String signature = null;
long timestamp = 0L;
String unsignedRequest;
StringBuffer unsignedRequestBuffer = new StringBuffer();
// - build a request string with sorted params, make sure it's all lowercase
// - sign the request, verify the signature is the same
List<String> parameterNames = new ArrayList<>();
for (Object paramNameObj : requestParameters.keySet()) {
parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later
}
Collections.sort(parameterNames);
try {
for (String paramName : parameterNames) {
// parameters come as name/value pairs in the form String/String[]
String paramValue = ((String[])requestParameters.get(paramName))[0];
if ("signature".equalsIgnoreCase(paramName)) {
signature = paramValue;
} else {
if ("timestamp".equalsIgnoreCase(paramName)) {
String timestampStr = paramValue;
try {
// If the timestamp is in a valid range according to our tolerance, verify the request
// signature, otherwise return null to indicate authentication failure
timestamp = Long.parseLong(timestampStr);
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - timestamp) > tolerance) {
if (logger.isDebugEnabled()) {
logger.debug("Expired timestamp passed in to login, current time = " + currentTime + ", timestamp = " + timestamp);
}
return null;
}
} catch (NumberFormatException nfe) {
if (logger.isDebugEnabled()) {
logger.debug("Invalid timestamp passed in to login: " + timestampStr);
}
return null;
}
}
if (unsignedRequestBuffer.length() != 0) {
unsignedRequestBuffer.append("&");
}
unsignedRequestBuffer.append(paramName).append("=").append(URLEncoder.encode(paramValue, "UTF-8"));
}
}
if ((signature == null) || (timestamp == 0L)) {
if (logger.isDebugEnabled()) {
logger.debug("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp);
}
return null;
}
unsignedRequest = unsignedRequestBuffer.toString().toLowerCase().replaceAll("\\+", "%20");
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1");
mac.init(keySpec);
mac.update(unsignedRequest.getBytes());
byte[] encryptedBytes = mac.doFinal();
String computedSignature = new String(Base64.encodeBase64(encryptedBytes));
boolean equalSig = ConstantTimeComparator.compareStrings(signature, computedSignature);
if (!equalSig) {
logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature);
} else {
user = _userAccountDao.getUserAccount(username, domainId);
}
} catch (Exception ex) {
logger.error("Exception authenticating user", ex);
return null;
}
} }
if (user != null) { if (user != null) {
@ -2778,18 +2820,35 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
} }
ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN, "user has logged in from IP Address " + loginIpAddress, user.getId(), ApiCommandResourceType.User.toString());
validUserLastAuthTimeDurationInMs = System.currentTimeMillis() - authStartTimeInMs;
// Here all is fine! // Here all is fine!
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("User: " + username + " in domain " + domainId + " has successfully logged in"); logger.debug(String.format("User: %s in domain %d has successfully logged in, auth time duration - %d ms", username, domainId, validUserLastAuthTimeDurationInMs));
} }
ActionEventUtils.onActionEvent(user.getId(), user.getAccountId(), user.getDomainId(), EventTypes.EVENT_USER_LOGIN, "user has logged in from IP Address " + loginIpAddress, user.getId(), ApiCommandResourceType.User.toString());
return user; return user;
} else { } else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("User: " + username + " in domain " + domainId + " has failed to log in"); logger.debug("User: " + username + " in domain " + domainId + " has failed to log in");
} }
long waitTimeDurationInMs;
long invalidUserAuthTimeDurationInMs = System.currentTimeMillis() - authStartTimeInMs;
if (validUserLastAuthTimeDurationInMs > 0) {
waitTimeDurationInMs = validUserLastAuthTimeDurationInMs - invalidUserAuthTimeDurationInMs;
} else {
waitTimeDurationInMs = DEFAULT_USER_AUTH_TIME_DURATION_MS - invalidUserAuthTimeDurationInMs;
}
if (waitTimeDurationInMs > 0) {
try {
Thread.sleep(waitTimeDurationInMs);
} catch (final InterruptedException e) {
}
}
return null; return null;
} }
} }
@ -2827,7 +2886,6 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
boolean updateIncorrectLoginCount = actionsOnFailedAuthenticaion.contains(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT); boolean updateIncorrectLoginCount = actionsOnFailedAuthenticaion.contains(ActionOnFailedAuthentication.INCREMENT_INCORRECT_LOGIN_ATTEMPT_COUNT);
if (authenticated) { if (authenticated) {
Domain domain = _domainMgr.getDomain(domainId); Domain domain = _domainMgr.getDomain(domainId);
String domainName = null; String domainName = null;
if (domain != null) { if (domain != null) {
@ -2869,6 +2927,99 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
} }
private UserAccount getUserAccountForSSO(String username, Long domainId, Map<String, Object[]> requestParameters) {
String key = _configDao.getValue("security.singlesignon.key");
if (key == null) {
// the SSO key is gone, don't authenticate
return null;
}
String singleSignOnTolerance = _configDao.getValue("security.singlesignon.tolerance.millis");
if (singleSignOnTolerance == null) {
// the SSO tolerance is gone (how much time before/after system time we'll allow the login request to be
// valid),
// don't authenticate
return null;
}
UserAccount user = null;
long tolerance = Long.parseLong(singleSignOnTolerance);
String signature = null;
long timestamp = 0L;
String unsignedRequest;
StringBuffer unsignedRequestBuffer = new StringBuffer();
// - build a request string with sorted params, make sure it's all lowercase
// - sign the request, verify the signature is the same
List<String> parameterNames = new ArrayList<>();
for (Object paramNameObj : requestParameters.keySet()) {
parameterNames.add((String)paramNameObj); // put the name in a list that we'll sort later
}
Collections.sort(parameterNames);
try {
for (String paramName : parameterNames) {
// parameters come as name/value pairs in the form String/String[]
String paramValue = ((String[])requestParameters.get(paramName))[0];
if ("signature".equalsIgnoreCase(paramName)) {
signature = paramValue;
} else {
if ("timestamp".equalsIgnoreCase(paramName)) {
String timestampStr = paramValue;
try {
// If the timestamp is in a valid range according to our tolerance, verify the request
// signature, otherwise return null to indicate authentication failure
timestamp = Long.parseLong(timestampStr);
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - timestamp) > tolerance) {
logger.debug("Expired timestamp passed in to login, current time = {}, timestamp = {}", currentTime, timestamp);
return null;
}
} catch (NumberFormatException nfe) {
logger.debug("Invalid timestamp passed in to login: {}", timestampStr);
return null;
}
}
if (unsignedRequestBuffer.length() != 0) {
unsignedRequestBuffer.append("&");
}
unsignedRequestBuffer.append(paramName).append("=").append(URLEncoder.encode(paramValue, "UTF-8"));
}
}
if ((signature == null) || (timestamp == 0L)) {
if (logger.isDebugEnabled()) {
logger.debug("Missing parameters in login request, signature = " + signature + ", timestamp = " + timestamp);
}
return null;
}
unsignedRequest = unsignedRequestBuffer.toString().toLowerCase().replaceAll("\\+", "%20");
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "HmacSHA1");
mac.init(keySpec);
mac.update(unsignedRequest.getBytes());
byte[] encryptedBytes = mac.doFinal();
String computedSignature = new String(Base64.encodeBase64(encryptedBytes));
boolean equalSig = ConstantTimeComparator.compareStrings(signature, computedSignature);
if (!equalSig) {
logger.info("User signature: " + signature + " is not equaled to computed signature: " + computedSignature);
} else {
user = _userAccountDao.getUserAccount(username, domainId);
}
} catch (Exception ex) {
logger.error("Exception authenticating user", ex);
return null;
}
return user;
}
protected void updateLoginAttemptsWhenIncorrectLoginAttemptsEnabled(UserAccount account, boolean updateIncorrectLoginCount, protected void updateLoginAttemptsWhenIncorrectLoginAttemptsEnabled(UserAccount account, boolean updateIncorrectLoginCount,
int allowedLoginAttempts) { int allowedLoginAttempts) {
int attemptsMade = account.getLoginAttempts() + 1; int attemptsMade = account.getLoginAttempts() + 1;
@ -2904,8 +3055,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
final Account account = getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account from the userID of the requested user. final Account account = getAccount(getUserAccountById(userId).getAccountId()); //Extracting the Account from the userID of the requested user.
User caller = CallContext.current().getCallingUser(); User caller = CallContext.current().getCallingUser();
preventRootDomainAdminAccessToRootAdminKeys(caller, account);
checkAccess(caller, account); checkAccess(caller, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
Map<String, String> keys = new HashMap<>(); Map<String, String> keys = new HashMap<>();
keys.put("apikey", user.getApiKey()); keys.put("apikey", user.getApiKey());
@ -2969,8 +3120,8 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
} }
Account account = _accountDao.findById(user.getAccountId()); Account account = _accountDao.findById(user.getAccountId());
preventRootDomainAdminAccessToRootAdminKeys(user, account);
checkAccess(caller, null, true, account); checkAccess(caller, null, true, account);
verifyCallerPrivilegeForUserOrAccountOperations(user);
// don't allow updating system user // don't allow updating system user
if (user.getId() == User.UID_SYSTEM) { if (user.getId() == User.UID_SYSTEM) {
@ -3426,7 +3577,7 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
public ConfigKey<?>[] getConfigKeys() { public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {UseSecretKeyInResponse, enableUserTwoFactorAuthentication, return new ConfigKey<?>[] {UseSecretKeyInResponse, enableUserTwoFactorAuthentication,
userTwoFactorAuthenticationDefaultProvider, mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, apiKeyAccess, userTwoFactorAuthenticationDefaultProvider, mandateUserTwoFactorAuthentication, userTwoFactorAuthenticationIssuer, apiKeyAccess,
userAllowMultipleAccounts}; userAllowMultipleAccounts, listOfRoleTypesAllowedForOperationsOfSameRoleType, allowOperationsOnUsersInSameAccount};
} }
public List<UserTwoFactorAuthenticator> getUserTwoFactorAuthenticationProviders() { public List<UserTwoFactorAuthenticator> getUserTwoFactorAuthenticationProviders() {

View File

@ -45,10 +45,12 @@ import com.cloud.storage.StorageManager;
import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VMTemplateZoneDao;
import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDao;
import com.cloud.user.Account; import com.cloud.user.Account;
import com.cloud.user.AccountManagerImpl;
import com.cloud.user.User; import com.cloud.user.User;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.EntityManager;
import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils; import com.cloud.utils.net.NetUtils;
import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.dao.VMInstanceDao;
import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.annotation.dao.AnnotationDao;
@ -57,6 +59,7 @@ import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd;
import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.UpdateDiskOfferingCmd;
import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd; import org.apache.cloudstack.api.command.admin.zone.DeleteZoneCmd;
import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope; import org.apache.cloudstack.engine.subsystem.api.storage.ZoneScope;
import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
@ -192,6 +195,7 @@ public class ConfigurationManagerImplTest {
configurationManagerImplSpy.populateConfigValuesForValidationSet(); configurationManagerImplSpy.populateConfigValuesForValidationSet();
configurationManagerImplSpy.weightBasedParametersForValidation(); configurationManagerImplSpy.weightBasedParametersForValidation();
configurationManagerImplSpy.overProvisioningFactorsForValidation(); configurationManagerImplSpy.overProvisioningFactorsForValidation();
configurationManagerImplSpy.populateConfigKeysAllowedOnlyForDefaultAdmin();
ReflectionTestUtils.setField(configurationManagerImplSpy, "templateZoneDao", vmTemplateZoneDao); ReflectionTestUtils.setField(configurationManagerImplSpy, "templateZoneDao", vmTemplateZoneDao);
ReflectionTestUtils.setField(configurationManagerImplSpy, "annotationDao", annotationDao); ReflectionTestUtils.setField(configurationManagerImplSpy, "annotationDao", annotationDao);
@ -884,4 +888,46 @@ public class ConfigurationManagerImplTest {
Pair<Configuration, String> result = configurationManagerImplSpy.resetConfiguration(cmd); Pair<Configuration, String> result = configurationManagerImplSpy.resetConfiguration(cmd);
Assert.assertEquals(".85", result.second()); Assert.assertEquals(".85", result.second());
} }
@Test
public void testValidateConfigurationAllowedOnlyForDefaultAdmin_withAdminUser_shouldNotThrowException() {
CallContext callContext = mock(CallContext.class);
when(callContext.getCallingUserId()).thenReturn(User.UID_ADMIN);
try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
when(CallContext.current()).thenReturn(callContext);
configurationManagerImplSpy.validateConfigurationAllowedOnlyForDefaultAdmin(AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key(), "Admin");
}
}
@Test
public void testValidateConfigurationAllowedOnlyForDefaultAdmin_withNonAdminUser_shouldThrowException() {
CallContext callContext = mock(CallContext.class);
when(callContext.getCallingUserId()).thenReturn(123L);
try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
when(CallContext.current()).thenReturn(callContext);
Assert.assertThrows(CloudRuntimeException.class, () ->
configurationManagerImplSpy.validateConfigurationAllowedOnlyForDefaultAdmin(AccountManagerImpl.allowOperationsOnUsersInSameAccount.key(), "Admin")
);
}
}
@Test
public void testValidateConfigurationAllowedOnlyForDefaultAdmin_withNonRestrictedKey_shouldNotThrowException() {
CallContext callContext = mock(CallContext.class);
try (MockedStatic<CallContext> ignored = Mockito.mockStatic(CallContext.class)) {
when(CallContext.current()).thenReturn(callContext);
configurationManagerImplSpy.validateConfigurationAllowedOnlyForDefaultAdmin("some.other.config.key", "Admin");
}
}
@Test(expected = CloudRuntimeException.class)
public void testValidateConfigurationAllowedOnlyForDefaultAdmin_withValidConfigNameAndInvalidValue_shouldThrowException() {
CallContext callContext = mock(CallContext.class);
try (MockedStatic<CallContext> mockedCallContext = Mockito.mockStatic(CallContext.class)) {
mockedCallContext.when(CallContext::current).thenReturn(callContext);
when(callContext.getCallingUserId()).thenReturn(User.UID_ADMIN);
String invalidValue = "Admin, SuperUser";
configurationManagerImplSpy.validateConfigurationAllowedOnlyForDefaultAdmin(AccountManagerImpl.listOfRoleTypesAllowedForOperationsOfSameRoleType.key(), invalidValue);
}
}
} }

View File

@ -16,6 +16,7 @@
// under the License. // under the License.
package com.cloud.user; package com.cloud.user;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.nullable;
import java.net.InetAddress; import java.net.InetAddress;
@ -126,6 +127,9 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
@Mock @Mock
ConfigKey<Boolean> enableUserTwoFactorAuthenticationMock; ConfigKey<Boolean> enableUserTwoFactorAuthenticationMock;
@Mock
ConfigKey<Boolean> allowOperationsOnUsersInSameAccountMock;
@Mock @Mock
RoleService roleService; RoleService roleService;
@ -133,6 +137,9 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
public void setUp() throws Exception { public void setUp() throws Exception {
enableUserTwoFactorAuthenticationMock = Mockito.mock(ConfigKey.class); enableUserTwoFactorAuthenticationMock = Mockito.mock(ConfigKey.class);
accountManagerImpl.enableUserTwoFactorAuthentication = enableUserTwoFactorAuthenticationMock; accountManagerImpl.enableUserTwoFactorAuthentication = enableUserTwoFactorAuthenticationMock;
allowOperationsOnUsersInSameAccountMock = Mockito.mock(ConfigKey.class);
accountManagerImpl.allowOperationsOnUsersInSameAccount = allowOperationsOnUsersInSameAccountMock;
} }
@Before @Before
@ -143,6 +150,8 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.doReturn(accountMockId).when(userVoMock).getAccountId(); Mockito.doReturn(accountMockId).when(userVoMock).getAccountId();
Mockito.doReturn(userVoIdMock).when(userVoMock).getId(); Mockito.doReturn(userVoIdMock).when(userVoMock).getId();
Mockito.lenient().doNothing().when(accountManagerImpl).checkRoleEscalation(accountMock, accountMock);
} }
@Test @Test
@ -193,6 +202,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.when(_sshKeyPairDao.remove(Mockito.anyLong())).thenReturn(true); Mockito.when(_sshKeyPairDao.remove(Mockito.anyLong())).thenReturn(true);
Mockito.when(userDataDao.removeByAccountId(Mockito.anyLong())).thenReturn(222); Mockito.when(userDataDao.removeByAccountId(Mockito.anyLong())).thenReturn(222);
Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong()); Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations((Account) any());
Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l)); Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l));
// assert that this was a clean delete // assert that this was a clean delete
@ -213,6 +223,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.lenient().when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domain); Mockito.lenient().when(_domainMgr.getDomain(Mockito.anyLong())).thenReturn(domain);
Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class))).thenReturn(true); Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), Mockito.any(Domain.class))).thenReturn(true);
Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong()); Mockito.doNothing().when(accountManagerImpl).deleteWebhooksForAccount(Mockito.anyLong());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations((Account) any());
Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l)); Assert.assertTrue(accountManagerImpl.deleteUserAccount(42l));
// assert that this was NOT a clean delete // assert that this was NOT a clean delete
@ -247,6 +258,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.doReturn(2L).when(accountVoMock).getId(); Mockito.doReturn(2L).when(accountVoMock).getId();
Mockito.doReturn(true).when(accountManagerImpl).isDeleteNeeded(Mockito.any(), Mockito.anyLong(), Mockito.any()); Mockito.doReturn(true).when(accountManagerImpl).isDeleteNeeded(Mockito.any(), Mockito.anyLong(), Mockito.any());
Mockito.doReturn(new ArrayList<Long>()).when(_projectAccountDao).listAdministratedProjectIds(Mockito.anyLong()); Mockito.doReturn(new ArrayList<Long>()).when(_projectAccountDao).listAdministratedProjectIds(Mockito.anyLong());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations((Account) any());
accountManagerImpl.deleteUserAccount(accountId); accountManagerImpl.deleteUserAccount(accountId);
} }
@ -284,6 +296,7 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.doReturn(2L).when(userVoMock).getId(); Mockito.doReturn(2L).when(userVoMock).getId();
Mockito.doNothing().when(accountManagerImpl).checkAccountAndAccess(Mockito.any(), Mockito.any()); Mockito.doNothing().when(accountManagerImpl).checkAccountAndAccess(Mockito.any(), Mockito.any());
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations(userVoMock);
accountManagerImpl.deleteUser(cmd); accountManagerImpl.deleteUser(cmd);
} }
} }
@ -350,7 +363,6 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
// Queried account - admin account // Queried account - admin account
AccountVO adminAccountMock = Mockito.mock(AccountVO.class); AccountVO adminAccountMock = Mockito.mock(AccountVO.class);
Mockito.when(adminAccountMock.getAccountId()).thenReturn(2L);
Mockito.when(_accountDao.findByIdIncludingRemoved(2L)).thenReturn(adminAccountMock); Mockito.when(_accountDao.findByIdIncludingRemoved(2L)).thenReturn(adminAccountMock);
Mockito.lenient().when(accountService.isRootAdmin(2L)).thenReturn(true); Mockito.lenient().when(accountService.isRootAdmin(2L)).thenReturn(true);
Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class), Mockito.lenient().when(securityChecker.checkAccess(Mockito.any(Account.class),
@ -398,6 +410,12 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
@Test @Test
public void updateUserTestTimeZoneAndEmailNull() { public void updateUserTestTimeZoneAndEmailNull() {
Mockito.when(userVoMock.getAccountId()).thenReturn(10L);
Mockito.doReturn(accountMock).when(accountManagerImpl).getAccount(10L);
Mockito.when(accountMock.getAccountId()).thenReturn(10L);
Mockito.doReturn(false).when(accountManagerImpl).isRootAdmin(10L);
Mockito.lenient().when(accountManagerImpl.getRoleType(Mockito.eq(accountMock))).thenReturn(RoleType.User);
prepareMockAndExecuteUpdateUserTest(0); prepareMockAndExecuteUpdateUserTest(0);
} }
@ -405,6 +423,11 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
public void updateUserTestTimeZoneAndEmailNotNull() { public void updateUserTestTimeZoneAndEmailNotNull() {
Mockito.when(UpdateUserCmdMock.getEmail()).thenReturn("email"); Mockito.when(UpdateUserCmdMock.getEmail()).thenReturn("email");
Mockito.when(UpdateUserCmdMock.getTimezone()).thenReturn("timezone"); Mockito.when(UpdateUserCmdMock.getTimezone()).thenReturn("timezone");
Mockito.when(userVoMock.getAccountId()).thenReturn(10L);
Mockito.doReturn(accountMock).when(accountManagerImpl).getAccount(10L);
Mockito.when(accountMock.getAccountId()).thenReturn(10L);
Mockito.doReturn(false).when(accountManagerImpl).isRootAdmin(10L);
Mockito.lenient().when(accountManagerImpl.getRoleType(Mockito.eq(accountMock))).thenReturn(RoleType.User);
prepareMockAndExecuteUpdateUserTest(1); prepareMockAndExecuteUpdateUserTest(1);
} }
@ -422,14 +445,17 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
Mockito.doReturn(true).when(userDaoMock).update(Mockito.anyLong(), Mockito.eq(userVoMock)); Mockito.doReturn(true).when(userDaoMock).update(Mockito.anyLong(), Mockito.eq(userVoMock));
Mockito.doReturn(Mockito.mock(UserAccountVO.class)).when(userAccountDaoMock).findById(Mockito.anyLong()); Mockito.doReturn(Mockito.mock(UserAccountVO.class)).when(userAccountDaoMock).findById(Mockito.anyLong());
Mockito.doNothing().when(accountManagerImpl).checkAccess(nullable(User.class), nullable(Account.class));
accountManagerImpl.updateUser(UpdateUserCmdMock); accountManagerImpl.updateUser(UpdateUserCmdMock);
Mockito.lenient().doNothing().when(accountManagerImpl).checkRoleEscalation(accountMock, accountMock);
InOrder inOrder = Mockito.inOrder(userVoMock, accountManagerImpl, userDaoMock, userAccountDaoMock); InOrder inOrder = Mockito.inOrder(userVoMock, accountManagerImpl, userDaoMock, userAccountDaoMock);
inOrder.verify(accountManagerImpl).retrieveAndValidateUser(UpdateUserCmdMock); inOrder.verify(accountManagerImpl).retrieveAndValidateUser(UpdateUserCmdMock);
inOrder.verify(accountManagerImpl).validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
inOrder.verify(accountManagerImpl).retrieveAndValidateAccount(userVoMock); inOrder.verify(accountManagerImpl).retrieveAndValidateAccount(userVoMock);
inOrder.verify(accountManagerImpl).validateAndUpdateApiAndSecretKeyIfNeeded(UpdateUserCmdMock, userVoMock);
inOrder.verify(accountManagerImpl).validateAndUpdateFirstNameIfNeeded(UpdateUserCmdMock, userVoMock); inOrder.verify(accountManagerImpl).validateAndUpdateFirstNameIfNeeded(UpdateUserCmdMock, userVoMock);
inOrder.verify(accountManagerImpl).validateAndUpdateLastNameIfNeeded(UpdateUserCmdMock, userVoMock); inOrder.verify(accountManagerImpl).validateAndUpdateLastNameIfNeeded(UpdateUserCmdMock, userVoMock);
@ -1341,8 +1367,8 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
accountManagerImpl.validateRoleChange(account, newRole, caller); accountManagerImpl.validateRoleChange(account, newRole, caller);
} }
@Test @Test
public void checkIfAccountManagesProjectsTestNotThrowExceptionWhenTheAccountIsNotAProjectAdministrator() { public void checkIfAccountManagesProjectsTestNotThrowExceptionWhenTheAccountIsNotAProjectAdministrator() {
long accountId = 1L; long accountId = 1L;
List<Long> managedProjectIds = new ArrayList<>(); List<Long> managedProjectIds = new ArrayList<>();
@ -1479,4 +1505,74 @@ public class AccountManagerImplTest extends AccountManagetImplTestBase {
accountManagerImpl.assertUserNotAlreadyInDomain(existingUser, originalAccount); accountManagerImpl.assertUserNotAlreadyInDomain(existingUser, originalAccount);
} }
@Test
public void testCheckCallerRoleTypeAllowedToUpdateUserSameAccount() {
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(accountMock);
Mockito.lenient().when(accountManagerImpl.getRoleType(Mockito.eq(accountMock))).thenReturn(RoleType.DomainAdmin);
accountManagerImpl.checkCallerRoleTypeAllowedForUserOrAccountOperations(accountMock, userVoMock);
}
@Test(expected = PermissionDeniedException.class)
public void testCheckCallerRoleTypeAllowedToUpdateUserLowerAccountRoleType() {
Account callingAccount = Mockito.mock(Account.class);
Mockito.lenient().when(callingAccount.getAccountId()).thenReturn(2L);
Mockito.lenient().doReturn(callingAccount).when(accountManagerImpl).getAccount(2L);
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(callingAccount);
Mockito.lenient().when(accountManagerImpl.getRoleType(Mockito.eq(callingAccount))).thenReturn(RoleType.DomainAdmin);
Mockito.lenient().when(accountManagerImpl.getRoleType(Mockito.eq(accountMock))).thenReturn(RoleType.Admin);
accountManagerImpl.checkCallerRoleTypeAllowedForUserOrAccountOperations(accountMock, userVoMock);
}
@Test
public void testcheckCallerApiPermissionsForUserOperationsRootAdminSameCaller() {
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(accountMock);
Mockito.when(accountMock.getId()).thenReturn(2L);
Mockito.doReturn(true).when(accountManagerImpl).isRootAdmin(2L);
accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock);
}
@Test(expected = PermissionDeniedException.class)
public void testcheckCallerApiPermissionsForUserOperationsRootAdminDifferentAccount() {
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(callingAccount);
Mockito.lenient().when(callingAccount.getAccountId()).thenReturn(3L);
Mockito.lenient().doReturn(callingAccount).when(accountManagerImpl).getAccount(3L);
Mockito.lenient().doReturn(false).when(accountManagerImpl).isRootAdmin(3L);
Mockito.when(accountMock.getAccountId()).thenReturn(2L);
Mockito.doReturn(true).when(accountManagerImpl).isRootAdmin(2L);
accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock);
}
@Test
public void testcheckCallerApiPermissionsForUserOperationsAllowedApis() {
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(callingAccount);
Mockito.lenient().when(callingAccount.getAccountId()).thenReturn(3L);
Mockito.lenient().doReturn(callingAccount).when(accountManagerImpl).getAccount(3L);
Mockito.lenient().doReturn(false).when(accountManagerImpl).isRootAdmin(3L);
Mockito.when(accountMock.getAccountId()).thenReturn(2L);
Mockito.doReturn(false).when(accountManagerImpl).isRootAdmin(2L);
Mockito.lenient().doNothing().when(accountManagerImpl).checkRoleEscalation(callingAccount, accountMock);
accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock);
}
@Test(expected = PermissionDeniedException.class)
public void testcheckCallerApiPermissionsForUserOperationsNotAllowedApis() {
Mockito.lenient().when(accountManagerImpl.getCurrentCallingAccount()).thenReturn(callingAccount);
Mockito.lenient().when(callingAccount.getAccountId()).thenReturn(3L);
Mockito.lenient().doReturn(callingAccount).when(accountManagerImpl).getAccount(3L);
Mockito.lenient().doReturn(false).when(accountManagerImpl).isRootAdmin(3L);
Mockito.when(accountMock.getAccountId()).thenReturn(2L);
Mockito.doReturn(false).when(accountManagerImpl).isRootAdmin(2L);
Mockito.lenient().doThrow(PermissionDeniedException.class).when(accountManagerImpl).checkRoleEscalation(callingAccount, accountMock);
accountManagerImpl.checkCallerApiPermissionsForUserOrAccountOperations(accountMock);
}
} }

View File

@ -192,6 +192,7 @@ public class AccountManagerImplVolumeDeleteEventTest extends AccountManagetImplT
public void destroyedVMRootVolumeUsageEvent() public void destroyedVMRootVolumeUsageEvent()
throws SecurityException, IllegalArgumentException, ReflectiveOperationException, AgentUnavailableException, ConcurrentOperationException, CloudException { throws SecurityException, IllegalArgumentException, ReflectiveOperationException, AgentUnavailableException, ConcurrentOperationException, CloudException {
lenient().doReturn(vm).when(_vmMgr).destroyVm(nullable(Long.class), nullable(Boolean.class)); lenient().doReturn(vm).when(_vmMgr).destroyVm(nullable(Long.class), nullable(Boolean.class));
Mockito.doNothing().when(accountManagerImpl).verifyCallerPrivilegeForUserOrAccountOperations((Account) any());
List<UsageEventVO> emittedEvents = deleteUserAccountRootVolumeUsageEvents(true); List<UsageEventVO> emittedEvents = deleteUserAccountRootVolumeUsageEvents(true);
Assert.assertEquals(0, emittedEvents.size()); Assert.assertEquals(0, emittedEvents.size());
} }

View File

@ -210,6 +210,9 @@ public class AccountManagetImplTestBase {
@Mock @Mock
RoutedIpv4Manager routedIpv4Manager; RoutedIpv4Manager routedIpv4Manager;
@Mock
Account accountMock;
@Before @Before
public void setup() { public void setup() {
accountManagerImpl.setUserAuthenticators(Arrays.asList(userAuthenticator)); accountManagerImpl.setUserAuthenticators(Arrays.asList(userAuthenticator));

View File

@ -503,4 +503,8 @@ public class MockAccountManagerImpl extends ManagerBase implements Manager, Acco
public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) { public UserAccount clearUserTwoFactorAuthenticationInSetupStateOnLogin(UserAccount user) {
return null; return null;
} }
@Override
public void verifyCallerPrivilegeForUserOrAccountOperations(Account userAccount) {
}
} }