Skip to content

Commit 7cfbcf4

Browse files
shwstpprnvazquezwinterhazelJoaoJandrePearl1594
authored
[4.19] server, api, ui: access improvements and assorted fixes (#22)
* server, api, ui: access improvements and assorted fixes Fixes domain-admin access check to prevent unauthorized access. Co-authored-by: Fabricio Duarte <fabricio.duarte.jr@gmail.com> Co-authored-by: nvazquez <nicovazquez90@gmail.com> Co-authored-by: Abhishek Kumar <abhishek.mrt22@gmail.com> * Revert "server: refactor listNetworks api database retrievals (#9184)" This reverts commit c7f1ba5. * Fix snapshot chain being deleted on XenServer (#9447) Using XenServer as the hypervisor, when deleting a snapshot that has a parent, that parent will also get erased on storage, causing data loss. This behavior was introduced with #7873, where the list of snapshot states that can be deleted was changed to add BackedUp snapshots. This PR changes the states list back to the original list, and swaps the while loop for a do while loop to account for the changes in #7873. Fixes #9446 * UI: Display Firewall, LB and Port Forwading rules tab for CKS clusters deployed on isolated networks (#9458) --------- Co-authored-by: nvazquez <nicovazquez90@gmail.com> Co-authored-by: Fabricio Duarte <fabricio.duarte.jr@gmail.com> Co-authored-by: João Jandre <48719461+JoaoJandre@users.noreply.github.com> Co-authored-by: Pearl Dsilva <pearl1594@gmail.com>
1 parent 9f4c895 commit 7cfbcf4

File tree

9 files changed

+589
-137
lines changed

9 files changed

+589
-137
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
fail-fast: false
3636
matrix:
3737
tests: [ "smoke/test_accounts
38+
smoke/test_account_access
3839
smoke/test_affinity_groups
3940
smoke/test_affinity_groups_projects
4041
smoke/test_annotations

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ public class DefaultSnapshotStrategy extends SnapshotStrategyBase {
102102
@Inject
103103
SnapshotZoneDao snapshotZoneDao;
104104

105+
private final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
106+
105107
public SnapshotDataStoreVO getSnapshotImageStoreRef(long snapshotId, long zoneId) {
106108
List<SnapshotDataStoreVO> snaps = snapshotStoreDao.listReadyBySnapshot(snapshotId, DataStoreRole.Image);
107109
for (SnapshotDataStoreVO ref : snaps) {
@@ -199,9 +201,8 @@ protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storageToStr
199201

200202
boolean result = false;
201203
boolean resultIsSet = false;
202-
final List<Snapshot.State> snapshotStatesAbleToDeleteSnapshot = Arrays.asList(Snapshot.State.BackedUp, Snapshot.State.Destroying, Snapshot.State.Destroyed, Snapshot.State.Error);
203204
try {
204-
while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState())) {
205+
do {
205206
SnapshotInfo child = snapshot.getChild();
206207

207208
if (child != null) {
@@ -247,7 +248,7 @@ protected boolean deleteSnapshotChain(SnapshotInfo snapshot, String storageToStr
247248
}
248249

249250
snapshot = parent;
250-
}
251+
} while (snapshot != null && snapshotStatesAbleToDeleteSnapshot.contains(snapshot.getState()));
251252
} catch (Exception e) {
252253
s_logger.error(String.format("Failed to delete snapshot [%s] on storage [%s] due to [%s].", snapshotTo, storageToString, e.getMessage()), e);
253254
}

server/src/main/java/com/cloud/acl/DomainChecker.java

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -208,38 +208,66 @@ public boolean checkAccess(Account caller, ControlledEntity entity, AccessType a
208208

209209
return true;
210210
} else if (entity instanceof Network && accessType != null && accessType == AccessType.UseEntry) {
211-
_networkMgr.checkNetworkPermissions(caller, (Network)entity);
211+
_networkMgr.checkNetworkPermissions(caller, (Network) entity);
212212
} else if (entity instanceof Network && accessType != null && accessType == AccessType.OperateEntry) {
213213
_networkMgr.checkNetworkOperatePermissions(caller, (Network)entity);
214214
} else if (entity instanceof VirtualRouter) {
215215
_networkMgr.checkRouterPermissions(caller, (VirtualRouter)entity);
216216
} else if (entity instanceof AffinityGroup) {
217217
return false;
218218
} else {
219-
if (_accountService.isNormalUser(caller.getId())) {
220-
Account account = _accountDao.findById(entity.getAccountId());
221-
String errorMessage = String.format("%s does not have permission to operate with resource", caller);
222-
if (account != null && account.getType() == Account.Type.PROJECT) {
223-
//only project owner can delete/modify the project
224-
if (accessType != null && accessType == AccessType.ModifyProject) {
225-
if (!_projectMgr.canModifyProjectAccount(caller, account.getId())) {
226-
throw new PermissionDeniedException(errorMessage);
227-
}
228-
} else if (!_projectMgr.canAccessProjectAccount(caller, account.getId())) {
229-
throw new PermissionDeniedException(errorMessage);
230-
}
231-
checkOperationPermitted(caller, entity);
232-
} else {
233-
if (caller.getId() != entity.getAccountId()) {
234-
throw new PermissionDeniedException(errorMessage);
235-
}
219+
validateCallerHasAccessToEntityOwner(caller, entity, accessType);
220+
}
221+
return true;
222+
}
223+
224+
protected void validateCallerHasAccessToEntityOwner(Account caller, ControlledEntity entity, AccessType accessType) {
225+
PermissionDeniedException exception = new PermissionDeniedException("Caller does not have permission to operate with provided resource.");
226+
String entityLog = String.format("entity [owner ID: %d, type: %s]", entity.getAccountId(),
227+
entity.getEntityType().getSimpleName());
228+
229+
if (_accountService.isRootAdmin(caller.getId())) {
230+
return;
231+
}
232+
233+
if (caller.getId() == entity.getAccountId()) {
234+
return;
235+
}
236+
237+
Account owner = _accountDao.findById(entity.getAccountId());
238+
if (owner == null) {
239+
s_logger.error(String.format("Owner not found for %s", entityLog));
240+
throw exception;
241+
}
242+
243+
Account.Type callerAccountType = caller.getType();
244+
if ((callerAccountType == Account.Type.DOMAIN_ADMIN || callerAccountType == Account.Type.RESOURCE_DOMAIN_ADMIN) &&
245+
_domainDao.isChildDomain(caller.getDomainId(), owner.getDomainId())) {
246+
return;
247+
}
248+
249+
if (owner.getType() == Account.Type.PROJECT) {
250+
// only project owner can delete/modify the project
251+
if (accessType == AccessType.ModifyProject) {
252+
if (!_projectMgr.canModifyProjectAccount(caller, owner.getId())) {
253+
s_logger.error(String.format("Caller ID: %d does not have permission to modify project with " +
254+
"owner ID: %d", caller.getId(), owner.getId()));
255+
throw exception;
236256
}
257+
} else if (!_projectMgr.canAccessProjectAccount(caller, owner.getId())) {
258+
s_logger.error(String.format("Caller ID: %d does not have permission to access project with " +
259+
"owner ID: %d", caller.getId(), owner.getId()));
260+
throw exception;
237261
}
262+
checkOperationPermitted(caller, entity);
263+
return;
238264
}
239-
return true;
265+
266+
s_logger.error(String.format("Caller ID: %d does not have permission to access %s", caller.getId(), entityLog));
267+
throw exception;
240268
}
241269

242-
private boolean checkOperationPermitted(Account caller, ControlledEntity entity) {
270+
protected boolean checkOperationPermitted(Account caller, ControlledEntity entity) {
243271
User user = CallContext.current().getCallingUser();
244272
Project project = projectDao.findByProjectAccountId(entity.getAccountId());
245273
if (project == null) {

0 commit comments

Comments
 (0)