Skip to content

Commit a6d9fa6

Browse files
DaanHooglandDaan Hoogland
andauthored
Role escalation prevention (apache#5879)
* prevent role access escallation * hierarchy issue fixed * create api list in account manager for checking new account access * full api list check * strange role restriction removed for BareMetal * add role check on upfdate account as well * more selective use of api checkers * error msg and var name Co-authored-by: Daan Hoogland <dahn@onecht.net>
1 parent 4ffb949 commit a6d9fa6

File tree

15 files changed

+237
-27
lines changed

15 files changed

+237
-27
lines changed

api/src/main/java/com/cloud/user/AccountService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.apache.cloudstack.acl.ControlledEntity;
2222
import org.apache.cloudstack.acl.RoleType;
2323
import org.apache.cloudstack.acl.SecurityChecker.AccessType;
24+
import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd;
2425
import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd;
2526
import org.apache.cloudstack.api.command.admin.user.RegisterCmd;
2627
import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd;
@@ -39,8 +40,7 @@ public interface AccountService {
3940
* Creates a new user and account, stores the password as is so encrypted passwords are recommended.
4041
* @return the user if created successfully, null otherwise
4142
*/
42-
UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, short accountType, Long roleId, Long domainId,
43-
String networkDomain, Map<String, String> details, String accountUUID, String userUUID);
43+
UserAccount createUserAccount(CreateAccountCmd accountCmd);
4444

4545
UserAccount createUserAccount(String userName, String password, String firstName, String lastName, String email, String timezone, String accountName, short accountType, Long roleId, Long domainId,
4646
String networkDomain, Map<String, String> details, String accountUUID, String userUUID, User.Source source);

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

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

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

@@ -27,4 +28,6 @@ public interface APIChecker extends Adapter {
2728
// If false, apiChecker is unable to handle the operation or not implemented
2829
// On exception, checkAccess failed don't allow
2930
boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException;
31+
boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException;
32+
boolean isEnabled();
3033
}

api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,7 @@ public void execute() {
186186
validateParams();
187187
CallContext.current().setEventDetails("Account Name: " + getUsername() + ", Domain Id:" + getDomainId());
188188
UserAccount userAccount =
189-
_accountService.createUserAccount(getUsername(), getPassword(), getFirstName(), getLastName(), getEmail(), getTimeZone(), getAccountName(), getAccountType(), getRoleId(),
190-
getDomainId(), getNetworkDomain(), getDetails(), getAccountUUID(), getUserUUID());
189+
_accountService.createUserAccount(this);
191190
if (userAccount != null) {
192191
AccountResponse response = _responseGenerator.createUserAccountResponse(ResponseView.Full, userAccount);
193192
response.setResponseName(getCommandName());

api/src/test/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmdTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void testExecuteWithNotBlankPassword() {
7373
} catch (ServerApiException e) {
7474
Assert.assertTrue("Received exception as the mock accountService createUserAccount returns null user", true);
7575
}
76-
Mockito.verify(accountService, Mockito.times(1)).createUserAccount(null, "Test", null, null, null, null, null, accountType, roleId, domainId, null, null, null, null);
76+
Mockito.verify(accountService, Mockito.times(1)).createUserAccount(createAccountCmd);
7777
}
7878

7979
@Test
@@ -86,7 +86,7 @@ public void testExecuteWithNullPassword() {
8686
Assert.assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode());
8787
Assert.assertEquals("Empty passwords are not allowed", e.getMessage());
8888
}
89-
Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, roleId, domainId, null, null, null, null);
89+
Mockito.verify(accountService, Mockito.never()).createUserAccount(createAccountCmd);
9090
}
9191

9292
@Test
@@ -99,6 +99,6 @@ public void testExecuteWithEmptyPassword() {
9999
Assert.assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode());
100100
Assert.assertEquals("Empty passwords are not allowed", e.getMessage());
101101
}
102-
Mockito.verify(accountService, Mockito.never()).createUserAccount(null, null, null, null, null, null, null, accountType, roleId, domainId, null, null, null, null);
102+
Mockito.verify(accountService, Mockito.never()).createUserAccount(createAccountCmd);
103103
}
104104
}

plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie
7575
throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null");
7676
}
7777

