Skip to content

Commit 6aa85a7

Browse files
authored
Merge branch '4.22' into propagate-stopanswer-errors
2 parents 0777372 + a5954f9 commit 6aa85a7

231 files changed

Lines changed: 7492 additions & 1170 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/linters/codespell.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ environmnet
187187
equivalant
188188
erro
189189
erronous
190+
errorprone
190191
everthing
191192
everytime
192193
excute

agent/src/main/java/com/cloud/agent/mockvm/MockVmMgr.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ public void freeVncPort(int port) {
249249
public MockVm createVmFromSpec(VirtualMachineTO vmSpec) {
250250
String vmName = vmSpec.getName();
251251
long ramSize = vmSpec.getMinRam();
252-
int utilizationPercent = randSeed.nextInt() % 100;
252+
int utilizationPercent = randSeed.nextInt(100);
253253
MockVm vm = null;
254254

255255
synchronized (this) {

api/src/main/java/org/apache/cloudstack/acl/APIChecker.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,22 @@
1717
package org.apache.cloudstack.acl;
1818

1919
import com.cloud.exception.PermissionDeniedException;
20+
import com.cloud.exception.RequestLimitException;
2021
import com.cloud.user.Account;
2122
import com.cloud.user.User;
2223
import com.cloud.utils.component.Adapter;
2324

25+
import java.util.ArrayList;
2426
import java.util.List;
2527

28+
import org.apache.logging.log4j.LogManager;
29+
import org.apache.logging.log4j.Logger;
30+
2631
/**
2732
* APICheckers is designed to verify the ownership of resources and to control the access to APIs.
2833
*/
2934
public interface APIChecker extends Adapter {
35+
Logger LOGGER = LogManager.getLogger(APIChecker.class);
3036
// Interface for checking access for a role using apiname
3137
// If true, apiChecker has checked the operation
3238
// If false, apiChecker is unable to handle the operation or not implemented
@@ -42,5 +48,27 @@ public interface APIChecker extends Adapter {
4248
* @return the list of allowed apis for the given user
4349
*/
4450
List<String> getApisAllowedToUser(Role role, User user, List<String> apiNames) throws PermissionDeniedException;
51+
52+
default List<String> getApisAllowedToAccount(Account account, List<String> apiNames) {
53+
List<String> allowedApis = new ArrayList<>();
54+
for (String apiName : apiNames) {
55+
try {
56+
checkAccess(account, apiName);
57+
allowedApis.add(apiName);
58+
} catch (RequestLimitException e) {
59+
// Non-ACL failure (e.g. rate limiting) should not be treated as simple "not allowed".
60+
// Propagate as unchecked so callers are aware of the failure.
61+
throw new RuntimeException("Failed to check access for API [" + apiName + "] due to request limits", e);
62+
} catch (PermissionDeniedException e) {
63+
LOGGER.trace("Account [" + account + "] is not allowed to access API [" + apiName + "]");
64+
}
65+
}
66+
return allowedApis;
67+
}
68+
4569
boolean isEnabled();
70+
71+
default void refreshRoleCacheOnPermissionsChange(Role role) {
72+
// Only applicable for dynamic role based checkers
73+
}
4674
}

api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,18 @@
1717

1818
package org.apache.cloudstack.api;
1919

20+
import java.util.Locale;
21+
import java.util.regex.Pattern;
22+
23+
import org.apache.commons.lang3.StringUtils;
24+
25+
import com.cloud.exception.InvalidParameterValueException;
26+
import com.cloud.utils.UuidUtils;
27+
2028
public enum ApiArgValidator {
2129
/**
22-
* Validates if the parameter is null or empty with the method {@link Strings#isNullOrEmpty(String)}.
30+
* Validates if the parameter is null or empty with the method {@link StringUtils#isEmpty(CharSequence)}.
31+
* Validation is currently done in the method ParamProcessWorker#validateNonEmptyString(String, String).
2332
*/
2433
NotNullOrEmpty,
2534

@@ -29,12 +38,72 @@ public enum ApiArgValidator {
2938
PositiveNumber,
3039

3140
/**
32-
* Validates if the parameter is an UUID with the method {@link UuidUtils#isUuid(String)}.
41+
* Validates if the parameter is a UUID with the method {@link UuidUtils#isUuid(String)}.
42+
* Validation is currently done in the method ParamProcessWorker#validateUuidString(String, String).
3343
*/
3444
UuidString,
3545

3646
/**
3747
* Validates if the parameter is a valid RFC Compliance domain name.
3848
*/
3949
RFCComplianceDomainName,
50+
51+
/**
52+
* Validates command option strings to avoid unsafe/code-like content.
53+
*/
54+
SafeCommandOptions((param, annotation) -> {
55+
if (BaseCmd.CommandType.STRING.equals(annotation.type())) {
56+
validateSafeCommandOptions(param, annotation.name());
57+
}
58+
});
59+
60+
private static final Pattern SAFE_COMMAND_OPTIONS_PATTERN = Pattern.compile("^[A-Za-z0-9,._=:/+\\-\\s]*$");
61+
62+
private static final String[] UNSAFE_TOKENS = {
63+
"$(", "`", "&&", "||", ";", "|", ">", "<"
64+
};
65+
66+
private final ValidationRule rule;
67+
68+
ApiArgValidator() {
69+
this(null);
70+
}
71+
72+
ApiArgValidator(ValidationRule rule) {
73+
this.rule = rule;
74+
}
75+
76+
public void validate(final Object paramObj, final Parameter annotation) {
77+
if (rule != null) {
78+
rule.validate(paramObj, annotation);
79+
}
80+
}
81+
82+
private static void validateSafeCommandOptions(final Object param, final String argName) {
83+
final String value = String.valueOf(param);
84+
if (StringUtils.isBlank(value)) {
85+
return;
86+
}
87+
88+
if (!SAFE_COMMAND_OPTIONS_PATTERN.matcher(value).matches()) {
89+
throwInvalidParameterValueException(argName, "contains unsupported or unsafe characters");
90+
}
91+
92+
final String normalized = value.toLowerCase(Locale.ROOT);
93+
for (String token : UNSAFE_TOKENS) {
94+
if (normalized.contains(token)) {
95+
throwInvalidParameterValueException(argName, "contains code-like or unsafe content");
96+
}
97+
}
98+
}
99+
100+
private static void throwInvalidParameterValueException(final String argName, final String customMsg) {
101+
throw new InvalidParameterValueException(String.format("Invalid value provided for API arg: %s%s", argName,
102+
StringUtils.isBlank(customMsg) ? "" : " - " + customMsg));
103+
}
104+
105+
@FunctionalInterface
106+
interface ValidationRule {
107+
void validate(Object paramObj, Parameter annotation);
108+
}
40109
}

api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ public Boolean getAllowUserDrivenBackups() {
8383
public void execute() {
8484
try {
8585
if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null) {
86-
throw new InvalidParameterValueException(String.format("Can't update Backup Offering [id: %s] because there are no parameters to be updated, at least one of the",
87-
"following should be informed: name, description or allowUserDrivenBackups.", id));
86+
throw new InvalidParameterValueException(String.format("Can't update Backup Offering [id: %s] because there are no parameters to be updated," +
87+
" at least one of the following should be passed: name, description or allowUserDrivenBackups.", id));
8888
}
8989

9090
BackupOffering result = backupManager.updateBackupOffering(this);

api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ResetVMPasswordCmdByAdmin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@
2727
@APICommand(name = "resetPasswordForVirtualMachine", responseObject=UserVmResponse.class, description = "Resets the password for Instance. " +
2828
"The Instance must be in a \"Stopped\" state and the Template must already " +
2929
"support this feature for this command to take effect. [async]", responseView = ResponseView.Full, entityType = {VirtualMachine.class},
30-
requestHasSensitiveInfo = false, responseHasSensitiveInfo = true)
30+
requestHasSensitiveInfo = true, responseHasSensitiveInfo = true)
3131
public class ResetVMPasswordCmdByAdmin extends ResetVMPasswordCmd implements AdminCmd {}

api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.apache.commons.collections.MapUtils;
2323

2424
import org.apache.cloudstack.acl.RoleType;
25+
import org.apache.cloudstack.api.ACL;
2526
import org.apache.cloudstack.api.APICommand;
2627
import org.apache.cloudstack.api.ApiCommandResourceType;
2728
import org.apache.cloudstack.api.ApiConstants;
@@ -106,6 +107,7 @@ public class CreateAutoScaleVmProfileCmd extends BaseAsyncCreateCmd {
106107
since = "4.18.0")
107108
private String userData;
108109

110+
@ACL
109111
@Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, description = "the ID of the Userdata", since = "4.18.1")
110112
private Long userDataId;
111113

api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public class UpdateAutoScaleVmProfileCmd extends BaseAsyncCustomIdCmd {
101101
since = "4.18.0")
102102
private String userData;
103103

104+
@ACL
104105
@Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, description = "the ID of the userdata",
105106
since = "4.18.1")
106107
private Long userDataId;

api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.cloud.utils.StringUtils;
2121
import org.apache.cloudstack.acl.RoleType;
2222
import org.apache.cloudstack.api.APICommand;
23+
import org.apache.cloudstack.api.ApiArgValidator;
2324
import org.apache.cloudstack.api.ApiConstants;
2425
import org.apache.cloudstack.api.ApiErrorCode;
2526
import org.apache.cloudstack.api.BaseCmd;
@@ -57,7 +58,8 @@ public class AddBackupRepositoryCmd extends BaseCmd {
5758
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "backup repository provider")
5859
private String provider;
5960

60-
@Parameter(name = ApiConstants.MOUNT_OPTIONS, type = CommandType.STRING, description = "shared storage mount options")
61+
@Parameter(name = ApiConstants.MOUNT_OPTIONS, type = CommandType.STRING, description = "shared storage mount options",
62+
validations = {ApiArgValidator.SafeCommandOptions})
6163
private String mountOptions;
6264

6365
@Parameter(name = ApiConstants.ZONE_ID,

api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
package org.apache.cloudstack.api.command.user.backup.repository;
1919

20+
import javax.inject.Inject;
21+
2022
import org.apache.cloudstack.acl.RoleType;
2123
import org.apache.cloudstack.api.APICommand;
24+
import org.apache.cloudstack.api.ApiArgValidator;
2225
import org.apache.cloudstack.api.ApiConstants;
2326
import org.apache.cloudstack.api.ApiErrorCode;
2427
import org.apache.cloudstack.api.BaseCmd;
@@ -29,8 +32,6 @@
2932
import org.apache.cloudstack.backup.BackupRepositoryService;
3033
import org.apache.cloudstack.context.CallContext;
3134

32-
import javax.inject.Inject;
33-
3435
@APICommand(name = "updateBackupRepository",
3536
description = "Update a backup repository",
3637
responseObject = BackupRepositoryResponse.class, since = "4.22.0",
@@ -53,7 +54,8 @@ public class UpdateBackupRepositoryCmd extends BaseCmd {
5354
@Parameter(name = ApiConstants.ADDRESS, type = CommandType.STRING, description = "address of the backup repository")
5455
private String address;
5556

56-
@Parameter(name = ApiConstants.MOUNT_OPTIONS, type = CommandType.STRING, description = "shared storage mount options")
57+
@Parameter(name = ApiConstants.MOUNT_OPTIONS, type = CommandType.STRING, description = "shared storage mount options",
58+
validations = {ApiArgValidator.SafeCommandOptions})
5759
private String mountOptions;
5860

5961
@Parameter(name = ApiConstants.CROSS_ZONE_INSTANCE_CREATION, type = CommandType.BOOLEAN, description = "backups in this repository can be used to create Instances on all Zones")

0 commit comments

Comments
 (0)