Java tutorial
/* * 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; } }