78+
return checkAccess(account, commandName);
79+
}
80+
81+
public boolean checkAccess(Account account, String commandName) {
7882
final Role accountRole = roleService.findRole(account.getRoleId());
7983
if (accountRole == null || accountRole.getId() < 1L) {
8084
denyApiAccess(commandName);
@@ -106,6 +110,11 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie
106110
throw new UnavailableCommandException("The API " + commandName + " does not exist or is not available for this account.");
107111
}
108112

113+
@Override
114+
public boolean isEnabled() {
115+
return roleService.isEnabled();
116+
}
117+
109118
public void addApiToRoleBasedAnnotationsMap(final RoleType roleType, final String commandName) {
110119
if (roleType == null || Strings.isNullOrEmpty(commandName)) {
111120
return;

plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,13 @@ private void denyApiAccess(final String commandName) throws PermissionDeniedExce
5858
throw new PermissionDeniedException("The API " + commandName + " is denied for the user's/account's project role.");
5959
}
6060

61+
@Override
62+
public boolean isEnabled() {
63+
return roleService.isEnabled();
64+
}
6165

6266
public boolean isDisabled() {
63-
return !roleService.isEnabled();
67+
return !isEnabled();
6468
}
6569

6670
@Override
@@ -103,6 +107,11 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe
103107
throw new UnavailableCommandException("The API " + apiCommandName + " does not exist or is not available for this account/user in project "+project.getUuid());
104108
}
105109

110+
@Override
111+
public boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException {
112+
return true;
113+
}
114+
106115
private boolean isPermitted(Project project, ProjectAccount projectUser, String apiCommandName) {
107116
ProjectRole projectRole = null;
108117
if(projectUser.getProjectRoleId() != null) {

plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ public StaticRoleBasedAPIAccessChecker() {
6565
}
6666
}
6767

68+
@Override
69+
public boolean isEnabled() {
70+
return !isDisabled();
71+
}
6872
public boolean isDisabled() {
6973
return roleService.isEnabled();
7074
}
@@ -80,6 +84,10 @@ public boolean checkAccess(User user, String commandName) throws PermissionDenie
8084
throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null");
8185
}
8286

87+
return checkAccess(account, commandName);
88+
}
89+
90+
public boolean checkAccess(Account account, String commandName) {
8391
RoleType roleType = accountService.getRoleType(account);
8492
boolean isAllowed =
8593
commandsPropertiesOverrides.contains(commandName) ? commandsPropertiesRoleBasedApisMap.get(roleType).contains(commandName) : annotationRoleBasedApisMap.get(

plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616
// under the License.
1717
package org.apache.cloudstack.discovery;
1818

19+
import com.cloud.user.Account;
1920
import org.apache.cloudstack.api.BaseResponse;
2021
import org.apache.cloudstack.api.response.ListResponse;
2122

2223
import com.cloud.user.User;
2324
import com.cloud.utils.component.PluggableService;
2425

26+
import java.util.List;
27+
2528
public interface ApiDiscoveryService extends PluggableService {
29+
List<String> listApiNames(Account account);
30+
2631
ListResponse<? extends BaseResponse> listApis(User user, String apiName);
2732
}

plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import javax.inject.Inject;
2929

30+
import com.cloud.user.Account;
3031
import org.apache.cloudstack.acl.APIChecker;
3132
import org.apache.cloudstack.api.APICommand;
3233
import org.apache.cloudstack.api.BaseAsyncCmd;
@@ -210,6 +211,24 @@ private ApiDiscoveryResponse getCmdRequestMap(Class<?> cmdClass, APICommand apiC
210211
return response;
211212
}
212213

214+
@Override
215+
public List<String> listApiNames(Account account) {
216+
List<String> apiNames = new ArrayList<>();
217+
for (String apiName : s_apiNameDiscoveryResponseMap.keySet()) {
218+
boolean isAllowed = true;
219+
for (APIChecker apiChecker : _apiAccessCheckers) {
220+
try {
221+
apiChecker.checkAccess(account, apiName);
222+
} catch (Exception ex) {
223+
isAllowed = false;
224+
}
225+
}
226+
if (isAllowed)
227+
apiNames.add(apiName);
228+
}
229+
return apiNames;
230+
}
231+
213232
@Override
214233
public ListResponse<? extends BaseResponse> listApis(User user, String name) {
215234
ListResponse<ApiDiscoveryResponse> response = new ListResponse<ApiDiscoveryResponse>();

plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,16 +147,20 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe
147147
}
148148
Long accountId = user.getAccountId();
149149
Account account = _accountService.getAccount(accountId);
150+
return checkAccess(account, apiCommandName);
151+
}
152+
153+
public boolean checkAccess(Account account, String commandName) {
150154
if (_accountService.isRootAdmin(account.getId())) {
151155
// no API throttling on root admin
152156
return true;
153157
}
154-
StoreEntry entry = _store.get(accountId);
158+
StoreEntry entry = _store.get(account.getId());
155159

156160
if (entry == null) {
157161

158162
/* Populate the entry, thus unlocking any underlying mutex */
159-
entry = _store.create(accountId, timeToLive);
163+
entry = _store.create(account.getId(), timeToLive);
160164
}
161165

162166
/* Increment the client count and see whether we have hit the maximum allowed clients yet. */
@@ -174,6 +178,11 @@ public boolean checkAccess(User user, String apiCommandName) throws PermissionDe
174178
}
175179
}
176180

181+
@Override
182+
public boolean isEnabled() {
183+
return enabled;
184+
}
185+
177186
@Override
178187
public List<Class<?>> getCommands() {
179188
List<Class<?>> cmdList = new ArrayList<Class<?>>();

0 commit comments

Comments
 (0)