/* * 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.sentry.binding.solr.authz; import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION; import static org.apache.sentry.core.model.search.SearchConstants.SENTRY_SEARCH_SERVICE_DEFAULT; import static org.apache.sentry.core.model.search.SearchConstants.SENTRY_SEARCH_SERVICE_KEY; import static org.apache.sentry.core.model.search.SearchModelAuthorizable.AuthorizableType.Collection; import java.io.File; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.List; import java.util.Set; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.security.UserGroupInformation; import org.apache.sentry.SentryUserException; import org.apache.sentry.binding.solr.conf.SolrAuthzConf; import org.apache.sentry.binding.solr.conf.SolrAuthzConf.AuthzConfVars; import org.apache.sentry.core.common.Action; import org.apache.sentry.core.common.ActiveRoleSet; import org.apache.sentry.core.common.Subject; import org.apache.sentry.core.model.search.Collection; import org.apache.sentry.core.model.search.SearchModelAction; import org.apache.sentry.policy.common.PolicyEngine; import org.apache.sentry.provider.common.AuthorizationComponent; import org.apache.sentry.provider.common.AuthorizationProvider; import org.apache.sentry.provider.common.GroupMappingService; import org.apache.sentry.provider.common.HadoopGroupResourceAuthorizationProvider; import org.apache.sentry.provider.common.ProviderBackend; import org.apache.sentry.provider.db.generic.SentryGenericProviderBackend; import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClient; import org.apache.sentry.provider.db.generic.service.thrift.SentryGenericServiceClientFactory; import org.apache.sentry.provider.db.generic.service.thrift.TAuthorizable; import org.apache.sentry.provider.db.generic.service.thrift.TSentryGrantOption; import org.apache.sentry.provider.db.generic.service.thrift.TSentryPrivilege; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Strings; import com.google.common.collect.Lists; public class SolrAuthzBinding { private static final Logger LOG = LoggerFactory .getLogger(SolrAuthzBinding.class); private static final String[] HADOOP_CONF_FILES = {"core-site.xml", "hdfs-site.xml", "mapred-site.xml", "yarn-site.xml", "hadoop-site.xml"}; public static final String KERBEROS_ENABLED = "solr.hdfs.security.kerberos.enabled"; public static final String KERBEROS_KEYTAB = "solr.hdfs.security.kerberos.keytabfile"; public static final String KERBEROS_PRINCIPAL = "solr.hdfs.security.kerberos.principal"; private static final String kerberosEnabledProp = Strings.nullToEmpty(System.getProperty(KERBEROS_ENABLED)).trim(); private static final String keytabProp = Strings.nullToEmpty(System.getProperty(KERBEROS_KEYTAB)).trim(); private static final String principalProp = Strings.nullToEmpty(System.getProperty(KERBEROS_PRINCIPAL)).trim(); private static Boolean kerberosInit; private final SolrAuthzConf authzConf; private final AuthorizationProvider authProvider; private final GroupMappingService groupMapping; private ProviderBackend providerBackend; private Subject bindingSubject; public SolrAuthzBinding (SolrAuthzConf authzConf) throws Exception { this.authzConf = addHdfsPropsToConf(authzConf); this.authProvider = getAuthProvider(); this.groupMapping = authProvider.getGroupMapping(); /** * The Solr server principal will use the binding */ this.bindingSubject = new Subject(UserGroupInformation.getCurrentUser() .getShortUserName()); } // Instantiate the configured authz provider private AuthorizationProvider getAuthProvider() throws Exception { // get the provider class and resources from the authz config String authProviderName = authzConf.get(AuthzConfVars.AUTHZ_PROVIDER.getVar()); String resourceName = authzConf.get(AuthzConfVars.AUTHZ_PROVIDER_RESOURCE.getVar()); String providerBackendName = authzConf.get(AuthzConfVars.AUTHZ_PROVIDER_BACKEND.getVar()); String policyEngineName = authzConf.get(AuthzConfVars.AUTHZ_POLICY_ENGINE.getVar()); String serviceName = authzConf.get(SENTRY_SEARCH_SERVICE_KEY, SENTRY_SEARCH_SERVICE_DEFAULT); LOG.debug("Using authorization provider " + authProviderName + " with resource " + resourceName + ", policy engine " + policyEngineName + ", provider backend " + providerBackendName); // load the provider backend class if (kerberosEnabledProp.equalsIgnoreCase("true")) { initKerberos(keytabProp, principalProp); } else { // set configuration so that group mappings are properly setup even if // we don't use kerberos, for testing UserGroupInformation.setConfiguration(authzConf); } // the SearchProviderBackend is deleted in SENTRY-828, this is for the compatible with the // previous Sentry. if ("org.apache.sentry.provider.db.generic.service.thrift.SearchProviderBackend" .equals(providerBackendName)) { providerBackendName = SentryGenericProviderBackend.class.getName(); } Constructor<?> providerBackendConstructor = Class.forName(providerBackendName).getDeclaredConstructor(Configuration.class, String.class); providerBackendConstructor.setAccessible(true); providerBackend = (ProviderBackend) providerBackendConstructor.newInstance(new Object[] {authzConf, resourceName}); if (providerBackend instanceof SentryGenericProviderBackend) { ((SentryGenericProviderBackend) providerBackend) .setComponentType(AuthorizationComponent.Search); ((SentryGenericProviderBackend) providerBackend).setServiceName(serviceName); } // load the policy engine class Constructor<?> policyConstructor = Class.forName(policyEngineName).getDeclaredConstructor(ProviderBackend.class); policyConstructor.setAccessible(true); PolicyEngine policyEngine = (PolicyEngine) policyConstructor.newInstance(new Object[] {providerBackend}); // if unset, set the hadoop auth provider to use new groups, so we don't // conflict with the group mappings that may already be set up if (authzConf.get(HadoopGroupResourceAuthorizationProvider.USE_NEW_GROUPS) == null) { authzConf.setBoolean(HadoopGroupResourceAuthorizationProvider.USE_NEW_GROUPS ,true); } // load the authz provider class Constructor<?> constrctor = Class.forName(authProviderName).getDeclaredConstructor(Configuration.class, String.class, PolicyEngine.class); constrctor.setAccessible(true); return (AuthorizationProvider) constrctor.newInstance(new Object[] {authzConf, resourceName, policyEngine}); } /** * Authorize access to an index/collection * @param subject * @param collection * @param actions * @throws SentrySolrAuthorizationException */ public void authorizeCollection(Subject subject, Collection collection, Set<SearchModelAction> actions) throws SentrySolrAuthorizationException { boolean isDebug = LOG.isDebugEnabled(); if(isDebug) { LOG.debug("Going to authorize collection " + collection.getName() + " for subject " + subject.getName()); LOG.debug("Actions: " + actions); } if (!authProvider.hasAccess(subject, Arrays.asList(new Collection[] {collection}), actions, ActiveRoleSet.ALL)) { throw new SentrySolrAuthorizationException("User " + subject.getName() + " does not have privileges for " + collection.getName()); } } /** * Get the list of groups the user belongs to * @param user * @return list of groups the user belongs to * @deprecated use getRoles instead */ @Deprecated public Set<String> getGroups(String user) { return groupMapping.getGroups(user); } /** * Get the roles associated with the user * @param user * @return The roles associated with the user */ public Set<String> getRoles(String user) { return providerBackend.getRoles(getGroups(user), ActiveRoleSet.ALL); } private SolrAuthzConf addHdfsPropsToConf(SolrAuthzConf conf) throws IOException { String confDir = System.getProperty("solr.hdfs.confdir"); if (confDir != null && confDir.length() > 0) { File confDirFile = new File(confDir); if (!confDirFile.exists()) { throw new IOException("Resource directory does not exist: " + confDirFile.getAbsolutePath()); } if (!confDirFile.isDirectory()) { throw new IOException("Specified resource directory is not a directory" + confDirFile.getAbsolutePath()); } if (!confDirFile.canRead()) { throw new IOException("Resource directory must be readable by the Solr process: " + confDirFile.getAbsolutePath()); } for (String file : HADOOP_CONF_FILES) { if (new File(confDirFile, file).exists()) { conf.addResource(new Path(confDir, file)); } } } return conf; } /** * Initialize kerberos via UserGroupInformation. Will only attempt to login * during the first request, subsequent calls will have no effect. */ public void initKerberos(String keytabFile, String principal) { if (keytabFile == null || keytabFile.length() == 0) { throw new IllegalArgumentException("keytabFile required because kerberos is enabled"); } if (principal == null || principal.length() == 0) { throw new IllegalArgumentException("principal required because kerberos is enabled"); } synchronized (SolrAuthzBinding.class) { if (kerberosInit == null) { kerberosInit = Boolean.TRUE; final String authVal = authzConf.get(HADOOP_SECURITY_AUTHENTICATION); final String kerberos = "kerberos"; if (authVal != null && !authVal.equals(kerberos)) { throw new IllegalArgumentException(HADOOP_SECURITY_AUTHENTICATION + " set to: " + authVal + ", not kerberos, but attempting to " + " connect to HDFS via kerberos"); } // let's avoid modifying the supplied configuration, just to be conservative final Configuration ugiConf = new Configuration(authzConf); ugiConf.set(HADOOP_SECURITY_AUTHENTICATION, kerberos); UserGroupInformation.setConfiguration(ugiConf); LOG.info( "Attempting to acquire kerberos ticket with keytab: {}, principal: {} ", keytabFile, principal); try { UserGroupInformation.loginUserFromKeytab(principal, keytabFile); } catch (IOException ioe) { throw new RuntimeException(ioe); } LOG.info("Got Kerberos ticket"); } } } /** * SENTRY-478 * If the binding uses the searchProviderBackend, it can sync privilege with Sentry Service */ public boolean isSyncEnabled() { return providerBackend instanceof SentryGenericProviderBackend; } public SentryGenericServiceClient getClient() throws Exception { return SentryGenericServiceClientFactory.create(authzConf); } /** * Attempt to notify the Sentry service when deleting collection happened * @param collection * @throws SolrException */ public void deleteCollectionPrivilege(String collection) throws SentrySolrAuthorizationException { if (!isSyncEnabled()) { return; } SentryGenericServiceClient client = null; try { client = getClient(); TSentryPrivilege tPrivilege = new TSentryPrivilege(); tPrivilege.setComponent(AuthorizationComponent.Search); tPrivilege.setServiceName(authzConf.get(SENTRY_SEARCH_SERVICE_KEY, SENTRY_SEARCH_SERVICE_DEFAULT)); tPrivilege.setAction(Action.ALL); tPrivilege.setGrantOption(TSentryGrantOption.UNSET); List<TAuthorizable> authorizables = Lists.newArrayList(new TAuthorizable(Collection.name(), collection)); tPrivilege.setAuthorizables(authorizables); client.dropPrivilege(bindingSubject.getName(), AuthorizationComponent.Search, tPrivilege); } catch (SentryUserException ex) { throw new SentrySolrAuthorizationException("User " + bindingSubject.getName() + " can't delete privileges for collection " + collection); } catch (Exception ex) { throw new SentrySolrAuthorizationException("Unable to obtain client:" + ex.getMessage()); } finally { if (client != null) { client.close(); } } } }