Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 43 additions & 19 deletions server/src/main/java/com/cloud/user/AccountManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,12 @@
import javax.inject.Inject;
import javax.naming.ConfigurationException;

import com.cloud.serializer.GsonHelper;
import com.cloud.user.dao.AccountDao;
import com.cloud.user.dao.SSHKeyPairDao;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.user.dao.UserDao;
import com.google.gson.reflect.TypeToken;
import org.apache.cloudstack.acl.APIChecker;
import org.apache.cloudstack.acl.ApiKeyPairManagerImpl;
import org.apache.cloudstack.acl.ApiKeyPairPermissionVO;
Expand Down Expand Up @@ -101,6 +103,7 @@
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.kms.KMSManager;
Expand Down Expand Up @@ -3274,17 +3277,17 @@ public ListResponse<ApiKeyPairResponse> listKeys(ListUserKeysCmd cmd) {
List<ApiKeyPairResponse> responses = new ArrayList<>();

if (cmd.getKeyId() != null || cmd.getApiKeyFilter() != null) {
fetchOnlyOneKeyPair(responses, cmd);
populateSingleKeyPairResponse(responses, cmd);
finalResponse.setResponses(responses);
return finalResponse;
}

Integer total = fetchMultipleKeyPairs(responses, cmd);
finalResponse.setResponses(responses, total);
populateMultipleKeyPairsResponse(responses, cmd);
finalResponse.setResponses(responses);
return finalResponse;
}

private void fetchOnlyOneKeyPair(List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
private void populateSingleKeyPairResponse(List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
ApiKeyPair keyPair;
if (cmd.getKeyId() != null) {
keyPair = _accountService.getKeyPairById(cmd.getKeyId());
Expand All @@ -3308,7 +3311,7 @@ private void validateAccessToApiKey(ApiKeyPair keyPair) {
_accountService.validateCallingUserHasAccessToDesiredUser(keyPair.getUserId());
}

private Integer fetchMultipleKeyPairs(List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
private void populateMultipleKeyPairsResponse(List<ApiKeyPairResponse> responses, ListUserKeysCmd cmd) {
List<Long> users;
if (cmd.getUserId() != null) {
_accountService.validateCallingUserHasAccessToDesiredUser(cmd.getUserId());
Expand All @@ -3325,8 +3328,6 @@ private Integer fetchMultipleKeyPairs(List<ApiKeyPairResponse> responses, ListUs
addKeypairResponse(keyPair, responses, cmd);
removeApiKeyPairIfExpired(keyPair);
});

return keyPairs.second();
}

@Override
Expand Down Expand Up @@ -3367,24 +3368,29 @@ private Boolean isAccessingKeypairSuperset(ApiKeyPair accessedKeyPair, BaseCmd c
@Override
public String getAccessingApiKey(BaseCmd cmd) {
try {
if (cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd) cmd).getJob().toString().contains("\"signature\"")) {
return parseApiKeyFromAsyncJob((BaseAsyncCmd) cmd);
Map<String, String> requestPayload = cmd.getFullUrlParams();

if (cmd instanceof BaseAsyncCmd && ((BaseAsyncCmd) cmd).getJob() instanceof AsyncJobVO) {
String asyncJobPayload = ((AsyncJobVO) ((BaseAsyncCmd) cmd).getJob()).getCmdInfo();
requestPayload = GsonHelper.getGson().fromJson(asyncJobPayload, new TypeToken<HashMap<String, String>>() {}.getType());
}
boolean accessedByApiKey = cmd.getFullUrlParams().containsKey(ApiConstants.SIGNATURE);
String accessingApiKey = cmd.getFullUrlParams().get("apiKey");

boolean accessedByApiKey = requestPayload.keySet().stream().anyMatch(ApiConstants.SIGNATURE::equalsIgnoreCase);
if (accessedByApiKey) {
return accessingApiKey;
String apiKey = requestPayload.entrySet().stream()
.filter(e -> ApiConstants.API_KEY.equalsIgnoreCase(e.getKey()))
.map(Map.Entry::getValue).findFirst().orElse(null);
if (apiKey != null) {
logger.info("Request's API key is [{}].", apiKey);
return apiKey;
}
}
} catch (NullPointerException e) {
logger.info("Accessing API through session.");
logger.warn("Unable to identify request API key due to: {}.", e);
}
return null;
}

private String parseApiKeyFromAsyncJob(BaseAsyncCmd cmd) {
String jobString = cmd.getJob().toString();
int indexOfApiKey = jobString.indexOf("apiKey") + 9;
return jobString.substring(indexOfApiKey, jobString.indexOf("\"", indexOfApiKey));
logger.info("Request's signature or API key were not identified; assuming it has been authenticated via session.");
return null;
}

private Boolean isApiKeySupersetOfPermission(List<RolePermissionEntity> baseKeyPairPermissions, List<RolePermissionEntity> comparedPermissions) {
Expand Down Expand Up @@ -3603,6 +3609,14 @@ private ApiKeyPairVO validateAndPersistKeyPairAndPermissions(Account account, Ap
permissions.add(new ApiKeyPairPermissionVO(0, rule, rulePermission, ruleDescription));
}

if (permissions.isEmpty() && accessingApiKey != null && doesKeyPairHaveExplicitPermissions(accessingApiKey)) {
logger.debug("No rules were specified for the new API key pair. Since the accessing API key [{}]" +
" has explicit permissions, these permissions will be defined as the rule set for the new pair.", accessingApiKey);
permissions = allPermissions.stream().map(permission -> (
new ApiKeyPairPermissionVO(0, permission.getRule().getRuleString(), permission.getPermission(), permission.getDescription())
)).collect(Collectors.toList());
}

if (!isApiKeySupersetOfPermission(allPermissions, permissions)) {
throw new InvalidParameterValueException(String.format("The key pair being created has a bigger set of permissions than the account [%s] " +
"that owns it. This is not allowed.", account.getUuid()));
Expand All @@ -3617,6 +3631,16 @@ private ApiKeyPairVO validateAndPersistKeyPairAndPermissions(Account account, Ap
return savedApiKeyPair;
}

private boolean doesKeyPairHaveExplicitPermissions(String apiKey) {
ApiKeyPair apiKeyPair = keyPairManager.findByApiKey(apiKey);
if (apiKeyPair == null) {
logger.info("Unable to find API key pair entity with the API key [{}].", apiKey);
return false;
}

return !apiKeyPairPermissionsDao.findAllByApiKeyPairId(apiKeyPair.getId()).isEmpty();
}

@Override
public List<RolePermissionEntity> getAllKeypairPermissions(String apiKey) {
if (apiKey == null) {
Expand Down
4 changes: 3 additions & 1 deletion server/src/main/java/com/cloud/vm/UserVmManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -3683,7 +3683,9 @@ public UserVm destroyVm(DestroyVMCmd cmd, boolean checkExpunge) throws ResourceU
if (checkExpunge && expunge) {
String jobParamsString = ((AsyncJobVO) cmd.getJob()).getCmdInfo();
HashMap<String,String> jobParams = GsonHelper.getGson().fromJson(jobParamsString, jobParamsType);
String apiKey = jobParams.get("apiKey");
String apiKey = jobParams.entrySet().stream()
.filter(e -> ApiConstants.API_KEY.equalsIgnoreCase(e.getKey()))
.map(Map.Entry::getValue).findFirst().orElse(null);
checkExpungeVmPermission(ctx.getCallingAccount(), apiKey);
}

Expand Down
Loading