...
By invoking these QueryChecker APIs, CloudStack API engine can pre-construct proper SQL where clause to achieve proper row filter for accessibility control.
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 (Full view and Restricted 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:
We will separate all current both admin and user allowed API commands to two classes: API for admin and API for user. For example, previous ListVMsCmd will be splitted into two classes: ListVMsCmdByAdmin and ListVMsCmd.
Code Block |
---|
@APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted)
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;
}
|
Note that ListVMsCmdByAdmin and ListVMsCmd are sharing the same API name "listVirtualMachines". From client perspective, this is transparent to them, CloudStack API client will still just invoke previous listVirtualMachine API, and CloudStack API server will internally consult with IAM plugin to determine the group associated with the invoking user and then determine which internal Cmd class to be invoked. There is a new attribute "responseView" introduced for @APICommand annoation, which can be used to instruct CloudStack to generate different response view. By separating the command class into admin cmd class and user cmd class, we can also restrict valid input parameters for different account. For example, in this case, when Admin invokes listVirtualMachine api, he/she can pass hostId, podId and storageId, which parameters are not applicable for end user.
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 | path | removed | created |
---|---|---|---|---|---|---|
1 | REGULAR_USER | Domain user group | d283d4f0-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 14:13:34 |
2 | ADMIN | Root admin group | d283de28-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 14:13:34 |
3 | DOMAIN_ADMIN | Domain admin group | d283e6e8-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 14:13:34 |
acl_group_account_map
IAM feature will be making use of CloudStack's plugin framework and will be implemented as a separate plugin. The end goal is to be able to host the IAM server as an independent service listening at an endpoint which the CS API service calls to do access checks.
IAM plugin will be developed under cloudstack-services project so that in future it can be easily separated as a service.
Following is the code base structure for the IAM feature:
cloud-iam service will be having two parts:
...
Following component diagram illustrates these boundaries:
Gliffy Diagram | ||
---|---|---|
|
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 (Full view and Restricted 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:
We will separate all current both admin and user allowed API commands to two classes: API for admin and API for user. For example, previous ListVMsCmd will be splitted into two classes: ListVMsCmdByAdmin and ListVMsCmd.
Code Block |
---|
@APICommand(name = "listVirtualMachines", description = "List the virtual machines owned by the account.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted)
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;
}
|
Note that ListVMsCmdByAdmin and ListVMsCmd are sharing the same API name "listVirtualMachines". From client perspective, this is transparent to them, CloudStack API client will still just invoke previous listVirtualMachine API, and CloudStack API server will internally consult with IAM plugin to determine the group associated with the invoking user and then determine which internal Cmd class to be invoked. There is a new attribute "responseView" introduced for @APICommand annoation, which can be used to instruct CloudStack to generate different response view. By separating the command class into admin cmd class and user cmd class, we can also restrict valid input parameters for different account. For example, in this case, when Admin invokes listVirtualMachine api, he/she can pass hostId, podId and storageId, which parameters are not applicable for end user.
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 | path | removed | created | |||||
---|---|---|---|---|---|---|---|---|---|---|---|
1 | REGULAR_USER | Domain user group | d283d4f0-31f0-11e3-ad37-80f85ce25918 | / | |||||||
id | group_id | account_id | removed | created | |||||||
2 | 2 | 2 | NULL | 2013-10-10 14:13:34 | |||||||
323 | ADMIN | 3 | NULL | 2013-10-11 00:14:54 | 4 | 1 | Root admin group | d283de28-31f0-11e3-ad37-80f85ce25918 | /4 | NULL | 2013-10-11 0010 14:1913:5534 |
acl_policy
3 | DOMAIN_ADMIN | Domain admin group | d283e6e8 | |||||||||
id | name | description | uuid | path | removed | created | policy_type | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | REGULAR_USER | Domain user role | d2838dce-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 14:13:34 | Static | 2 | ADMIN | Root admin role | d2839c56-31f0-11e3-ad37-80f85ce25918 |
acl_group_account_map
id | group_id | account_id | removed | created | ||||||
---|---|---|---|---|---|---|---|---|---|---|
2 | 2 | 2/ | NULL | 2013-10-10 14:13:34 | ||||||
Static3 | 3 | 3 | NULL | 2013-10-11 00:14:54 | ||||||
4 | 1 | 4 | DOMAIN_ADMIN | Domain admin role | d283a7f0-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 1411 00:13:34 | Static | 19:55 |
acl_policy
id | name | description | uuid | path | removed | created | policy_type | ||||
---|---|---|---|---|---|---|---|---|---|---|---|
1 | REGULAR_USER | Domain user role | d2838dce | 6 | RESOURCE_OWNER | Resource owner role | d283c794-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 14:13:34 | Dynamic |
acl_group_policy_map
Static | ||||||||||||
2 | ADMIN | Root admin role | d2839c56-31f0-11e3-ad37-80f85ce25918 | / | ||||||||
id | group_id | policy_id | removed | created | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 1 | NULL | 2013-10-10 14:13:34 | 2Static2 | |||||||
3 | 2 | NULL | 2013-10-10 14:13:34 | DOMAIN_ADMIN | Domain admin role | d283a7f0-31f0-11e3-ad37-80f85ce25918 | / | 3 | 3 | 3 | NULL | 2013-10-10 14:13:34 |
acl_permission
Static | |||||||
6 | RESOURCE_OWNER | Resource owner role | d283c794-31f0-11e3-ad37-80f85ce25918 | / | NULL | 2013-10-10 14:13:34 | Dynamic |
acl_group_policy_map
id | group_id | policy_id | id | action | resource_type | scope_id | scope | access_type | permission | removed | created |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | startVirtualMachine1 | VirtualMachine1 | NULL | ALL | NULL | Allow | NULL | 2013-2013-10-10 14:13:34 | |||
2 | startVirtualMachine2 | VirtualMachine2 | $domainId | Domain | NULLAllow | NULL | 2013-10-10 14:13:34 | ||||
3 | startVirtualMachine | VirtualMachine | $accountId | 3 | 3Account | NULLAllow | NULL | 2013-10-10 14:13:34 |
acl_policy_permission_mappermission
id | action | resource_type | scope_id | scope | access_type | permission | id | policy_id | permission_id | removed | created |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 6startVirtualMachine | 3VirtualMachine | NULL | ALL | NULL | Allow | NULL | 2013-2013-10-10 14:13:34 | |||
2 | 2 | startVirtualMachine | VirtualMachine | $domainId | Domain | NULL | Allow1 | NULL | 2013-10-10 14:13:34 | ||
3 | 3 | startVirtualMachine | VirtualMachine | $accountId | Account | NULL | Allow2 | NULL | 2013-10-10 14:13:34 |
Lets consider the StartVM API is being called by a user and run through the access control usecase for each default policy.
The StartVMCmd will contain an annotation on the field that needs to be checked for access:
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 usecase for each default policy.
The StartVMCmd will contain an annotation on the field that needs to be checked for access:
Code Block |
---|
@APICommand(name = "startVirtualMachine", responseObject = UserVmResponse.class, description = "Starts |
Code Block |
@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; |
Regular user 'domainUserA' calls this command for his own VM:
API access check
The API layer will call the APICheckers to see if the user is allowed to invoke this API. The PolicyBasedAccessChecker :: checkAccess(user, apiName) will just return true and rely on next step
Entity Access Check
The @ACL annotation invokes the SecurityChecker implementation. The PolicyBasedAccessChecker:: checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) is invoked for the given user account and VM Id for action 'startVirtualMachine'
A Domain Admin 'domainAdmin' calls this command for a VM in his domain:
Entity Access Check
A Root Admin 'admin' calls this command for any VM:
@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;
|
Regular user 'domainUserA' calls this command for his own VM:
API access check
The API layer will call the APICheckers to see if the user is allowed to invoke this API. The PolicyBasedAccessChecker :: checkAccess(user, apiName) will just return true and rely on next step
Entity Access Check
The @ACL annotation invokes the SecurityChecker implementation. The PolicyBasedAccessChecker:: checkAccess(Account caller, ControlledEntity entity, AccessType accessType, String action) is invoked for the given user account and VM Id for action 'startVirtualMachine'Entity Access Check
Consider following example:
A Domain admin wants to create a 'Service Desk' group for his domain and allow 'ready only' access to the group for all VMs and Volumes within the Domain.
Steps:
What should be the method of ‘creating a custom Policy/Role’?
For this release, creating a custom policy is supported through API only.
For further releases, we can provide either a UI or a config file + policy language mechanism to facilitate the custom policy creation.
IAM feature will be making use of CloudStack's plugin framework and will be implemented as a separate plugin. The end goal is to be able to host the IAM server as an independent service listening at an endpoint which the CS API service calls to do access checks.
IAM plugin will be developed under cloudstack-services project so that in future it can be easily separated as a service.
Following is the code base structure for the IAM feature:
cloud-iam service will be having two parts:
...
Following component diagram illustrates these boundaries:
A Domain Admin 'domainAdmin' calls this command for a VM in his domain:
Entity Access Check
A Root Admin 'admin' calls this command for any VM:
Entity Access Check
Consider following example:
A Domain admin wants to create a 'Service Desk' group for his domain and allow 'ready only' access to the group for all VMs and Volumes within the Domain.
Steps:
What should be the method of ‘creating a custom Policy/Role’?
For this release, creating a custom policy is supported through API only. For further releases, we can provide either a UI or a config file + policy language mechanism to facilitate the custom policy creation. Gliffy Diagram