SENTRY-1475: Integrate Sentry with Solr 7 authorization framework. (Hrishikesh Gadre, reviewed by Kalyan Kumar Kalvagadda)
Project: http://git-wip-us.apache.org/repos/asf/sentry/repo Commit: http://git-wip-us.apache.org/repos/asf/sentry/commit/e62fa28d Tree: http://git-wip-us.apache.org/repos/asf/sentry/tree/e62fa28d Diff: http://git-wip-us.apache.org/repos/asf/sentry/diff/e62fa28d Branch: refs/heads/akolb-cli Commit: e62fa28d0fe342a8a154e0f50221cf34a658a045 Parents: 8a80d60 Author: Kalyan Kumar Kalvagadda <[email protected]> Authored: Mon Nov 13 08:37:22 2017 -0600 Committer: Kalyan Kumar Kalvagadda <[email protected]> Committed: Mon Nov 13 08:37:22 2017 -0600 ---------------------------------------------------------------------- pom.xml | 12 +- sentry-binding/sentry-binding-solr/pom.xml | 11 +- .../authz/SentrySolrAuthorizationException.java | 25 - .../solr/authz/SentrySolrPluginImpl.java | 408 ++++++++ .../binding/solr/authz/SolrAuthzBinding.java | 268 ++---- .../binding/solr/authz/SolrAuthzUtil.java | 271 ++++++ .../sentry/binding/solr/conf/SolrAuthzConf.java | 14 +- .../org/apache/solr/sentry/AuditLogger.java | 92 ++ .../RollingFileWithoutDeleteAppender.java | 182 ++++ .../sentry/binding/solr/HdfsTestUtil.java | 50 +- .../binding/solr/TestSolrAuthzBinding.java | 338 ++++--- .../solr/AbstractTestSearchPolicyEngine.java | 129 --- .../solr/AbstractTestSolrPolicyEngine.java | 129 +++ .../policy/solr/SearchPolicyTestUtil.java | 45 - .../sentry/policy/solr/SolrPolicyTestUtil.java | 45 + .../solr/TestCollectionRequiredInRole.java | 64 -- ...SearchAuthorizationProviderGeneralCases.java | 193 ---- ...SearchAuthorizationProviderSpecialCases.java | 84 -- .../solr/TestSearchModelAuthorizables.java | 54 -- .../policy/solr/TestSearchPolicyEngineDFS.java | 74 -- .../solr/TestSearchPolicyEngineLocalFS.java | 43 - .../policy/solr/TestSearchPolicyNegative.java | 101 -- ...stSolrAuthorizationProviderGeneralCases.java | 196 ++++ ...stSolrAuthorizationProviderSpecialCases.java | 84 ++ .../policy/solr/TestSolrModelAuthorizables.java | 54 ++ .../policy/solr/TestSolrPolicyEngineDFS.java | 74 ++ .../solr/TestSolrPolicyEngineLocalFS.java | 43 + .../policy/solr/TestSolrPolicyNegative.java | 101 ++ .../solr/TestCommonPrivilegeForSearch.java | 221 ----- .../solr/TestCommonPrivilegeForSolr.java | 293 ++++++ .../src/test/resources/test-authz-provider.ini | 4 +- sentry-core/pom.xml | 2 +- sentry-core/sentry-core-model-search/pom.xml | 43 - .../sentry/core/model/search/Collection.java | 51 - .../apache/sentry/core/model/search/Field.java | 54 -- .../core/model/search/SearchActionFactory.java | 80 -- .../core/model/search/SearchConstants.java | 35 - .../core/model/search/SearchModelAction.java | 39 - .../model/search/SearchModelAuthorizable.java | 29 - .../model/search/SearchModelAuthorizables.java | 50 - .../core/model/search/SearchPrivilegeModel.java | 60 -- .../AbstractSearchPrivilegeValidator.java | 52 -- .../CollectionRequiredInPrivilege.java | 43 - .../sentry/core/search/TestCollection.java | 50 - .../core/search/TestSearchBitFieldAction.java | 73 -- sentry-core/sentry-core-model-solr/pom.xml | 43 + .../sentry/core/model/solr/AdminOperation.java | 42 + .../sentry/core/model/solr/Collection.java | 29 + .../apache/sentry/core/model/solr/Config.java | 27 + .../apache/sentry/core/model/solr/Field.java | 30 + .../apache/sentry/core/model/solr/Schema.java | 27 + .../core/model/solr/SolrActionFactory.java | 80 ++ .../sentry/core/model/solr/SolrConstants.java | 39 + .../sentry/core/model/solr/SolrModelAction.java | 39 + .../core/model/solr/SolrModelAuthorizable.java | 60 ++ .../core/model/solr/SolrModelAuthorizables.java | 58 ++ .../core/model/solr/SolrPrivilegeModel.java | 66 ++ .../solr/validator/SolrPrivilegeValidator.java | 101 ++ .../apache/sentry/core/solr/TestCollection.java | 49 + .../core/solr/TestSolrBitFieldAction.java | 73 ++ sentry-dist/pom.xml | 14 +- sentry-provider/sentry-provider-db/pom.xml | 15 +- .../persistent/PrivilegeOperatePersistence.java | 4 +- .../tools/GenericPrivilegeConverter.java | 8 +- .../db/generic/tools/SentryConfigToolSolr.java | 4 +- .../TestPrivilegeOperatePersistence.java | 108 +-- .../persistent/TestSentryGMPrivilege.java | 54 +- .../service/persistent/TestSentryRole.java | 2 +- .../TestSentryGenericPolicyProcessor.java | 14 +- .../TestSentryGenericServiceIntegration.java | 36 +- .../generic/tools/TestSentryConfigToolSolr.java | 2 +- .../db/generic/tools/TestSentryShellSolr.java | 2 +- sentry-solr/pom.xml | 1 - sentry-solr/solr-sentry-core/pom.xml | 58 -- .../org/apache/solr/sentry/AuditLogger.java | 97 -- .../RollingFileWithoutDeleteAppender.java | 175 ---- .../solr/sentry/SecureRequestHandlerUtil.java | 83 -- .../SentryIndexAuthorizationSingleton.java | 255 ----- sentry-solr/solr-sentry-handlers/pom.xml | 10 +- .../SecureDocumentAnalysisRequestHandler.java | 33 - .../SecureFieldAnalysisRequestHandler.java | 33 - .../solr/handler/SecureRealTimeGetHandler.java | 36 - .../solr/handler/SecureReplicationHandler.java | 38 - .../solr/handler/admin/SecureAdminHandlers.java | 183 ---- .../handler/admin/SecureCollectionsHandler.java | 89 -- .../handler/admin/SecureCoreAdminHandler.java | 181 ---- .../solr/handler/admin/SecureInfoHandler.java | 36 - .../QueryDocAuthorizationComponent.java | 116 ++- .../QueryIndexAuthorizationComponent.java | 79 -- .../component/SecureRealTimeGetComponent.java | 356 ------- .../UpdateIndexAuthorizationProcessor.java | 103 --- ...pdateIndexAuthorizationProcessorFactory.java | 41 - .../lib/classes/empty-file-main-lib.txt | 1 - .../handler/TestSecureAnalysisHandlers.java | 82 -- .../handler/TestSecureReplicationHandler.java | 63 -- .../handler/admin/SecureAdminHandlersTest.java | 176 ---- .../admin/SecureCollectionsHandlerTest.java | 84 -- .../admin/SecureCoreAdminHandlerTest.java | 209 ----- .../handler/admin/SecureInfoHandlerTest.java | 101 -- .../QueryDocAuthorizationComponentTest.java | 265 ------ .../QueryIndexAuthorizationComponentTest.java | 127 --- .../SentryIndexAuthorizationSingletonTest.java | 256 ----- .../sentry/SentrySingletonTestInstance.java | 93 -- .../org/apache/solr/sentry/SentryTestBase.java | 187 ---- .../UpdateIndexAuthorizationProcessorTest.java | 193 ---- sentry-tests/sentry-tests-solr/pom.xml | 211 ++++- .../e2e/solr/AbstractSolrSentryTestBase.java | 923 ------------------- .../e2e/solr/AbstractSolrSentryTestCase.java | 600 ++++++++++++ .../tests/e2e/solr/DocLevelGenerator.java | 16 +- .../tests/e2e/solr/DummyAuthPluginImpl.java | 68 ++ .../ModifiableUserAuthenticationFilter.java | 73 -- .../e2e/solr/TestCollAdminCoreOperations.java | 145 --- .../tests/e2e/solr/TestDocLevelOperations.java | 400 ++++---- .../tests/e2e/solr/TestQueryOperations.java | 78 -- .../sentry/tests/e2e/solr/TestRealTimeGet.java | 476 ---------- .../sentry/tests/e2e/solr/TestSentryServer.java | 144 +++ .../tests/e2e/solr/TestSolrAdminOperations.java | 188 ++++ .../e2e/solr/TestSolrCollectionOperations.java | 141 +++ .../e2e/solr/TestSolrConfigOperations.java | 232 +++++ .../e2e/solr/TestSolrSchemaOperations.java | 146 +++ .../tests/e2e/solr/TestUpdateOperations.java | 168 ---- .../AbstractSolrSentryTestWithDbProvider.java | 324 ------- .../db/integration/TestSolrAdminOperations.java | 242 ----- .../integration/TestSolrDocLevelOperations.java | 204 ---- .../db/integration/TestSolrQueryOperations.java | 96 -- .../integration/TestSolrUpdateOperations.java | 100 -- .../cloud-managed/conf/managed-schema | 27 + .../cloud-managed/conf/solrconfig.xml | 51 + .../configsets/cloud-minimal/conf/schema.xml | 28 + .../cloud-minimal/conf/solrconfig.xml | 47 + .../conf/schema.xml | 29 + .../conf/solrconfig.xml | 82 ++ .../test/resources/solr/security/security.json | 18 + 133 files changed, 5438 insertions(+), 9048 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index bea2c5f..0d4fe0a 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ limitations under the License. <pig.version>0.12.0</pig.version> <shiro.version>1.4.0</shiro.version> <slf4j.version>1.7.25</slf4j.version> - <solr.version>4.10.2</solr.version> + <solr.version>7.1.0</solr.version> <sqoop.version>1.99.7</sqoop.version> <surefire.version>2.20.1</surefire.version> <test.sentry.hadoop.classpath>${maven.test.classpath}</test.sentry.hadoop.classpath> @@ -208,11 +208,6 @@ limitations under the License. </dependency> <dependency> <groupId>org.apache.sentry</groupId> - <artifactId>solr-sentry-core</artifactId> - <version>${project.version}</version> - </dependency> - <dependency> - <groupId>org.apache.sentry</groupId> <artifactId>solr-sentry-handlers</artifactId> <version>${project.version}</version> </dependency> @@ -394,7 +389,7 @@ limitations under the License. </dependency> <dependency> <groupId>org.apache.sentry</groupId> - <artifactId>sentry-core-model-search</artifactId> + <artifactId>sentry-core-model-solr</artifactId> <version>${project.version}</version> </dependency> <dependency> @@ -643,9 +638,9 @@ limitations under the License. <module>sentry-binding</module> <module>sentry-provider</module> <module>sentry-policy</module> + <module>sentry-solr</module> <module>sentry-tests</module> <module>sentry-hdfs</module> - <module>sentry-solr</module> <module>sentry-dist</module> </modules> @@ -849,6 +844,7 @@ limitations under the License. <exclude>**.patch</exclude> <!-- Exclude generated solr config files --> <exclude>**/solr/collection1/conf/**</exclude> + <exclude>**/solr/security/**</exclude> <exclude>**/empty-file-main-lib.txt</exclude> <!-- Exclude generated thrift files --> <exclude>**/gen/**</exclude> http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/pom.xml ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/pom.xml b/sentry-binding/sentry-binding-solr/pom.xml index ed2624b..2452e2f 100644 --- a/sentry-binding/sentry-binding-solr/pom.xml +++ b/sentry-binding/sentry-binding-solr/pom.xml @@ -30,17 +30,18 @@ limitations under the License. <dependencies> <dependency> + <groupId>org.apache.solr</groupId> + <artifactId>solr-core</artifactId> + <version>${solr.version}</version> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.sentry</groupId> - <artifactId>sentry-core-common</artifactId> - </dependency> - <dependency> - <groupId>org.apache.sentry</groupId> - <artifactId>sentry-core-model-search</artifactId> + <artifactId>sentry-core-model-solr</artifactId> </dependency> <dependency> <groupId>org.apache.sentry</groupId> http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrAuthorizationException.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrAuthorizationException.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrAuthorizationException.java deleted file mode 100644 index 938dbfd..0000000 --- a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrAuthorizationException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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; - -public class SentrySolrAuthorizationException extends Exception { - private static final long serialVersionUID = -263787088321897523L; - - public SentrySolrAuthorizationException(String message) { - super(message); - } -} http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrPluginImpl.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrPluginImpl.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrPluginImpl.java new file mode 100644 index 0000000..c0ead28 --- /dev/null +++ b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SentrySolrPluginImpl.java @@ -0,0 +1,408 @@ +/* + * 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.sentry.binding.solr.authz.SolrAuthzBinding.QUERY; +import static org.apache.sentry.binding.solr.authz.SolrAuthzBinding.UPDATE; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.util.KerberosName; +import org.apache.http.auth.BasicUserPrincipal; +import org.apache.sentry.binding.solr.conf.SolrAuthzConf; +import org.apache.sentry.core.common.Subject; +import org.apache.sentry.core.model.solr.AdminOperation; +import org.apache.sentry.core.model.solr.Collection; +import org.apache.sentry.core.model.solr.SolrConstants; +import org.apache.sentry.core.model.solr.SolrModelAction; +import org.apache.sentry.core.model.solr.SolrModelAuthorizable; +import org.apache.solr.common.SolrException; +import org.apache.solr.common.SolrException.ErrorCode; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationContext.CollectionRequest; +import org.apache.solr.security.AuthorizationPlugin; +import org.apache.solr.security.AuthorizationResponse; +import org.apache.solr.security.PermissionNameProvider; +import org.apache.solr.security.PermissionNameProvider.Name; +import org.apache.solr.sentry.AuditLogger; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * A concrete implementation of Solr {@linkplain AuthorizationPlugin} backed by Sentry. + * + */ +public class SentrySolrPluginImpl implements AuthorizationPlugin { + private static final Logger LOG = LoggerFactory.getLogger(SentrySolrPluginImpl.class); + + /** + * A property specifies the value of the prefix to be used to define Java system property + * for configuring the authentication mechanism. The name of the Java system property is + * defined by appending the configuration parmeter namne to this prefix value e.g. if prefix + * is 'solr' then the Java system property 'solr.kerberos.principal' defines the value of + * configuration parameter 'kerberos.principal'. + */ + private static final String SYSPROP_PREFIX_PROPERTY = "sysPropPrefix"; + + /** + * A property specifying the configuration parameters required by the Sentry authorization + * plugin. + */ + private static final String AUTH_CONFIG_NAMES_PROPERTY = "authConfigs"; + + /** + * A property specifying the default values for the configuration parameters specified by the + * {@linkplain #AUTH_CONFIG_NAMES_PROPERTY} property. The default values are specified as a + * collection of key-value pairs (i.e. property-name : default_value). + */ + private static final String DEFAULT_AUTH_CONFIGS_PROPERTY = "defaultConfigs"; + + /** + * A configuration property specifying location of sentry-site.xml + */ + public static final String SNTRY_SITE_LOCATION_PROPERTY = "authorization.sentry.site"; + + /** + * A configuration property specifying the Solr super-user name. The Sentry permissions + * check will be skipped if the request is authenticated with this user name. + */ + public static final String SENTRY_SOLR_AUTH_SUPERUSER = "authorization.superuser"; + + /** + * A configuration property to enable audit log for the Solr operations. Please note that + * audit log is available only for operations handled by the Solr authorization framework. + */ + public static final String SENTRY_ENABLE_SOLR_AUDITLOG = "authorization.enable.auditlog"; + + /** + * A configuration property to specify the location of Hadoop configuration files (specifically + * core-site.xml) required to properly setup Hadoop {@linkplain UserGroupInformation} context. + */ + public static final String SENTRY_HADOOP_CONF_DIR_PROPERTY = "authorization.sentry.hadoop.conf"; + + private String solrSuperUser; + private SolrAuthzBinding binding; + private Optional<AuditLogger> auditLog = Optional.empty(); + + @SuppressWarnings("unchecked") + @Override + public void init(Map<String, Object> pluginConfig) { + Map<String, String> params = new HashMap<>(); + + String sysPropPrefix = (String) pluginConfig.getOrDefault(SYSPROP_PREFIX_PROPERTY, "solr."); + java.util.Collection<String> authConfigNames = (java.util.Collection<String>) pluginConfig. + getOrDefault(AUTH_CONFIG_NAMES_PROPERTY, Collections.emptyList()); + Map<String,String> authConfigDefaults = (Map<String,String>) pluginConfig + .getOrDefault(DEFAULT_AUTH_CONFIGS_PROPERTY, Collections.emptyMap()); + + for ( String configName : authConfigNames) { + String systemProperty = sysPropPrefix + configName; + String defaultConfigVal = authConfigDefaults.get(configName); + String configVal = System.getProperty(systemProperty, defaultConfigVal); + if (configVal != null) { + params.put(configName, configVal); + } + } + + initializeSentry(params); + } + + @Override + public void close() throws IOException { + if (this.binding != null) { + this.binding.close(); + } + } + + @Override + public AuthorizationResponse authorize(AuthorizationContext authCtx) { + if (authCtx.getUserPrincipal() == null) { // Request not authenticated. + return AuthorizationResponse.PROMPT; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Authorizing a request with authorization context {} ", SolrAuthzUtil.toString(authCtx)); + } + + String userNameStr = getShortUserName(authCtx.getUserPrincipal()); + + if (this.solrSuperUser.equals(userNameStr)) { + return AuthorizationResponse.OK; + } + + if (authCtx.getHandler() instanceof PermissionNameProvider) { + Subject userName = new Subject(userNameStr); + Name perm = ((PermissionNameProvider) authCtx.getHandler()).getPermissionName(authCtx); + switch (perm) { + case READ_PERM: + case UPDATE_PERM: { + AuthorizationResponse resp = AuthorizationResponse.FORBIDDEN; + Set<SolrModelAction> actions = (perm == Name.READ_PERM) ? QUERY : UPDATE; + for (CollectionRequest req : authCtx.getCollectionRequests()) { + resp = binding.authorizeCollection(userName, + new Collection(req.collectionName), actions); + if (!AuthorizationResponse.OK.equals(resp)) { + break; + } + } + + audit (perm, authCtx, resp); + return resp; + } + case SECURITY_EDIT_PERM: { + return binding.authorize(userName, Collections.singleton(AdminOperation.SECURITY), UPDATE); + } + case SECURITY_READ_PERM: { + return binding.authorize(userName, Collections.singleton(AdminOperation.SECURITY), QUERY); + } + case CORE_READ_PERM: + case CORE_EDIT_PERM: + case COLL_READ_PERM: + case COLL_EDIT_PERM: { + AuthorizationResponse resp = AuthorizationResponse.FORBIDDEN; + SolrModelAuthorizable auth = (perm == Name.COLL_READ_PERM || perm == Name.COLL_EDIT_PERM) + ? AdminOperation.COLLECTIONS : AdminOperation.CORES; + Set<SolrModelAction> actions = (perm == Name.COLL_READ_PERM || perm == Name.CORE_READ_PERM) + ? QUERY : UPDATE; + resp = binding.authorize(userName, Collections.singleton(auth), actions); + audit (perm, authCtx, resp); + if (AuthorizationResponse.OK.equals(resp)) { + // Apply collection/core-level permissions check as well. + for (Map.Entry<String, SolrModelAction> entry : + SolrAuthzUtil.getCollectionsForAdminOp(authCtx).entrySet()) { + resp = binding.authorizeCollection(userName, + new Collection(entry.getKey()), Collections.singleton(entry.getValue())); + Name p = entry.getValue().equals(SolrModelAction.UPDATE) ? Name.UPDATE_PERM : Name.READ_PERM; + audit(p, authCtx, resp); + if (!AuthorizationResponse.OK.equals(resp)) { + break; + } + } + } + return resp; + } + case CONFIG_EDIT_PERM: { + return binding.authorize(userName, SolrAuthzUtil.getConfigAuthorizables(authCtx), UPDATE); + } + case CONFIG_READ_PERM: { + return binding.authorize(userName, SolrAuthzUtil.getConfigAuthorizables(authCtx), QUERY); + } + case SCHEMA_EDIT_PERM: { + return binding.authorize(userName, SolrAuthzUtil.getSchemaAuthorizables(authCtx), UPDATE); + } + case SCHEMA_READ_PERM: { + return binding.authorize(userName, SolrAuthzUtil.getSchemaAuthorizables(authCtx), QUERY); + } + case METRICS_READ_PERM: { + return binding.authorize(userName, Collections.singleton(AdminOperation.METRICS), QUERY); + } + case AUTOSCALING_READ_PERM: + case AUTOSCALING_HISTORY_READ_PERM: { + return binding.authorize(userName, Collections.singleton(AdminOperation.AUTOSCALING), QUERY); + } + case AUTOSCALING_WRITE_PERM: { + return binding.authorize(userName, Collections.singleton(AdminOperation.AUTOSCALING), UPDATE); + } + case ALL: { + return AuthorizationResponse.OK; + } + } + } + + /* + * The switch-case statement above handles all possible permission types. Some of the request handlers + * in SOLR do not implement PermissionNameProvider interface and hence are incapable to providing the + * type of permission to be enforced for this request. This is a design limitation (or a bug) on the SOLR + * side. Until that issue is resolved, Solr/Sentry plugin needs to return OK for such requests. + * Ref: SOLR-11623 + */ + return AuthorizationResponse.OK; + } + + /** + * This method returns the roles associated with the specified user name. + */ + public Set<String> getRoles (String userName) { + return binding.getRoles(userName); + } + + private void initializeSentry(Map<String, String> config) { + String sentrySiteLoc = + Preconditions.checkNotNull(config.get(SNTRY_SITE_LOCATION_PROPERTY), + "The authorization plugin configuration is missing " + SNTRY_SITE_LOCATION_PROPERTY + + " property"); + String sentryHadoopConfLoc = (String)config.get(SENTRY_HADOOP_CONF_DIR_PROPERTY); + + try { + List<URL> configFiles = getHadoopConfigFiles(sentryHadoopConfLoc); + configFiles.add((new File(sentrySiteLoc)).toURI().toURL()); + + binding = new SolrAuthzBinding(new SolrAuthzConf(configFiles)); + LOG.info("SolrAuthzBinding created successfully"); + } catch (Exception e) { + throw new SolrException(ErrorCode.SERVER_ERROR, "Unable to create SolrAuthzBinding", e); + } + + this.solrSuperUser = Preconditions.checkNotNull(config.get(SENTRY_SOLR_AUTH_SUPERUSER)); + boolean enableAuditLog = Boolean.parseBoolean( + Preconditions.checkNotNull(config.get(SENTRY_ENABLE_SOLR_AUDITLOG))); + if (enableAuditLog) { + this.auditLog = Optional.of(new AuditLogger()); + } + } + + private void audit (Name perm, AuthorizationContext ctx, AuthorizationResponse resp) { + if (!auditLog.isPresent() || !auditLog.get().isLogEnabled()) { + return; + } + + String userName = getShortUserName(ctx.getUserPrincipal()); + String ipAddress = ctx.getRemoteAddr(); + long eventTime = System.currentTimeMillis(); + int allowed = (resp.statusCode == AuthorizationResponse.OK.statusCode) + ? AuditLogger.ALLOWED : AuditLogger.UNAUTHORIZED; + String operationParams = ctx.getParams().toString(); + + switch (perm) { + case COLL_EDIT_PERM: + case COLL_READ_PERM: { + String collectionName = "admin"; + String actionName = ctx.getParams().get(CoreAdminParams.ACTION); + String operationName = (actionName != null) ? + "CollectionAction." + ctx.getParams().get(CoreAdminParams.ACTION) + : ctx.getHandler().getClass().getName(); + auditLog.get().log (userName, null, ipAddress, + operationName, operationParams, eventTime, allowed, collectionName); + break; + } + + case CORE_EDIT_PERM: + case CORE_READ_PERM: { + String collectionName = "admin"; + String operationName = "CoreAdminAction.STATUS"; + if (ctx.getParams().get(CoreAdminParams.ACTION) != null) { + operationName = "CoreAdminAction." + ctx.getParams().get(CoreAdminParams.ACTION); + } + + auditLog.get().log (userName, null, ipAddress, + operationName, operationParams, eventTime, allowed, collectionName); + break; + } + + case READ_PERM: + case UPDATE_PERM: { + List<String> names = new ArrayList<>(); + for (CollectionRequest r : ctx.getCollectionRequests()) { + names.add(r.collectionName); + } + String collectionName = String.join(",", names); + String operationName = (perm == Name.READ_PERM) ? SolrConstants.QUERY : SolrConstants.UPDATE; + auditLog.get().log (userName, null, ipAddress, + operationName, operationParams, eventTime, allowed, collectionName); + break; + } + + default: { + // Do nothing. + break; + } + } + } + + /** + * Workaround until SOLR-10814 is fixed. This method allows extracting short user-name from + * Solr provided {@linkplain Principal} instance. + * + * @param ctx The Solr provided authorization context + * @return The short name of the authenticated user for this request + */ + public static String getShortUserName (Principal princ) { + if (princ instanceof BasicUserPrincipal) { + return princ.getName(); + } + + KerberosName name = new KerberosName(princ.getName()); + try { + return name.getShortName(); + } catch (IOException e) { + LOG.error("Error converting kerberos name. principal = {}, KerberosName.rules = {}", + princ, KerberosName.getRules()); + throw new SolrException(ErrorCode.SERVER_ERROR, "Unexpected error converting a kerberos name", e); + } + } + + /** + * This method provides the path(s) of various Hadoop configuration files required + * by the Sentry/Solr plugin. + * @param confDir Location of a folder (on local file-system) storing Sentry Hadoop + * configuration files + * @return A list of URLs containing the Sentry Hadoop + * configuration files + */ + private List<URL> getHadoopConfigFiles(String confDir) { + List<URL> result = new ArrayList<>(); + + if (confDir != null && !confDir.isEmpty()) { + File confDirFile = new File(confDir); + if (!confDirFile.exists()) { + throw new SolrException(ErrorCode.SERVER_ERROR, + "Specified Sentry hadoop config directory does not exist: " + + confDirFile.getAbsolutePath()); + } + if (!confDirFile.isDirectory()) { + throw new SolrException(ErrorCode.SERVER_ERROR, + "Specified Sentry hadoop config directory path is not a directory: " + + confDirFile.getAbsolutePath()); + } + if (!confDirFile.canRead()) { + throw new SolrException(ErrorCode.SERVER_ERROR, + "Specified Sentry hadoop config directory must be readable by the Solr process: " + + confDirFile.getAbsolutePath()); + } + + for (String file : Arrays.asList("core-site.xml", + "hdfs-site.xml", "ssl-client.xml")) { + File f = new File(confDirFile, file); + if (f.exists()) { + try { + result.add(f.toURI().toURL()); + } catch (MalformedURLException e) { + throw new SolrException(ErrorCode.SERVER_ERROR, e.getMessage(), e); + } + } + } + } + + return result; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzBinding.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzBinding.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzBinding.java index 0a818e5..803e5ea 100644 --- a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzBinding.java +++ b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzBinding.java @@ -16,81 +16,80 @@ */ 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 static org.apache.sentry.core.model.solr.SolrConstants.SENTRY_SOLR_SERVICE_DEFAULT; +import static org.apache.sentry.core.model.solr.SolrConstants.SENTRY_SOLR_SERVICE_KEY; -import java.io.File; +import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Constructor; import java.util.Arrays; -import java.util.List; +import java.util.Collections; +import java.util.HashSet; 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.core.common.exception.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.Authorizable; import org.apache.sentry.core.common.Model; 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.core.model.search.SearchPrivilegeModel; +import org.apache.sentry.core.model.solr.SolrPrivilegeModel; +import org.apache.sentry.core.model.solr.AdminOperation; +import org.apache.sentry.core.model.solr.Collection; +import org.apache.sentry.core.model.solr.SolrModelAction; +import org.apache.sentry.core.model.solr.SolrModelAuthorizable; 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.common.ProviderBackendContext; +import org.apache.sentry.provider.common.GroupMappingService; 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.apache.sentry.provider.db.generic.tools.GenericPrivilegeConverter; import org.apache.sentry.service.thrift.ServiceConstants; +import org.apache.solr.security.AuthorizationResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; - -public class SolrAuthzBinding { +/** + * This class provides functionality to initialize the Sentry (specifically + * {@linkplain AuthorizationProvider} and {@linkplain ProviderBackend}) as + * well as query the permissions for a given user and a specific set of + * entities. + */ +public class SolrAuthzBinding implements Closeable { 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 SOLR_POLICY_ENGINE_OLD = "org.apache.sentry.policy.search.SimpleSearchPolicyEngine"; - 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; + + static final Set<SolrModelAction> QUERY = new HashSet<>(Arrays.asList(SolrModelAction.QUERY)); + static final Set<SolrModelAction> UPDATE = new HashSet<>(Arrays.asList(SolrModelAction.UPDATE)); private final SolrAuthzConf authzConf; - private final AuthorizationProvider authProvider; + public final AuthorizationProvider authProvider; private final GroupMappingService groupMapping; - private ProviderBackend providerBackend; - private Subject bindingSubject; + public ProviderBackend providerBackend; - public SolrAuthzBinding (SolrAuthzConf authzConf) throws Exception { - this.authzConf = addHdfsPropsToConf(authzConf); + /** + * The constructor. + * + * @param authzConf The Sentry/Solr authorization configuration + * @throws Exception in case of errors. + */ + public SolrAuthzBinding(SolrAuthzConf authzConf) throws Exception { + this.authzConf = authzConf; this.authProvider = getAuthProvider(); this.groupMapping = authProvider.getGroupMapping(); - /** - * The Solr server principal will use the binding - */ - this.bindingSubject = new Subject(UserGroupInformation.getCurrentUser() - .getShortUserName()); + } + + @Override + public void close() throws IOException { + if (this.authProvider != null) { + this.authProvider.close(); + } } // Instantiate the configured authz provider @@ -103,36 +102,18 @@ public class SolrAuthzBinding { authzConf.get(AuthzConfVars.AUTHZ_PROVIDER_BACKEND.getVar()); String policyEngineName = authzConf.get(AuthzConfVars.AUTHZ_POLICY_ENGINE.getVar(), AuthzConfVars.AUTHZ_POLICY_ENGINE.getDefault()); - String serviceName = authzConf.get(SENTRY_SEARCH_SERVICE_KEY, SENTRY_SEARCH_SERVICE_DEFAULT); - - // for the backward compatibility - if (SOLR_POLICY_ENGINE_OLD.equals(policyEngineName)) { - policyEngineName = AuthzConfVars.AUTHZ_POLICY_ENGINE.getDefault(); - } + String serviceName = authzConf.get(SENTRY_SOLR_SERVICE_KEY, SENTRY_SOLR_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); - } // for convenience, set the PrivilegeConverter. if (authzConf.get(ServiceConstants.ClientConfig.PRIVILEGE_CONVERTER) == null) { - authzConf.set(ServiceConstants.ClientConfig.PRIVILEGE_CONVERTER, GenericPrivilegeConverter.class.getName()); + authzConf.set(ServiceConstants.ClientConfig.PRIVILEGE_CONVERTER, + GenericPrivilegeConverter.class.getName()); } - // 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); @@ -142,14 +123,14 @@ public class SolrAuthzBinding { if (providerBackend instanceof SentryGenericProviderBackend) { ((SentryGenericProviderBackend) providerBackend) - .setComponentType(AuthorizationComponent.Search); + .setComponentType(AuthorizationComponent.Search); ((SentryGenericProviderBackend) providerBackend).setServiceName(serviceName); } // Create backend context ProviderBackendContext context = new ProviderBackendContext(); context.setAllowPerDatabase(false); - context.setValidators(SearchPrivilegeModel.getInstance().getPrivilegeValidators()); + context.setValidators(SolrPrivilegeModel.getInstance().getPrivilegeValidators()); providerBackend.initialize(context); // load the policy engine class @@ -167,34 +148,63 @@ public class SolrAuthzBinding { // load the authz provider class Constructor<?> constrctor = - Class.forName(authProviderName).getDeclaredConstructor(Configuration.class, - String.class, PolicyEngine.class, Model.class); + Class.forName(authProviderName).getDeclaredConstructor(Configuration.class, + String.class, PolicyEngine.class, Model.class); constrctor.setAccessible(true); return (AuthorizationProvider) constrctor.newInstance(new Object[] {authzConf, resourceName, - policyEngine, SearchPrivilegeModel.getInstance()}); + policyEngine, SolrPrivilegeModel.getInstance()}); } - /** - * Authorize access to an index/collection - * @param subject - * @param collection - * @param actions - * @throws SentrySolrAuthorizationException + * Authorize access to a Solr operation + * @param subject The user invoking the SOLR operation + * @param admin The {@linkplain SolrModelAuthorizable} associated with the operation + * @param actions The action performed as part of the operation (query or update) + * @return {@linkplain AuthorizationResponse#OK} If the authorization is successful + * {@linkplain AuthorizationResponse#FORBIDDEN} if the authorization fails. */ - 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() + + public AuthorizationResponse authorize (Subject subject, + java.util.Collection<? extends SolrModelAuthorizable> authorizables, Set<SolrModelAction> actions) { + if(LOG.isDebugEnabled()) { + LOG.debug("Going to authorize " + authorizables + " 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()); + for (SolrModelAuthorizable a : authorizables) { + if (!authProvider.hasAccess(subject, Arrays.asList(new Authorizable[] { a }), + actions, ActiveRoleSet.ALL)) { + return AuthorizationResponse.FORBIDDEN; + } } + + return AuthorizationResponse.OK; + } + + /** + * Authorize access to an index/collection + * @param subject The user invoking the SOLR collection related operation + * @param collection The Solr collection associated with the operation + * @param actions The action performed as part of the operation (query or update) + * @return {@linkplain AuthorizationResponse#OK} If the authorization is successful + * {@linkplain AuthorizationResponse#FORBIDDEN} if the authorization fails. + */ + public AuthorizationResponse authorizeCollection(Subject subject, Collection collection, + Set<SolrModelAction> actions) { + return authorize(subject, Collections.singleton(collection), actions); + } + + /** + * Authorize access to an admin operation + * @param subject The user invoking the SOLR admin operation + * @param admin The type of the admin operation + * @param actions The action performed as part of the operation (query or update) + * @return {@linkplain AuthorizationResponse#OK} If the authorization is successful + * {@linkplain AuthorizationResponse#FORBIDDEN} if the authorization fails. + */ + public AuthorizationResponse authorizeAdminAction(Subject subject, AdminOperation admin, + Set<SolrModelAction> actions) { + return authorize(subject, Collections.singleton(admin), actions); } /** @@ -217,103 +227,7 @@ public class SolrAuthzBinding { 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; - } - try (SentryGenericServiceClient 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()); - } - } } http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzUtil.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzUtil.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzUtil.java new file mode 100644 index 0000000..42dce03 --- /dev/null +++ b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/authz/SolrAuthzUtil.java @@ -0,0 +1,271 @@ +/* + * 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 java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.sentry.core.model.solr.Config; +import org.apache.sentry.core.model.solr.Schema; +import org.apache.sentry.core.model.solr.SolrModelAction; +import org.apache.solr.common.params.CollectionParams.CollectionAction; +import org.apache.solr.common.params.CoreAdminParams.CoreAdminAction; +import org.apache.solr.common.params.CollectionAdminParams; +import org.apache.solr.common.params.CommonParams; +import org.apache.solr.common.params.CoreAdminParams; +import org.apache.solr.common.params.SolrParams; +import org.apache.solr.handler.admin.ConfigSetsHandler; +import org.apache.solr.security.AuthorizationContext; +import org.apache.solr.security.AuthorizationContext.CollectionRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Solr authorization framework does not currently support fine-grained authorization. + * Specifically it does not provide the resource names consistently. This utility class + * extracts the resource names for specific APIs by introspecting the {@linkplain SolrParams} + * passed as part of {@linkplain AuthorizationContext}. This class can be removed once SOLR-11543 + * is available. + */ +class SolrAuthzUtil { + private static final Logger LOG = LoggerFactory.getLogger(SolrAuthzUtil.class); + + /** + * This method returns a collection of {@linkplain Config} entities associated with the current + * operation. + */ + static Collection<Config> getConfigAuthorizables (AuthorizationContext ctx) { + List<Config> result = new ArrayList<>(1); + if (ctx.getHandler() instanceof ConfigSetsHandler) { // For Solr configset APIs + String name = ctx.getParams().get(CommonParams.NAME); + if (name != null) { + result.add(new Config(name)); + } + } else { // For Solr config APIs + for (CollectionRequest r : ctx.getCollectionRequests()) { + result.add(new Config(r.collectionName)); + } + } + if (result.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Missing collection name for the config operation with authorization context {}." + + " Using * permissions for authorization check", toString(ctx)); + } + result.add(Config.ALL); + } + + return result; + } + + /** + * This method returns a collection of {@linkplain Schema} entities associated with the current + * operation. + */ + static Collection<Schema> getSchemaAuthorizables (AuthorizationContext ctx) { + List<Schema> result = new ArrayList<>(1); + for (CollectionRequest r : ctx.getCollectionRequests()) { + result.add(new Schema(r.collectionName)); + } + if (result.isEmpty()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Missing collection name for the schema operation with authorization context {}." + + " Using * permissions for authorization check", toString(ctx)); + } + result.add(Schema.ALL); + } + + return result; + } + + /** + * This method extracts the {@linkplain org.apache.sentry.core.model.solr.Collection} entities + * associated with this admin request and return a mapping of entity_name -> expected_auth_action. + * This is used by Solr/Sentry authorization plugin to further restrict Solr admin operations. + */ + static Map<String, SolrModelAction> getCollectionsForAdminOp(AuthorizationContext ctx) { + String actionName = ctx.getParams().get(CoreAdminParams.ACTION); + CollectionAction action = CollectionAction.get(actionName); + if (action != null) { + switch (action) { + case LISTSNAPSHOTS: + case BACKUP: { + String name = ctx.getParams().get(CollectionAdminParams.COLLECTION); + return (name != null) ? Collections.singletonMap(name, SolrModelAction.QUERY) + : Collections.emptyMap(); + } + + case MIGRATE: { + Map<String, SolrModelAction> result = new HashMap<>(); + String source = ctx.getParams().get(CollectionAdminParams.COLLECTION); + String target = ctx.getParams().get("target."+CollectionAdminParams.COLLECTION); + if (source != null) { + result.put (source, SolrModelAction.QUERY); + } + if (target != null) { + result.put (source, SolrModelAction.UPDATE); + } + return result; + } + + case DELETE: + case DELETEALIAS: + case CREATESHARD: + case DELETESHARD: + case SPLITSHARD: + case RELOAD: + case CREATE: { + String name = ctx.getParams().get(CommonParams.NAME); + return (name != null) ? Collections.singletonMap(name, SolrModelAction.UPDATE) + : Collections.emptyMap(); + } + + case DELETESNAPSHOT: + case CREATESNAPSHOT: + case SYNCSHARD: + case MOVEREPLICA: + case RESTORE: + case MIGRATESTATEFORMAT: + case FORCELEADER: + case REBALANCELEADERS: + case BALANCESHARDUNIQUE: + case ADDREPLICAPROP: + case DELETEREPLICAPROP: + case ADDREPLICA: + case DELETEREPLICA: + case MODIFYCOLLECTION: { + String name = ctx.getParams().get(CollectionAdminParams.COLLECTION); + return (name != null) ? Collections.singletonMap(name, SolrModelAction.UPDATE) + : Collections.emptyMap(); + } + + case MOCK_COLL_TASK: + case MOCK_REPLICA_TASK: + case MOCK_SHARD_TASK: + case REPLACENODE: + case DELETENODE: + case ADDROLE: + case REMOVEROLE: + case CREATEALIAS: + case REQUESTSTATUS: + case DELETESTATUS: + case LIST: + case LISTALIASES: + case CLUSTERPROP: + case OVERSEERSTATUS: + case CLUSTERSTATUS: { + return Collections.emptyMap(); + } + } + } + + return Collections.emptyMap(); + } + + /** + * This method extracts the {@linkplain org.apache.sentry.core.model.solr.Collection} entities + * associated with this admin request and return a mapping of entity_name -> expected_auth_action. + * This is used by Solr/Sentry authorization plugin to further restrict Solr admin operations. + */ + static Map<String, SolrModelAction> getCoresForAdminOp(AuthorizationContext ctx) { + String actionName = ctx.getParams().get(CoreAdminParams.ACTION); + CoreAdminAction action = CoreAdminAction.get(actionName); + if (action != null) { + switch (action) { + case REQUESTBUFFERUPDATES: + case REQUESTAPPLYUPDATES: + case CREATE: { + String coreName = ctx.getParams().get(CoreAdminParams.NAME); + return (coreName != null) ? Collections.singletonMap(coreName, SolrModelAction.UPDATE) + : Collections.emptyMap(); + } + + case REQUESTSTATUS: + case OVERSEEROP: + case INVOKE: + // TODO - is this correct ? + case DELETEALIAS: { + return Collections.emptyMap(); + } + + case REQUESTSYNCSHARD: + case REJOINLEADERELECTION: + case PREPRECOVERY: + case FORCEPREPAREFORLEADERSHIP: + case CREATESNAPSHOT: + case DELETESNAPSHOT: + case RESTORECORE: + case REQUESTRECOVERY: + case SPLIT: + case MERGEINDEXES: + case UNLOAD: + case RENAME: + case RELOAD: { + String coreName = ctx.getParams().get(CoreAdminParams.CORE); + return (coreName != null) ? Collections.singletonMap(coreName, SolrModelAction.UPDATE) + : Collections.emptyMap(); + } + + case LISTSNAPSHOTS: + case BACKUPCORE: + case STATUS: { + String coreName = ctx.getParams().get(CoreAdminParams.CORE); + return (coreName != null) ? Collections.singletonMap(coreName, SolrModelAction.QUERY) + : Collections.emptyMap(); + } + case SWAP: { + Map<String, SolrModelAction> result = new HashMap<>(); + String core1 = ctx.getParams().get(CoreAdminParams.CORE); + String core2 = ctx.getParams().get("other"); + if (core1 != null) { + result.put(core1, SolrModelAction.UPDATE); + } + if (core2 != null) { + result.put(core2, SolrModelAction.UPDATE); + } + return result; + } + } + } + + return Collections.emptyMap(); + } + + + static String toString(AuthorizationContext ctx) { + StringBuilder builder = new StringBuilder(); + builder.append("AuthorizationContext {"); + builder.append("userPrincipal : "); + builder.append(ctx.getUserPrincipal().getName()); + // NOTE - comment out the code until SOLR-10814 is fixed. + //builder.append(", userName : "); + //builder.append(ctx.getUserName()); + builder.append(", collections : "); + builder.append(ctx.getCollectionRequests()); + builder.append(", handler : "); + builder.append(ctx.getHandler()); + builder.append(", HTTP method : "); + builder.append(ctx.getHttpMethod()); + builder.append("}"); + + return builder.toString(); + } + + +} http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/conf/SolrAuthzConf.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/conf/SolrAuthzConf.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/conf/SolrAuthzConf.java index 37efa5b..0883e70 100644 --- a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/conf/SolrAuthzConf.java +++ b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/sentry/binding/solr/conf/SolrAuthzConf.java @@ -18,6 +18,7 @@ package org.apache.sentry.binding.solr.conf; import java.net.URL; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.hadoop.conf.Configuration; @@ -71,14 +72,21 @@ public class SolrAuthzConf extends Configuration { currentToDeprecatedProps.put(AuthzConfVars.AUTHZ_PROVIDER.getVar(), AuthzConfVars.AUTHZ_PROVIDER_DEPRECATED); } - @SuppressWarnings("unused") private static final Logger LOG = LoggerFactory .getLogger(SolrAuthzConf.class); public static final String AUTHZ_SITE_FILE = "sentry-site.xml"; - public SolrAuthzConf(URL solrAuthzSiteURL) { + /** + * This constructor allows configuration of Sentry including sentry-site.xml and core-site.xml + * + * @param sentryConf Location of a folder (on local file-system) storing + * sentry Hadoop configuration files + */ + public SolrAuthzConf(List<URL> sentryConf) { super(true); - addResource(solrAuthzSiteURL); + for (URL u : sentryConf) { + addResource(u); + } } @Override http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/AuditLogger.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/AuditLogger.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/AuditLogger.java new file mode 100644 index 0000000..fc85769 --- /dev/null +++ b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/AuditLogger.java @@ -0,0 +1,92 @@ +/* + * 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.solr.sentry; + +import org.apache.lucene.util.Version; +import org.noggit.CharArr; +import org.noggit.JSONWriter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Writes audit events to the audit log. This helps answer questions such as: + * Who did what action when from where, and what values were changed from what + * to what as a result? + */ +public final class AuditLogger { + + public static final int ALLOWED = 1; + public static final int UNAUTHORIZED = 0; + + private final Logger logger; + + private static final String SOLR_VERSION = Version.LATEST.toString(); + + + public AuditLogger() { + this.logger = LoggerFactory.getLogger(getClass()); + } + + public boolean isLogEnabled() { + return logger.isInfoEnabled(); + } + + public void log( + String userName, + String impersonator, + String ipAddress, + String operation, + String operationParams, + long eventTime, + int allowed, + String collectionName) { + + if (!isLogEnabled()) { + return; + } + CharArr chars = new CharArr(512); + JSONWriter writer = new JSONWriter(chars, -1); + writer.startObject(); + writeField("solrVersion", SOLR_VERSION, writer); + writer.writeValueSeparator(); + writeField("eventTime", eventTime, writer); + writer.writeValueSeparator(); + writeField("allowed", allowed, writer); + writer.writeValueSeparator(); + writeField("collectionName", collectionName, writer); + writer.writeValueSeparator(); + writeField("operation", operation, writer); + writer.writeValueSeparator(); + writeField("operationParams", operationParams, writer); + writer.writeValueSeparator(); + writeField("ipAddress", ipAddress, writer); + writer.writeValueSeparator(); + writeField("username", userName, writer); + writer.writeValueSeparator(); + writeField("impersonator", impersonator, writer); + writer.endObject(); + logger.info("{}", chars); + } + + private void writeField(String key, Object value, JSONWriter writer) { + writer.writeString(key); + writer.writeNameSeparator(); + writer.write(value); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java new file mode 100644 index 0000000..a5dc8c3 --- /dev/null +++ b/sentry-binding/sentry-binding-solr/src/main/java/org/apache/solr/sentry/RollingFileWithoutDeleteAppender.java @@ -0,0 +1,182 @@ +/** + * 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.solr.sentry; + +import java.io.File; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.io.Writer; +import org.apache.log4j.FileAppender; +import org.apache.log4j.Layout; +import org.apache.log4j.RollingFileAppender; +import org.apache.log4j.helpers.CountingQuietWriter; +import org.apache.log4j.helpers.LogLog; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.log4j.spi.LoggingEvent; + +/** + * This class extends {@linkplain FileAppender} such that the log + * files are not deleted automatically (as done by {@linkplain RollingFileAppender}. + * It is responsibility of external process to consume these log files + * and delete them afterwards. This class should be used to provide audit log + * capability for Solr. + */ +public class RollingFileWithoutDeleteAppender extends FileAppender { + /** + * The default maximum file size is 10MB. + */ + protected long maxFileSize = 10 * 1024 * 1024; + + private long nextRollover = 0; + + /** + * The default constructor simply calls its {@link FileAppender#FileAppender + * parents constructor}. + */ + public RollingFileWithoutDeleteAppender() { + super(); + } + + /** + * Instantiate a RollingFileAppender and open the file designated by + * <code>filename</code>. The opened filename will become the ouput + * destination for this appender. + * <p> + * If the <code>append</code> parameter is true, the file will be appended to. + * Otherwise, the file desginated by <code>filename</code> will be truncated + * before being opened. + */ + public RollingFileWithoutDeleteAppender(Layout layout, String filename, + boolean append) throws IOException { + super(layout, getLogFileName(filename), append); + } + + /** + * Instantiate a FileAppender and open the file designated by + * <code>filename</code>. The opened filename will become the output + * destination for this appender. + * <p> + * The file will be appended to. + */ + public RollingFileWithoutDeleteAppender(Layout layout, String filename) + throws IOException { + super(layout, getLogFileName(filename)); + } + + /** + * Get the maximum size that the output file is allowed to reach before being + * rolled over to backup files. + */ + public long getMaximumFileSize() { + return maxFileSize; + } + + /** + * Implements the usual roll over behaviour. + * <p> + * <code>File</code> is renamed <code>File.yyyyMMddHHmmss</code> and closed. A + * new <code>File</code> is created to receive further log output. + */ + // synchronization not necessary since doAppend is alreasy synched + public void rollOver() { + if (qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + LogLog.debug("rolling over count=" + size); + // if operation fails, do not roll again until + // maxFileSize more bytes are written + nextRollover = size + maxFileSize; + } + + this.closeFile(); // keep windows happy. + + String newFileName = getLogFileName(fileName); + try { + // This will also close the file. This is OK since multiple + // close operations are safe. + this.setFile(newFileName, false, bufferedIO, bufferSize); + nextRollover = 0; + } catch (IOException e) { + if (e instanceof InterruptedIOException) { + Thread.currentThread().interrupt(); + } + LogLog.error("setFile(" + newFileName + ", false) call failed.", e); + } + } + + public synchronized void setFile(String fileName, boolean append, + boolean bufferedIO, int bufferSize) throws IOException { + super.setFile(fileName, append, this.bufferedIO, this.bufferSize); + if (append) { + File f = new File(fileName); + ((CountingQuietWriter) qw).setCount(f.length()); + } + } + + /** + * Set the maximum size that the output file is allowed to reach before being + * rolled over to backup files. + * <p> + * This method is equivalent to {@link #setMaxFileSize} except that it is + * required for differentiating the setter taking a <code>long</code> argument + * from the setter taking a <code>String</code> argument by the JavaBeans + * {@link java.beans.Introspector Introspector}. + * + * @see #setMaxFileSize(String) + */ + public void setMaximumFileSize(long maxFileSize) { + this.maxFileSize = maxFileSize; + } + + /** + * Set the maximum size that the output file is allowed to reach before being + * rolled over to backup files. + * <p> + * In configuration files, the <b>MaxFileSize</b> option takes an long integer + * in the range 0 - 2^63. You can specify the value with the suffixes "KB", + * "MB" or "GB" so that the integer is interpreted being expressed + * respectively in kilobytes, megabytes or gigabytes. For example, the value + * "10KB" will be interpreted as 10240. + */ + public void setMaxFileSize(String value) { + maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1); + } + + protected void setQWForFiles(Writer writer) { + this.qw = new CountingQuietWriter(writer, errorHandler); + } + + /** + * This method differentiates RollingFileAppender from its super class. + */ + protected void subAppend(LoggingEvent event) { + super.subAppend(event); + + if (fileName != null && qw != null) { + long size = ((CountingQuietWriter) qw).getCount(); + if (size >= maxFileSize && size >= nextRollover) { + rollOver(); + } + } + } + + // Mangled file name. Append the current timestamp + private static String getLogFileName(String oldFileName) { + return oldFileName + "." + Long.toString(System.currentTimeMillis()); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/sentry/blob/e62fa28d/sentry-binding/sentry-binding-solr/src/test/java/org/apache/sentry/binding/solr/HdfsTestUtil.java ---------------------------------------------------------------------- diff --git a/sentry-binding/sentry-binding-solr/src/test/java/org/apache/sentry/binding/solr/HdfsTestUtil.java b/sentry-binding/sentry-binding-solr/src/test/java/org/apache/sentry/binding/solr/HdfsTestUtil.java index 859c793..e952a55 100644 --- a/sentry-binding/sentry-binding-solr/src/test/java/org/apache/sentry/binding/solr/HdfsTestUtil.java +++ b/sentry-binding/sentry-binding-solr/src/test/java/org/apache/sentry/binding/solr/HdfsTestUtil.java @@ -1,3 +1,17 @@ +/* + * 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; import java.io.File; @@ -8,27 +22,8 @@ import java.util.Locale; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hdfs.MiniDFSCluster; -/* - * 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. - */ - - /** - * Copied from Apache Solr since the solr-core test jar currently isn't - * published + * Copied from Apache Solr since the solr-core test jar currently isn't published */ public class HdfsTestUtil { private static Locale savedLocale; @@ -50,8 +45,10 @@ public class HdfsTestUtil { conf.set("hdfs.minidfs.basedir", dir.getAbsolutePath() + File.separator + "hdfsBaseDir"); conf.set("dfs.namenode.name.dir", dir.getAbsolutePath() + File.separator + "nameNodeNameDir"); - System.setProperty("test.build.data", dir.getAbsolutePath() + File.separator + "hdfs" + File.separator + "build"); - System.setProperty("test.cache.data", dir.getAbsolutePath() + File.separator + "hdfs" + File.separator + "cache"); + System.setProperty("test.build.data", + dir.getAbsolutePath() + File.separator + "hdfs" + File.separator + "build"); + System.setProperty("test.cache.data", + dir.getAbsolutePath() + File.separator + "hdfs" + File.separator + "cache"); System.setProperty("solr.lock.type", "hdfs"); MiniDFSCluster dfsCluster = new MiniDFSCluster(conf, dataNodes, true, null); @@ -73,13 +70,10 @@ public class HdfsTestUtil { } } - public static String getDataDir(MiniDFSCluster dfsCluster, String dataDir) - throws IOException { + public static String getDataDir(MiniDFSCluster dfsCluster, String dataDir) throws IOException { URI uri = dfsCluster.getURI(); - String dir = uri.toString() - + "/" - + new File(dataDir).toString().replaceAll(":", "_") - .replaceAll("/", "_"); + String dir = uri.toString() + "/" + + new File(dataDir).toString().replaceAll(":", "_").replaceAll("/", "_"); return dir; } }
