Currently CloudStack provides very limited IAM services and there are several drawbacks within those services:
Goal for this feature would be to address these limitations and offer true IAM services in a phased manner
Group contains a number of CloudStack accounts. Customers should be able to Create, Edit, List and Delete Groups. Editing includes adding or removing accounts to or from a group. For backwards compatibility, out of box, CloudStack will provide 3 default groups:
Account is just our current CloudStack Account, all the permission controls are done at Account level. We can assign an Account to more than one Group.
CloudStack user just contains login credentials, and this is not the level that we are performing permission control.
Policy is a set of permission. Customer should be able to attach several policies to a Group to define the permission for that group. By default, we have the following 3 types of policy templates:
Other than that, customer should be able to define customized policies by grant or deny permission to customize permissions for the group. So far, for cross-account permission grant, we are currently supporting the following 3 types of granting/denying:
A policy consists of set of Permissions. A Permission is a way of defining access control.
Using Permission, customer defines what actions are allowed or denied, on what resources, under which account or domain.
A single permission definition consists of:
Currently CloudStack supports two different views: Admin view for root admin user, User view for domain admin and end user. With IAM added, we still need to support this for newly added account groups. Analogy to row filter controlled by Permission, this is like column filter. For this release, we are not supporting dynamic column filter at API level, instead, out-of-box, we will provide two static response views: Admin view and User view. When user is creating a group, they can specify what kind of view that this group account should see.
New API's
CloudStack currently has a domain-tree based implementation of access checks, namely com.cloud.acl.DomainChecker. This implementation is based on an adapter interface of Cloudstack - org.apache.cloudstack.acl.SecurityChecker that defines the basic ACL interface to check ownership and access control to objects within the account/ domain.
The IAM plugin will provide another implementation 'PolicyBasedAccessChecker' of the SecurityChecker interface. We will also have to add to this interface some more methods or change some signatures to facilitate group, policy and api name (action) based access control.
/** * SecurityChecker checks the ownership and access control to objects within */ public interface SecurityChecker extends Adapter { ... /** * Checks if the account can access the object. * * @param caller * account to check against. * @param entity * object that the account is trying to access. * @param accessType * * @param action * * @return true if access allowed. false if this adapter cannot provide permission. * @throws PermissionDeniedException * if this adapter is suppose to authenticate ownership and the check failed. */ boolean checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) throws PermissionDeniedException; .... }
IAM Plugin 'PolicyBasedAccessChecker' will also provide a group and policy based implementation of the APIChecker interface. The implementation will check if a given user is permitted to make the given API call by looking at the users' groups and the associated policies of those groups.
For given user and given api name,
// APIChecker checks the ownership and access control to API requests public interface APIChecker extends Adapter { // Interface for checking access for a role using apiname // If true, apiChecker has checked the operation // If false, apiChecker is unable to handle the operation or not implemented // On exception, checkAccess failed don't allow boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException; }
Besides SecurityChecker and APIChecker interface, IAM plugin will also implement another QueryChecker interface to allow CloudStack to do proper row filter in ListAPI based on caller's policy.
/** * QueryChecker returns granted domain, or account or resources for caller. */ public interface QueryChecker extends Adapter { ... /** * List granted domains for the caller, given a specific entity type. * * @param caller account to check against. * @param entityType entity type * @return list of domain Ids granted to the caller account. */ List<Long> getAuthorizedDomains(Account caller, String entityType); /** * List granted accounts for the caller, given a specific entity type. * * @param caller account to check against. * @param entityType entity type * @return list of domain Ids granted to the caller account. */ List<Long> getAuthorizedAccounts(Account caller, String entityType); /** * List granted resources for the caller, given a specific entity type. * * @param caller account to check against. * @param entityType entity type * @return list of domain Ids granted to the caller account. */ List<Long> getAuthorizedResources(Account caller, String entityType); }
Currently CloudStack provides different response views for Root admin and non-root user, some response fields are only visible to root admin. Basically we have provided two static response views (Admin view and User view), domain admin will also a User view. With new IAM service introduced, we should also allow customers to be able to specify what view should be applied to the new Acl group when they are creating a new customized Acl group, for example, customer care group. To achieve that, we will implement as follows:
@APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.User) public class ListVMsCmd extends BaseListTaggedResourcesCmd { ...... } @APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.Admin) 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; }
For backward compatibility, out-of-box, we provided 3 default policies: Root admin policy, domain admin policy, and end user policy. The process to populate permissions for these default policies are as follows:
When user creates a customized policy, he can specify a source policy from list of default policies. Then we will create new mapping between these default policy permission entries and the new policy in acl_policy_permission_map table.
account
id |
account_name |
uuid |
domain_id |
---|---|---|---|
2 |
admin |
1c5afd64-482b-11e3-86f3-8118f47f9f9f |
1 |
3 |
domainAdmin |
929d172c-b95e-4b86-9474-9789072c9bdb |
2 |
4 |
domainUserA |
f96ddb47-d3c0-4360-a9cd-613d631c8333 |
2 |
acl_group
id |
name |
description |
uuid |
domain_id |
removed |
created |
---|---|---|---|---|---|---|
1 |
REGULAR_USER |
Domain user group |
d283d4f0-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
2 |
ADMIN |
Root admin group |
d283de28-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
3 |
DOMAIN_ADMIN |
Domain admin group |
d283e6e8-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
acl_group_account_map
id |
group_id |
account_id |
removed |
created |
---|---|---|---|---|
2 |
2 |
2 |
NULL |
2013-10-10 14:13:34 |
3 |
3 |
3 |
NULL |
2013-10-11 00:14:54 |
4 |
1 |
4 |
NULL |
2013-10-11 00:19:55 |
acl_policy
id |
name |
description |
uuid |
domain_id |
removed |
created |
policy_type |
---|---|---|---|---|---|---|---|
1 |
REGULAR_USER |
Domain user role |
d2838dce-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
Static |
2 |
ADMIN |
Root admin role |
d2839c56-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
Static |
3 |
DOMAIN_ADMIN |
Domain admin role |
d283a7f0-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
Static |
6 |
RESOURCE_OWNER |
Resource owner role |
d283c794-31f0-11e3-ad37-80f85ce25918 |
1 |
NULL |
2013-10-10 14:13:34 |
Dynamic |
acl_group_policy_map
id |
group_id |
policy_id |
removed |
created |
---|---|---|---|---|
1 |
1 |
1 |
NULL |
2013-10-10 14:13:34 |
2 |
2 |
2 |
NULL |
2013-10-10 14:13:34 |
3 |
3 |
3 |
NULL |
2013-10-10 14:13:34 |
acl_permission
id |
action |
resource_type |
scope_id |
scope |
access_type |
permission |
removed |
created |
---|---|---|---|---|---|---|---|---|
1 |
startVirtualMachine |
VirtualMachine |
NULL |
ALL |
NULL |
Allow |
NULL |
2013-10-10 14:13:34 |
2 |
startVirtualMachine |
VirtualMachine |
$domainId |
Domain |
NULL |
Allow |
NULL |
2013-10-10 14:13:34 |
3 |
startVirtualMachine |
VirtualMachine |
$accountId |
Account |
NULL |
Allow |
NULL |
2013-10-10 14:13:34 |
acl_policy_permission_map
id |
policy_id |
permission_id |
removed |
created |
---|---|---|---|---|
1 |
6 |
3 |
NULL |
2013-10-10 14:13:34 |
2 |
2 |
1 |
NULL |
2013-10-10 14:13:34 |
3 |
3 |
2 |
NULL |
2013-10-10 14:13:34 |
Lets consider the StartVM API is being called by a user and run through the access control usecases for various out-of-box policies.
The StartVMCmd will contain an annotation on the field that needs to be checked for access:
@APICommand(name = "startVirtualMachine", responseObject = UserVmResponse.class, description = "Starts a virtual machine.") public class StartVMCmd extends BaseAsyncCmd { public static final Logger s_logger = Logger.getLogger(StartVMCmd.class.getName()); private static final String s_name = "startvirtualmachineresponse"; // /////////////////////////////////////////////////// // ////////////// API parameters ///////////////////// // /////////////////////////////////////////////////// *@ACL(action="startVirtualMachine")* @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType=UserVmResponse.class, required = true, description = "The ID of the virtual machine") private Long id;
Thus following will happen if the above regular user 'domainUserA' calls this command:
API access check
The APIlayer will call the APICheckers to see if the user is allowed to invoke this API
The PolicyBasedAccessChecker :: checkAccess(user, apiName) will check following:
— Find all groups the user belongs too: groupIDs = 1
— Find all 'Effective' policies the groups are associated to: policies = 1, 6
— If any policy 'Allows' the startVirtualMachine API, grant permission to make this call: Policy Id 6 and Permission Id 3 allow the API to be invoked for this user.