You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ranger.apache.org by sp...@apache.org on 2019/09/19 22:19:03 UTC

[ranger] branch master updated: RANGER-2393: Document level authorization support for solr

This is an automated email from the ASF dual-hosted git repository.

spolavarapu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 8ed1b33  RANGER-2393: Document level authorization support for solr
8ed1b33 is described below

commit 8ed1b3385abfdb1e725f10d131bee7d339bb8fd4
Author: Sailaja Polavarapu <sp...@cloudera.com>
AuthorDate: Thu Sep 19 15:18:48 2019 -0700

    RANGER-2393: Document level authorization support for solr
---
 .../solr/authorizer/RangerSolrAuthorizer.java      | 193 +++++++++++++++++++--
 .../solr/authorizer/RangerSolrAuthorizer.java      |  92 +++++++++-
 .../java/org/apache/ranger/biz/RoleDBStore.java    |  22 ++-
 .../java/org/apache/ranger/db/XXServiceDefDao.java |  12 ++
 .../main/resources/META-INF/jpa_named_queries.xml  |   4 +
 5 files changed, 308 insertions(+), 15 deletions(-)

diff --git a/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
index 48d4fb7..46d0f66 100644
--- a/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
+++ b/plugin-solr/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import com.google.common.base.Joiner;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -38,16 +39,40 @@ import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
 import org.apache.ranger.plugin.policyengine.RangerAccessResult;
 import org.apache.ranger.plugin.service.RangerBasePlugin;
 import org.apache.ranger.plugin.util.RangerPerfTracer;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.SearchComponent;
+import org.apache.solr.request.LocalSolrQueryRequest;
+import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.security.AuthorizationContext.RequestType;
 import org.apache.solr.security.AuthorizationPlugin;
 import org.apache.solr.security.AuthorizationResponse;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.AuthorizationContext.CollectionRequest;
 
