With the introduction of the CS IAM feature in 4.4 release, there are a few changes to be aware of and followed while adding a new API and any service layer access control.
In this document we will maintain any API coding conventions, annotations, standards to be followed w.r.t IAM checks.
What happens to commands.properties and @APICommand(authorized=) annotation after adding the IAM feature? No changes, they work as they used to.
Additionally, specify what entity the API Cmd operates on in entityType attribute of the @APICommand annotation.
@APICommand(name = "deployVirtualMachine", ..... entityType = { VirtualMachine.class }) public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd { ... }
Following guidelines should be followed while adding a new API to CloudStack inorder to ensure correct access control is weaved into the logic for all the entities involved.
(create/update/delete)
With IAM, one should use the correct AccessType in the @ACL annotation or at service layer to specify what kind of access is needed for the entity while invoking this API.
Example: A domainAdmin registers a template T and allows a regular user of the domain to launch a VM using that template. Entity: Template T Principal1: domainAdmin, Access allowed: OperateEntry (operate access since he can invoke delete/updatepermissions operations on the template) Principal2: normal domain user, Access allowed: UseEntry (the user can only list the template and use it for launching VM)
Typically for:
Example: DeployVMCmd: This is a create API: Add @ACL(accessType = AccessType.UseEntry access for all entities like template, network Start/Stop/Reboot/Destroy/AttachXXXTOVM: These are the update/delete APIs. Add @ACL(accessType = AccessType.OperateEntry) for VM ID
CS Service layer logic uses "accountManager.checkAccess" to invoke the SecurityCheckers to do access control. Instead, one should try to use @ACL annotation on the API parameters that have to be checked for access.
This will help to funnel the calls to the IAM Service at API layer itself without invoking any service layer logic.
But in some cases, you may need to still add such checks at Service layer. (example, when the entity you want to check access for is not exposed in the API cmd as a parameter)
(listXXX commands)
In new IAM model, we have updated the following interfaces in AccountManager to build search criteria by considering IAM group/policy/permissions. In these interfaces, we have added "permittedDomains", "permittedAccounts" and "permittedResources" lists to account for granting by domain, account and resource, respectively.
// new ACL model routine for query api void buildACLSearchParameters(Account caller, Long id, String accountName, Long projectId, List<Long> permittedDomains, List<Long> permittedAccounts, List<Long> permittedResources, Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject, boolean listAll, boolean forProjectInvitation, String action); void buildACLSearchBuilder(SearchBuilder<? extends ControlledEntity> sb, boolean isRecursive, List<Long> permittedDomains, List<Long> permittedAccounts, List<Long> permittedResources, ListProjectResourcesCriteria listProjectResourcesCriteria); void buildACLSearchCriteria(SearchCriteria<? extends ControlledEntity> sc, boolean isRecursive, List<Long> permittedDomains, List<Long> permittedAccounts, List<Long> permittedResources, ListProjectResourcesCriteria listProjectResourcesCriteria); void buildACLViewSearchCriteria(SearchCriteria<? extends ControlledEntity> sc, SearchCriteria<? extends ControlledEntity> aclSc, boolean isRecursive, List<Long> permittedDomains, List<Long> permittedAccounts, List<Long> permittedResources, ListProjectResourcesCriteria listProjectResourcesCriteria);
Currently in CloudStack code, we have two types of ControlledEntity: one with db view created (like VirtualMachineTemplate, Volume, etc) whose list logic is mainly implemented in QueryManagerImpl; the other with no db view created (like FirewallRule,etc) whose list logic is scattered around at service layer.
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>( cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedDomains, permittedAccounts, permittedResources, domainIdRecursiveListProject, listAll, false, "listVirtualMachines"); ...... SearchBuilder<UserVmJoinVO> sb = _userVmJoinDao.createSearchBuilder(); ...... SearchCriteria<UserVmJoinVO> sc = sb.create(); SearchCriteria<UserVmJoinVO> aclSc = _userVmJoinDao.createSearchCriteria(); // building ACL search criteria to join with sc _accountMgr.buildACLViewSearchCriteria(sc, aclSc, isRecursive, permittedDomains, permittedAccounts, permittedResources, listProjectResourcesCriteria);
Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<Long, Boolean, ListProjectResourcesCriteria>(cmd.getDomainId(), cmd.isRecursive(), null); _accountMgr.buildACLSearchParameters(caller, id, cmd.getAccountName(), cmd.getProjectId(), permittedDomains, permittedAccounts, permittedResources, domainIdRecursiveListProject, cmd.listAll(), false, "listFirewallRules"); ....... SearchBuilder<FirewallRuleVO> sb = _firewallDao.createSearchBuilder(); _accountMgr.buildACLSearchBuilder(sb, isRecursive, permittedDomains, permittedAccounts, permittedResources, listProjectResourcesCriteria); ...... SearchCriteria<FirewallRuleVO> sc = sb.create(); _accountMgr.buildACLSearchCriteria(sc, isRecursive, permittedDomains, permittedAccounts, permittedResources, listProjectResourcesCriteria);
For some commands that can be invoked by both admin and user, if you want to provide different response contents to root admin and non-root user, for example, some response fields are only visible to root admin. We have defined two enumeration types (ResponseView.Full and ResponseView.Restricted) for these two types of response views. You should your API command to two classes: API for admin that will return Full response view and may take additional admin-only parameters and API for user that will return Restricted response view. For example, previous ListVMsCmd has been splitted into two classes: ListVMsCmdByAdmin and ListVMsCmd. The naming convention is that the API command class that returns Full response view should have a suffix "ByAdmin". We also introduced a new annotation (responseView) in @APICommand to specify which response view should be returned from this command. Normally, ***ByAdmin class can be extended from its restricted view class. Here is an example of this command split and response view annotation:
@APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted, entityType = { IAMEntityType.VirtualMachine }) public class ListVMsCmd extends BaseListTaggedResourcesCmd { ...... } @APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.Full) public class ListVMsCmdByAdmin extends ListVMsCmd { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// @Parameter(name=ApiConstants.HOST_ID, type=CommandType.UUID, entityType=HostResponse.class, description="the host ID") private Long hostId; @Parameter(name=ApiConstants.POD_ID, type=CommandType.UUID, entityType=PodResponse.class, description="the pod ID") private Long podId; @Parameter(name=ApiConstants.STORAGE_ID, type=CommandType.UUID, entityType=StoragePoolResponse.class, description="the storage ID where vm's volumes belong to") private Long storageId; }
In execute() of these two command classes, we should make a distinction to create different response content based on responseView annotation.