| /* |
| * 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.hawq.ranger.authorization; |
| |
| import org.apache.commons.collections.CollectionUtils; |
| import org.apache.commons.collections.MapUtils; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.apache.hadoop.security.UserGroupInformation; |
| import org.apache.hawq.ranger.authorization.model.AuthorizationRequest; |
| import org.apache.hawq.ranger.authorization.model.AuthorizationResponse; |
| import org.apache.hawq.ranger.authorization.model.HawqPrivilege; |
| import org.apache.hawq.ranger.authorization.model.HawqResource; |
| import org.apache.hawq.ranger.authorization.model.ResourceAccess; |
| import org.apache.ranger.audit.provider.MiscUtil; |
| import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler; |
| import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl; |
| import org.apache.ranger.plugin.policyengine.RangerAccessResource; |
| import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; |
| import org.apache.ranger.plugin.policyengine.RangerAccessResult; |
| import org.apache.ranger.plugin.service.RangerBasePlugin; |
| |
| import java.util.*; |
| |
| import static org.apache.hawq.ranger.authorization.Utils.APPID; |
| import static org.apache.hawq.ranger.authorization.Utils.HAWQ; |
| |
| /** |
| * Authorizer implementation that uses Ranger to make access decision. Implemented as a singleton. |
| */ |
| public class RangerHawqAuthorizer implements HawqAuthorizer { |
| |
| private static final Log LOG = LogFactory.getLog(RangerHawqAuthorizer.class); |
| |
| private static final RangerHawqAuthorizer INSTANCE = new RangerHawqAuthorizer(); |
| |
| private RangerBasePlugin rangerPlugin; |
| |
| /** |
| * Returns the instance of the RangerHawqAuthorizer. |
| * @return the singleton instance |
| */ |
| public static RangerHawqAuthorizer getInstance() { |
| return INSTANCE; |
| } |
| |
| /** |
| * Constructor. Initializes Ranger Base Plugin to fetch policies from Ranger. |
| */ |
| private RangerHawqAuthorizer() { |
| |
| LOG.info("********** Initializing RangerHawqAuthorizer **********"); |
| |
| String instance = Utils.getInstanceName(); |
| |
| LOG.info(String.format("Initializing RangerBasePlugin for service %s:%s:%s", HAWQ, instance, APPID)); |
| rangerPlugin = new RangerBasePlugin(HAWQ, APPID); |
| rangerPlugin.setResultProcessor(new RangerDefaultAuditHandler()); |
| rangerPlugin.init(); |
| LOG.info(String.format("********** Initialized RangerBasePlugin for service %s:%s:%s **********", HAWQ, instance, APPID)); |
| } |
| |
| @Override |
| public AuthorizationResponse isAccessAllowed(AuthorizationRequest request) { |
| |
| // validate request to make sure no data is missing |
| validateRequest(request); |
| |
| // prepare response object |
| AuthorizationResponse response = new AuthorizationResponse(); |
| response.setRequestId(request.getRequestId()); |
| Set<ResourceAccess> access = new HashSet<>(); |
| response.setAccess(access); |
| |
| // iterate over resource requests, augment processed ones with the decision and add to the response |
| for (ResourceAccess resourceAccess : request.getAccess()) { |
| boolean accessAllowed = authorizeResource(resourceAccess, request.getUser(), request.getClientIp(), request.getContext()); |
| resourceAccess.setAllowed(accessAllowed); |
| access.add(resourceAccess); |
| } |
| |
| return response; |
| } |
| |
| /** |
| * Authorizes access to a single resource for a given user. |
| * |
| * @param resourceAccess resource to authorize access to |
| * @param user user requesting authorization |
| * @return true if access is authorized, false otherwise |
| */ |
| private boolean authorizeResource(ResourceAccess resourceAccess, String user, String clientIp, String context) { |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(String.format("Request: access for user=%s to resource=%s with privileges=%s", |
| user, resourceAccess.getResource(), resourceAccess.getPrivileges())); |
| } |
| |
| RangerAccessResourceImpl rangerResource = new RangerAccessResourceImpl(); |
| //resource.setOwnerUser(); |
| for (Map.Entry<HawqResource, String> resourceEntry : resourceAccess.getResource().entrySet()) { |
| rangerResource.setValue(resourceEntry.getKey().name(), resourceEntry.getValue()); |
| } |
| // determine user groups |
| Set<String> userGroups = getUserGroups(user); |
| |
| boolean accessAllowed = true; |
| // iterate over all privileges requested |
| for (HawqPrivilege privilege : resourceAccess.getPrivileges()) { |
| boolean privilegeAuthorized = authorizeResourcePrivilege(rangerResource, privilege.name(), user, userGroups, clientIp, context); |
| // ALL model of evaluation -- all privileges must be authorized for access to be allowed |
| if (!privilegeAuthorized) { |
| accessAllowed = false; |
| break; // terminate early if even a single privilege is not authorized |
| } |
| } |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(String.format("Decision: accessAllowed=%s for user=%s to resource=%s with privileges=%s", |
| accessAllowed, user, resourceAccess.getResource(), resourceAccess.getPrivileges())); |
| } |
| |
| return accessAllowed; |
| } |
| |
| /** |
| * Authorizes access of a given type (privilege) to a single resource for a given user. |
| * |
| * @param rangerResource resource to authorize access to |
| * @param accessType privilege requested for a given resource |
| * @param user user requesting authorization |
| * @param userGroups groups a user belongs to |
| * @return true if access is authorized, false otherwise |
| */ |
| private boolean authorizeResourcePrivilege(RangerAccessResource rangerResource, String accessType, String user, Set<String> userGroups, String clientIp, String context) { |
| |
| Map<String, String> resourceMap = rangerResource.getAsMap(); |
| String database = resourceMap.get(HawqResource.database.name()); |
| String schema = resourceMap.get(HawqResource.schema.name()); |
| int resourceSize = resourceMap.size(); |
| |
| // special handling for non-leaf policies |
| if (accessType.equals(HawqPrivilege.create.name()) && database != null && schema == null && resourceSize == 1) { |
| accessType = HawqPrivilege.create_schema.toValue(); |
| LOG.debug("accessType mapped to: create-schema"); |
| } else if (accessType.equals(HawqPrivilege.usage.name()) && database != null && schema != null && resourceSize == 2) { |
| accessType = HawqPrivilege.usage_schema.toValue(); |
| LOG.debug("accessType mapped to: usage-schema"); |
| } |
| |
| RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl(rangerResource, accessType, user, userGroups); |
| rangerRequest.setAccessTime(new Date()); |
| rangerRequest.setAction(accessType); |
| rangerRequest.setClientIPAddress(clientIp); |
| rangerRequest.setRequestData(context); |
| RangerAccessResult result = rangerPlugin.isAccessAllowed(rangerRequest); |
| boolean accessAllowed = result != null && result.getIsAllowed(); |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(String.format("--- RangerDecision: accessAllowed=%s for user=%s to resource=%s with privileges=%s, result present=%s", |
| accessAllowed, user, rangerResource.getAsString(), accessType, result!=null)); |
| } |
| |
| return accessAllowed; |
| } |
| |
| /** |
| * Validates that authorization requests do not have any missing data. |
| * |
| * @param request authorization request |
| * @throws IllegalArgumentException if any data is missing |
| */ |
| private void validateRequest(AuthorizationRequest request) { |
| LOG.debug("Validating authorization request"); |
| |
| if (request == null) { |
| throw new IllegalArgumentException("request is null"); |
| } |
| |
| if (request.getRequestId() == null) { |
| throw new IllegalArgumentException("requestId field is missing or null in the request"); |
| } |
| |
| if (StringUtils.isEmpty(request.getUser())) { |
| throw new IllegalArgumentException("user field is missing or empty in the request"); |
| } |
| |
| if (StringUtils.isEmpty(request.getClientIp())) { |
| throw new IllegalArgumentException("clientIp field is missing or empty in the request"); |
| } |
| |
| if (StringUtils.isEmpty(request.getContext())) { |
| throw new IllegalArgumentException("context field is missing or empty in the request"); |
| } |
| |
| Set<ResourceAccess> accessSet = request.getAccess(); |
| if (CollectionUtils.isEmpty(accessSet)) { |
| throw new IllegalArgumentException("access field is missing or empty in the request"); |
| } |
| |
| for (ResourceAccess access : accessSet) { |
| validateResourceAccess(access); |
| } |
| |
| LOG.debug("Successfully validated authorization request"); |
| } |
| |
| /** |
| * Validates that resource access does not have any missing data. |
| * |
| * @param access resource access data |
| * @throws IllegalArgumentException if any data is missing |
| */ |
| private void validateResourceAccess(ResourceAccess access) { |
| Map<HawqResource, String> resourceMap = access.getResource(); |
| if (MapUtils.isEmpty(resourceMap)) { |
| throw new IllegalArgumentException("resource field is missing or empty in the request"); |
| } |
| for (Map.Entry<HawqResource, String> resourceEntry : resourceMap.entrySet()) { |
| if (StringUtils.isEmpty(resourceEntry.getValue())) { |
| throw new IllegalArgumentException( |
| String.format("resource value is missing for key=%s in the request", resourceEntry.getKey()) |
| ); |
| } |
| } |
| if (CollectionUtils.isEmpty(access.getPrivileges())) { |
| throw new IllegalArgumentException("set of privileges is missing empty in the request"); |
| } |
| } |
| |
| /** |
| * Returns a set of groups the user belongs to |
| * @param user user name |
| * @return set of groups for the user |
| */ |
| private Set<String> getUserGroups(String user) { |
| String[] userGroups = null; |
| try { |
| UserGroupInformation ugi = UserGroupInformation.createRemoteUser(user); |
| userGroups = ugi.getGroupNames(); |
| if (LOG.isDebugEnabled()) { |
| LOG.debug(String.format("Determined user=%s belongs to groups=%s", user, Arrays.toString(userGroups))); |
| } |
| } catch (Throwable e) { |
| LOG.warn("Failed to determine groups for user=" + user, e); |
| } |
| return userGroups == null ? Collections.<String>emptySet() : new HashSet<String>(Arrays.asList(userGroups)); |
| } |
| |
| |
| |
| /** |
| * Sets an instance of the Ranger Plugin for testing. |
| * |
| * @param plugin plugin instance to use while testing |
| */ |
| void setRangerPlugin(RangerBasePlugin plugin) { |
| rangerPlugin = plugin; |
| } |
| |
| /** |
| * Returns the instance of the Ranger Plugin for testing. |
| * |
| * @return BaseRangerPlugin instance |
| */ |
| RangerBasePlugin getRangerPlugin() { |
| return rangerPlugin; |
| } |
| } |