-public class RangerSolrAuthorizer implements AuthorizationPlugin {
+import javax.servlet.http.HttpServletRequest;
+
+public class RangerSolrAuthorizer extends SearchComponent implements AuthorizationPlugin {
 	private static final Log logger = LogFactory
 			.getLog(RangerSolrAuthorizer.class);
 	private static final Log PERF_SOLRAUTH_REQUEST_LOG = RangerPerfTracer.getPerfLogger("solrauth.request");
+	public static final String SUPERUSER = System.getProperty("solr.authorization.superuser", "solr");
+
+	public static final String AUTH_FIELD_PROP = "rangerAuthField";
+	public static final String DEFAULT_AUTH_FIELD = "ranger_auth";
+	public static final String ALL_ROLES_TOKEN_PROP = "allRolesToken";
+	public static final String ENABLED_PROP = "enabled";
+	public static final String MODE_PROP = "matchMode";
+	public static final String DEFAULT_MODE_PROP = MatchType.DISJUNCTIVE.toString();
+
+	public static final String ALLOW_MISSING_VAL_PROP = "allow_missing_val";
+	public static final String TOKEN_COUNT_PROP = "tokenCountField";
+	public static final String DEFAULT_TOKEN_COUNT_FIELD_PROP = "ranger_auth_count";
+	public static final String QPARSER_PROP = "qParser";
 
 	public static final String PROP_USE_PROXY_IP = "xasecure.solr.use_proxy_ip";
 	public static final String PROP_PROXY_IP_HEADER = "xasecure.solr.proxy_ip_header";
@@ -69,9 +94,39 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 	String proxyIPHeader = "HTTP_X_FORWARDED_FOR";
 	String solrAppName = "Client";
 
+	private String authField;
+	private String allRolesToken;
+	private boolean enabled;
+	private MatchType matchMode;
+	private String tokenCountField;
+	private boolean allowMissingValue;
+	private String qParserName;
+
+	private enum MatchType {
+		DISJUNCTIVE,
+		CONJUNCTIVE
+	}
+
 	public RangerSolrAuthorizer() {
 		logger.info("RangerSolrAuthorizer()");
+	}
 
+	@Override
+	public void init(NamedList args) {
+		SolrParams params = args.toSolrParams();
+		this.authField = params.get(AUTH_FIELD_PROP, DEFAULT_AUTH_FIELD);
+		this.allRolesToken = params.get(ALL_ROLES_TOKEN_PROP, "");
+		this.enabled = params.getBool(ENABLED_PROP, false);
+		this.matchMode = MatchType.valueOf(params.get(MODE_PROP, DEFAULT_MODE_PROP).toUpperCase());
+
+		if (this.matchMode == MatchType.CONJUNCTIVE) {
+			this.qParserName = params.get(QPARSER_PROP, "subset").trim();
+			this.allowMissingValue = params.getBool(ALLOW_MISSING_VAL_PROP, false);
+			this.tokenCountField = params.get(TOKEN_COUNT_PROP, DEFAULT_TOKEN_COUNT_FIELD_PROP);
+		}
+		logger.info("RangerSolrAuthorizer.init(): authField={" + authField + "}, allRolesToken={" + allRolesToken +
+				"}, enabled={" + enabled + "}, matchType={" + matchMode + "}, qParserName={" + qParserName +
+				"}, allowMissingValue={" + allowMissingValue + "}, tokenCountField={" + tokenCountField + "}");
 	}
 
 	/*
@@ -125,15 +180,6 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 		}
 	}
 
-	private void authToJAASFile() {
-		try {
-			MiscUtil.setUGIFromJAASConfig(solrAppName);
-			logger.info("LoginUser=" + MiscUtil.getUGILoginUser());
-		} catch (Throwable t) {
-			logger.error("Error authenticating for appName=" + solrAppName, t);
-		}
-	}
-
 	/*
 	 * (non-Javadoc)
 	 *
@@ -265,6 +311,56 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 		return response;
 	}
 
+	@Override
+	public void prepare(ResponseBuilder rb) throws IOException {
+		if (!enabled) {
+			return;
+		}
+
+		String userName = getUserName(rb.req);
+		if (SUPERUSER.equals(userName)) {
+			return;
+		}
+
+		Set<String> roles = getRolesForUser(userName);
+		if (roles != null && !roles.isEmpty()) {
+			String filterQuery;
+			if (matchMode == MatchType.DISJUNCTIVE) {
+				filterQuery = getDisjunctiveFilterQueryStr(roles);
+			} else {
+				filterQuery = getConjunctiveFilterQueryStr(roles);
+			}
+			ModifiableSolrParams newParams = new ModifiableSolrParams(rb.req.getParams());
+			newParams.add("fq", filterQuery);
+			rb.req.setParams(newParams);
+			if (logger.isDebugEnabled()) {
+				logger.debug("Adding filter query {" + filterQuery + "} for user {" + userName + "} with roles {" + roles + "}");
+			}
+
+		} else {
+			throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED,
+					"Request from user: " + userName + " rejected because user is not associated with any roles");
+		}
+	}
+
+	@Override
+	public void process(ResponseBuilder rb) throws IOException {
+	}
+
+	@Override
+	public String getDescription() {
+		return "Handle Query Document Authorization";
+	}
+
+	private void authToJAASFile() {
+		try {
+			MiscUtil.setUGIFromJAASConfig(solrAppName);
+			logger.info("LoginUser=" + MiscUtil.getUGILoginUser());
+		} catch (Throwable t) {
+			logger.error("Error authenticating for appName=" + solrAppName, t);
+		}
+	}
+
 	/**
 	 * @param context
 	 */
@@ -424,4 +520,81 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 		return accessType;
 	}
 
+	private void addDisjunctiveRawClause(StringBuilder builder, String value) {
+		// requires a space before the first term, so the
+		// default lucene query parser will be used
+		builder.append(" {!raw f=").append(authField).append(" v=")
+				.append(value).append("}");
+	}
+
+	private String getDisjunctiveFilterQueryStr(Set<String> roles) {
+		if (roles != null && !roles.isEmpty()) {
+			StringBuilder builder = new StringBuilder();
+			for (String role : roles) {
+				addDisjunctiveRawClause(builder, role);
+			}
+			if (allRolesToken != null && !allRolesToken.isEmpty()) {
+				addDisjunctiveRawClause(builder, allRolesToken);
+			}
+			return builder.toString();
+		}
+		return null;
+	}
+
+	private String getConjunctiveFilterQueryStr(Set<String> roles) {
+		StringBuilder filterQuery = new StringBuilder();
+		filterQuery
+				.append(" {!").append(qParserName)
+				.append(" set_field=\"").append(authField).append("\"")
+				.append(" set_value=\"").append(Joiner.on(',').join(roles.iterator())).append("\"")
+				.append(" count_field=\"").append(tokenCountField).append("\"");
+		if (allRolesToken != null && !allRolesToken.equals("")) {
+			filterQuery.append(" wildcard_token=\"").append(allRolesToken).append("\"");
+		}
+		filterQuery.append(" allow_missing_val=").append(allowMissingValue).append(" }");
+
+		return filterQuery.toString();
+	}
+
+	private Set<String> getRolesForUser(String name) {
+		if (solrPlugin.getCurrentRangerAuthContext() != null) {
+			return solrPlugin.getCurrentRangerAuthContext().getRolesFromUserAndGroups(name, getGroupsForUser(name));
+		}
+		else {
+			logger.info("Current Ranger Auth Context is null!!");
+			return null;
+		}
+	}
+
+	/**
+	 * This method return the user name from the provided {@linkplain SolrQueryRequest}
+	 */
+	private final String getUserName(SolrQueryRequest req) {
+		// If a local request, treat it like a super user request; i.e. it is equivalent to an
+		// http request from the same process.
+		if (req instanceof LocalSolrQueryRequest) {
+			return SUPERUSER;
+		}
+
+		SolrCore solrCore = req.getCore();
+
+		HttpServletRequest httpServletRequest = (HttpServletRequest) req.getContext().get("httpRequest");
+		if (httpServletRequest == null) {
+			StringBuilder builder = new StringBuilder("Unable to locate HttpServletRequest");
+			if (solrCore != null && !solrCore.getSolrConfig().getBool("requestDispatcher/requestParsers/@addHttpRequestToContext", true)) {
+				builder.append(", ensure requestDispatcher/requestParsers/@addHttpRequestToContext is set to true in solrconfig.xml");
+			}
+			throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED, builder.toString());
+		}
+
+		String userName = httpServletRequest.getRemoteUser();
+		if (userName == null) {
+			userName = MiscUtil.getShortNameFromPrincipalName(httpServletRequest.getUserPrincipal().getName());
+		}
+		if (userName == null) {
+			throw new SolrException(SolrException.ErrorCode.UNAUTHORIZED, "This request is not authenticated.");
+		}
+
+		return userName;
+	}
 }
