// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package org.apache.cloudstack.iam; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.acl.PermissionScope; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.iam.api.IAMPolicy; import org.apache.cloudstack.iam.api.IAMPolicyPermission; import org.apache.cloudstack.iam.api.IAMPolicyPermission.Permission; import org.apache.cloudstack.iam.api.IAMService; import com.cloud.api.ApiServerService; import com.cloud.exception.PermissionDeniedException; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountService; import com.cloud.user.User; import com.cloud.utils.PropertiesUtil; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.component.PluggableService; import com.cloud.utils.exception.CloudRuntimeException; //This is the Role Based API access checker that grab's the account's roles //based on the set of roles, access is granted if any of the role has access to the api public class RoleBasedAPIAccessChecker extends AdapterBase implements APIChecker { protected static final Logger s_logger = Logger.getLogger(RoleBasedAPIAccessChecker.class); @Inject AccountService _accountService; @Inject ApiServerService _apiServer; @Inject IAMService _iamSrv; @Inject VMTemplateDao _templateDao; Set<String> commandsPropertiesOverrides = new HashSet<String>(); Map<RoleType, Set<String>> commandsPropertiesRoleBasedApisMap = new HashMap<RoleType, Set<String>>(); List<PluggableService> _services; protected RoleBasedAPIAccessChecker() { super(); for (RoleType roleType : RoleType.values()) { commandsPropertiesRoleBasedApisMap.put(roleType, new HashSet<String>()); } } @Override public boolean checkAccess(User user, String commandName) throws PermissionDeniedException { Account account = _accountService.getAccount(user.getAccountId()); if (account == null) { throw new PermissionDeniedException("The account id=" + user.getAccountId() + "for user id=" + user.getId() + "is null"); } List<IAMPolicy> policies = _iamSrv.listIAMPolicies(account.getAccountId()); boolean isAllowed = _iamSrv.isActionAllowedForPolicies(commandName, policies); if (!isAllowed) { throw new PermissionDeniedException("The API does not exist or is blacklisted. api: " + commandName); } return isAllowed; } @Override public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { super.configure(name, params); processMapping(PropertiesUtil.processConfigFile(new String[] { "commands.properties" })); return true; } @Override public boolean start() { // drop all default policy api permissions - we reload them every time // to include any changes done to the @APICommand or // commands.properties. for (RoleType role : RoleType.values()) { Long policyId = getDefaultPolicyId(role); if (policyId != null) { _iamSrv.resetIAMPolicy(policyId); } } // add the system-domain capability _iamSrv.addIAMPermissionToIAMPolicy(new Long(Account.ACCOUNT_TYPE_ADMIN + 1), null, null, null, "SystemCapability", null, Permission.Allow, false); _iamSrv.addIAMPermissionToIAMPolicy(new Long(Account.ACCOUNT_TYPE_DOMAIN_ADMIN + 1), null, null, null, "DomainCapability", null, Permission.Allow, false); _iamSrv.addIAMPermissionToIAMPolicy(new Long(Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN + 1), null, null, null, "DomainResourceCapability", null, Permission.Allow, false); // add permissions for public templates List<VMTemplateVO> pTmplts = _templateDao.listByPublic(); for (VMTemplateVO tmpl : pTmplts){ _iamSrv.addIAMPermissionToIAMPolicy(new Long(Account.ACCOUNT_TYPE_ADMIN + 1), VirtualMachineTemplate.class.getSimpleName(), PermissionScope.RESOURCE.toString(), tmpl.getId(), "listTemplates", AccessType.UseEntry.toString(), Permission.Allow, false); _iamSrv.addIAMPermissionToIAMPolicy(new Long(Account.ACCOUNT_TYPE_DOMAIN_ADMIN + 1), VirtualMachineTemplate.class.getSimpleName(), PermissionScope.RESOURCE.toString(), tmpl.getId(), "listTemplates", AccessType.UseEntry.toString(), Permission.Allow, false); _iamSrv.addIAMPermissionToIAMPolicy(new Long(Account.ACCOUNT_TYPE_NORMAL + 1), VirtualMachineTemplate.class.getSimpleName(), PermissionScope.RESOURCE.toString(), tmpl.getId(), "listTemplates", AccessType.UseEntry.toString(), Permission.Allow, false); } for (PluggableService service : _services) { for (Class<?> cmdClass : service.getCommands()) { APICommand command = cmdClass.getAnnotation(APICommand.class); if (!commandsPropertiesOverrides.contains(command.name())) { for (RoleType role : command.authorized()) { addDefaultAclPolicyPermission(command.name(), cmdClass, role); } } } } // read commands.properties and load api acl permissions - // commands.properties overrides any @APICommand authorization for (String apiName : commandsPropertiesOverrides) { Class<?> cmdClass = _apiServer.getCmdClass(apiName); for (RoleType role : RoleType.values()) { if (commandsPropertiesRoleBasedApisMap.get(role).contains(apiName)) { // insert permission for this role for this api addDefaultAclPolicyPermission(apiName, cmdClass, role); } } } return super.start(); } private Long getDefaultPolicyId(RoleType role) { Long policyId = null; switch (role) { case User: policyId = new Long(Account.ACCOUNT_TYPE_NORMAL + 1); break; case Admin: policyId = new Long(Account.ACCOUNT_TYPE_ADMIN + 1); break; case DomainAdmin: policyId = new Long(Account.ACCOUNT_TYPE_DOMAIN_ADMIN + 1); break; case ResourceAdmin: policyId = new Long(Account.ACCOUNT_TYPE_RESOURCE_DOMAIN_ADMIN + 1); break; } return policyId; } private void processMapping(Map<String, String> configMap) { for (Map.Entry<String, String> entry : configMap.entrySet()) { String apiName = entry.getKey(); String roleMask = entry.getValue(); commandsPropertiesOverrides.add(apiName); try { short cmdPermissions = Short.parseShort(roleMask); for (RoleType roleType : RoleType.values()) { if ((cmdPermissions & roleType.getMask()) != 0) commandsPropertiesRoleBasedApisMap.get(roleType).add(apiName); } } catch (NumberFormatException nfe) { s_logger.info("Malformed key=value pair for entry: " + entry.toString()); } } } public List<PluggableService> getServices() { return _services; } @Inject public void setServices(List<PluggableService> services) { _services = services; } private void addDefaultAclPolicyPermission(String apiName, Class<?> cmdClass, RoleType role) { AccessType accessType = null; Class<?>[] entityTypes = null; PermissionScope permissionScope = PermissionScope.ACCOUNT; Long policyId = getDefaultPolicyId(role); switch (role) { case User: permissionScope = PermissionScope.ACCOUNT; break; case Admin: permissionScope = PermissionScope.ALL; break; case DomainAdmin: permissionScope = PermissionScope.DOMAIN; break; case ResourceAdmin: permissionScope = PermissionScope.DOMAIN; break; } boolean addAccountScopedUseEntry = false; if (cmdClass != null) { BaseCmd cmdObj; try { cmdObj = (BaseCmd) cmdClass.newInstance(); if (cmdObj instanceof BaseListCmd) { if (permissionScope == PermissionScope.ACCOUNT) { accessType = AccessType.UseEntry; } else { accessType = AccessType.ListEntry; addAccountScopedUseEntry = true; } } else { accessType = AccessType.OperateEntry; } } catch (Exception e) { throw new CloudRuntimeException(String.format( "%s is claimed as an API command, but it cannot be instantiated", cmdClass.getName())); } APICommand at = cmdClass.getAnnotation(APICommand.class); entityTypes = at.entityType(); } if (entityTypes == null || entityTypes.length == 0) { _iamSrv.addIAMPermissionToIAMPolicy(policyId, null, permissionScope.toString(), new Long(IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER), apiName, (accessType == null) ? null : accessType.toString(), Permission.Allow, false); if (addAccountScopedUseEntry) { _iamSrv.addIAMPermissionToIAMPolicy(policyId, null, PermissionScope.ACCOUNT.toString(), new Long( IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER), apiName, AccessType.UseEntry.toString(), Permission.Allow, false); } } else { for (Class<?> entityType : entityTypes) { _iamSrv.addIAMPermissionToIAMPolicy(policyId, entityType.getSimpleName(), permissionScope.toString(), new Long( IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER), apiName, (accessType == null) ? null : accessType.toString(), Permission.Allow, false); if (addAccountScopedUseEntry) { _iamSrv.addIAMPermissionToIAMPolicy(policyId, entityType.getSimpleName(), PermissionScope.ACCOUNT.toString(), new Long( IAMPolicyPermission.PERMISSION_SCOPE_ID_CURRENT_CALLER), apiName, AccessType.UseEntry.toString(), Permission.Allow, false); } } } } }