diff --git a/ranger-solr-plugin-shim/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java b/ranger-solr-plugin-shim/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
index 4cfa7e1..cfede46 100644
--- a/ranger-solr-plugin-shim/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
+++ b/ranger-solr-plugin-shim/src/main/java/org/apache/ranger/authorization/solr/authorizer/RangerSolrAuthorizer.java
@@ -26,11 +26,14 @@ import java.util.Map;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.ranger.plugin.classloader.RangerPluginClassLoader;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.handler.component.ResponseBuilder;
+import org.apache.solr.handler.component.SearchComponent;
 import org.apache.solr.security.AuthorizationContext;
 import org.apache.solr.security.AuthorizationPlugin;
 import org.apache.solr.security.AuthorizationResponse;
 
-public class RangerSolrAuthorizer implements AuthorizationPlugin {
+public class RangerSolrAuthorizer extends SearchComponent implements AuthorizationPlugin {
 	private static final Log LOG = LogFactory
 			.getLog(RangerSolrAuthorizer.class);
 
@@ -38,6 +41,7 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 	private static final String   RANGER_SOLR_AUTHORIZER_IMPL_CLASSNAME   = "org.apache.ranger.authorization.solr.authorizer.RangerSolrAuthorizer";
 
 	private 		AuthorizationPlugin  	rangerSolrAuthorizerImpl 	  = null;
+	private			SearchComponent			rangerSearchComponentImpl	  = null;
 	private static	RangerPluginClassLoader rangerPluginClassLoader  	  = null;
 
 	public RangerSolrAuthorizer() {
@@ -65,7 +69,9 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 
 			activatePluginClassLoader();
 
-			rangerSolrAuthorizerImpl = cls.newInstance();
+			Object impl = cls.newInstance();
+			rangerSolrAuthorizerImpl = (AuthorizationPlugin)impl;
+			rangerSearchComponentImpl = (SearchComponent)impl;
 		} catch (Exception e) {
 			// check what need to be done
 			LOG.error("Error Enabling RangerSolrPlugin", e);
@@ -98,6 +104,24 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 	}
 
 	@Override
+	public void init(NamedList args) {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerSolrAuthorizer.init(" + args.toString() + ")");
+		}
+		try {
+			activatePluginClassLoader();
+
+			rangerSearchComponentImpl.init(args);
+		} finally {
+			deactivatePluginClassLoader();
+		}
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerSolrAuthorizer.init(" + args.toString() + ")");
+		}
+	}
+
+	@Override
 	public void close() throws IOException {
 		if(LOG.isDebugEnabled()) {
 			LOG.debug("==> RangerSolrAuthorizer.close(Resource)");
@@ -119,7 +143,7 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 	@Override
 	public AuthorizationResponse authorize(AuthorizationContext context) {
 		if(LOG.isDebugEnabled()) {
-			LOG.debug("==> RangerSolrAuthorizer.init(context)");
+			LOG.debug("==> RangerSolrAuthorizer.authorize(context)");
 		}
 		AuthorizationResponse ret = null;
 		try {
@@ -131,12 +155,72 @@ public class RangerSolrAuthorizer implements AuthorizationPlugin {
 		}
 
 		if(LOG.isDebugEnabled()) {
-			LOG.debug("<== RangerSolrAuthorizer.init(context)");
+			LOG.debug("<== RangerSolrAuthorizer.authorize(context)");
 		}
 
 		return ret;
 	}
 
+	@Override
+	public void prepare(ResponseBuilder responseBuilder) throws IOException {
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerSolrAuthorizer.prepare()");
+		}
+
+		try {
+			activatePluginClassLoader();
+
+			rangerSearchComponentImpl.prepare(responseBuilder);
+		} finally {
+			deactivatePluginClassLoader();
+		}
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerSolrAuthorizer.prepare()");
+		}
+	}
+
+	@Override
+	public void process(ResponseBuilder responseBuilder) throws IOException {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerSolrAuthorizer.process()");
+		}
+		try {
+			activatePluginClassLoader();
+
+			rangerSearchComponentImpl.process(responseBuilder);
+		} finally {
+			deactivatePluginClassLoader();
+		}
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerSolrAuthorizer.process()");
+		}
+
+	}
+
+	@Override
+	public String getDescription() {
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("==> RangerSolrAuthorizer.getDescription()");
+		}
+
+		String ret = null;
+		try {
+			activatePluginClassLoader();
+
+			ret = rangerSearchComponentImpl.getDescription();
+		} finally {
+			deactivatePluginClassLoader();
+		}
+
+		if(LOG.isDebugEnabled()) {
+			LOG.debug("<== RangerSolrAuthorizer.getDescription()");
+		}
+		return ret;
+	}
+
 	private void activatePluginClassLoader() {
 		if(rangerPluginClassLoader != null) {
 			rangerPluginClassLoader.activate();
diff --git a/security-admin/src/main/java/org/apache/ranger/biz/RoleDBStore.java b/security-admin/src/main/java/org/apache/ranger/biz/RoleDBStore.java
index 28b2c11..7a67e9c 100644
--- a/security-admin/src/main/java/org/apache/ranger/biz/RoleDBStore.java
+++ b/security-admin/src/main/java/org/apache/ranger/biz/RoleDBStore.java
@@ -29,6 +29,7 @@ import org.apache.commons.collections.ListUtils;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.ranger.authorization.hadoop.config.RangerConfiguration;
 import org.apache.ranger.common.MessageEnums;
 import org.apache.ranger.common.RESTErrorUtil;
 import org.apache.ranger.db.RangerDaoManager;
@@ -226,8 +227,27 @@ public class RoleDBStore implements RoleStore {
 
     public List<RangerRole> getRoles(Long serviceId) {
         List<RangerRole> ret = ListUtils.EMPTY_LIST;
+
         if (serviceId != null) {
-            List<XXRole> rolesFromDb = daoMgr.getXXRole().findByServiceId(serviceId);
+            String       serviceTypeName            = daoMgr.getXXServiceDef().findServiceDefTypeByServiceId(serviceId);
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Service Type for serviceId (" + serviceId + ") = " + serviceTypeName);
+            }
+            String       serviceTypesToGetAllRoles  = RangerConfiguration.getInstance().get("ranger.admin.service.types.for.returning.all.roles", "solr");
+
+            boolean      getAllRoles                = false;
+            if (StringUtils.isNotEmpty(serviceTypesToGetAllRoles)) {
+                String[] allRolesServiceTypes = StringUtils.split(serviceTypesToGetAllRoles, ",");
+                if (allRolesServiceTypes != null) {
+                    for (String allRolesServiceType : allRolesServiceTypes) {
+                        if (StringUtils.equalsIgnoreCase(serviceTypeName, allRolesServiceType)) {
+                            getAllRoles = true;
+                            break;
+                        }
+                    }
+                }
+            }
+            List<XXRole> rolesFromDb = getAllRoles ? daoMgr.getXXRole().getAll() : daoMgr.getXXRole().findByServiceId(serviceId);
             if (CollectionUtils.isNotEmpty(rolesFromDb)) {
                 ret = new ArrayList<>();
                 for (XXRole xxRole : rolesFromDb) {
diff --git a/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java b/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
index 835e5fe..beef5bf 100644
--- a/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
+++ b/security-admin/src/main/java/org/apache/ranger/db/XXServiceDefDao.java
@@ -78,4 +78,16 @@ public class XXServiceDefDao extends BaseDao<XXServiceDef> {
                 }
                 return serviceType;
         }
+
+	public String findServiceDefTypeByServiceId(Long serviceId) {
+		String serviceType = null;
+		try {
+			serviceType = getEntityManager()
+					.createNamedQuery("XXServiceDef.findServiceDefNameByServiceId", String.class)
+					.setParameter("id", serviceId).getSingleResult();
+		} catch (NoResultException e) {
+			return null;
+		}
+		return serviceType;
+	}
 }
\ No newline at end of file
diff --git a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
index 3c3d1de..078588a 100755
--- a/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
+++ b/security-admin/src/main/resources/META-INF/jpa_named_queries.xml
@@ -325,6 +325,10 @@
                 <query>select obj.name from XXServiceDef obj, XXService serviceObj where obj.id = serviceObj.type and serviceObj.name = :name</query>
         </named-query>
 
+	<named-query name="XXServiceDef.findServiceDefNameByServiceId">
+		<query>select obj.name from XXServiceDef obj, XXService serviceObj where obj.id = serviceObj.type and serviceObj.id = :id</query>
+	</named-query>
+
 	<!-- XXResourceDef -->
 	<named-query name="XXResourceDef.findByNameAndDefId">
 		<query>select obj from XXResourceDef obj where obj.name = :name and obj.defId = :defId order by obj.level</query>