diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java index 6013b284ca56..574db64bf3d1 100644 --- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java +++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java @@ -180,6 +180,8 @@ public CoreLoadFailure(CoreDescriptor cd, Exception loadFailure) { private SecurityPluginHolder authenticationPlugin; + private boolean useShortUserName; // for authorization (See SOLR-10814) + private BackupRepositoryFactory backupRepoFactory; protected SolrMetricManager metricManager; @@ -682,6 +684,11 @@ public void securityNodeChanged() { */ private void reloadSecurityProperties() { SecurityConfHandler.SecurityConfig securityConfig = securityConfHandler.getSecurityConfig(false); + + String useShortUserNameStr = (String)securityConfig.getData().get("useShortUserName"); + this.useShortUserName = (useShortUserNameStr != null) ? + Boolean.parseBoolean(useShortUserNameStr) : false; // Default is false. + initializeAuthorizationPlugin((Map) securityConfig.getData().get("authorization")); initializeAuthenticationPlugin((Map) securityConfig.getData().get("authentication")); } @@ -1558,6 +1565,10 @@ public boolean isCoreLoading(String name) { return solrCores.isCoreLoading(name); } + public boolean useShortUserName() { + return useShortUserName; + } + public AuthorizationPlugin getAuthorizationPlugin() { return authorizationPlugin == null ? null : authorizationPlugin.plugin; } diff --git a/solr/core/src/java/org/apache/solr/security/AuthorizationContext.java b/solr/core/src/java/org/apache/solr/security/AuthorizationContext.java index 65363a7f0948..3ff7229d5a19 100644 --- a/solr/core/src/java/org/apache/solr/security/AuthorizationContext.java +++ b/solr/core/src/java/org/apache/solr/security/AuthorizationContext.java @@ -36,9 +36,32 @@ public CollectionRequest(String collectionName) { } public abstract SolrParams getParams() ; - + + /** + * This method returns the {@linkplain Principal} corresponding to + * the authenticated user for the current request. Please note that + * the value returned by {@linkplain Principal#getName()} method depends + * upon the type of the authentication mechanism used (e.g. for user "foo" + * with BASIC authentication the result would be "foo". On the other hand + * with KERBEROS it would be foo@RELMNAME). Hence + * {@linkplain AuthorizationContext#getUserName()} method should be preferred + * to extract the identity of authenticated user instead of this method. + * + * @return user principal in case of an authenticated request + * null in case of unauthenticated request + */ public abstract Principal getUserPrincipal() ; + /** + * This method returns the name of the authenticated user for the current request. + * The return value of this method is agnostic of the underlying authentication + * mechanism used. + * + * @return user name in case of an authenticated user + * null in case of unauthenticated request + */ + public abstract String getUserName() ; + public abstract String getHttpHeader(String header); public abstract Enumeration getHeaderNames(); diff --git a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java index eab89e3ca4a2..bc870f0605d6 100644 --- a/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java +++ b/solr/core/src/java/org/apache/solr/security/BasicAuthPlugin.java @@ -129,6 +129,11 @@ public boolean doAuthenticate(ServletRequest servletRequest, ServletResponse ser public Principal getUserPrincipal() { return new BasicUserPrincipal(username); } + + @Override + public String getRemoteUser() { + return username; + } }; filterChain.doFilter(wrapper, response); return true; diff --git a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java index 0a6e62cfbcdc..6a5c3fac977d 100644 --- a/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java +++ b/solr/core/src/java/org/apache/solr/servlet/HttpSolrCall.java @@ -48,6 +48,7 @@ import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; +import org.apache.http.auth.BasicUserPrincipal; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; @@ -1033,9 +1034,17 @@ public SolrParams getParams() { @Override public Principal getUserPrincipal() { + if (cores.useShortUserName()) { + return new BasicUserPrincipal(getReq().getRemoteUser()); + } return getReq().getUserPrincipal(); } + @Override + public String getUserName() { + return getReq().getRemoteUser(); + } + @Override public String getHttpHeader(String s) { return getReq().getHeader(s); diff --git a/solr/core/src/test-files/solr/security/hadoop_kerberos_authz_config.json b/solr/core/src/test-files/solr/security/hadoop_kerberos_authz_config.json new file mode 100644 index 000000000000..280bdf05b7ac --- /dev/null +++ b/solr/core/src/test-files/solr/security/hadoop_kerberos_authz_config.json @@ -0,0 +1,37 @@ +{ + "useShortUserName": "true", + "authentication": { + "class": "org.apache.solr.security.ConfigurableInternodeAuthHadoopPlugin", + "sysPropPrefix": "solr.", + "type": "kerberos", + "clientBuilderFactory": "org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder", + "initKerberosZk": "true", + "authConfigs": [ + "kerberos.principal", + "kerberos.keytab", + "kerberos.name.rules" + ], + "defaultConfigs": { + } + }, + "authorization":{ + "class":"solr.RuleBasedAuthorizationPlugin", + "permissions":[ + { + "name":"collection-admin-edit", + "role":"admin" + }, + { + "name":"read", + "role":"admin" + }, + { + "name":"update", + "role":"admin" + } + ], + "user-role":{ + "solr":"admin" + } + } +} \ No newline at end of file diff --git a/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java b/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java index f4509a896bba..50c61fd16e99 100644 --- a/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java +++ b/solr/core/src/test/org/apache/solr/security/TestRuleBasedAuthorizationPlugin.java @@ -402,6 +402,11 @@ public Principal getUserPrincipal() { return userPrincipal == null ? null : new BasicUserPrincipal(String.valueOf(userPrincipal)); } + @Override + public String getUserName() { + return (getUserPrincipal() != null) ? getUserPrincipal().getName() : null; + } + @Override public String getHttpHeader(String header) { return null; diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/KerberosUtils.java b/solr/core/src/test/org/apache/solr/security/hadoop/KerberosUtils.java new file mode 100644 index 000000000000..42874b81d3e6 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/security/hadoop/KerberosUtils.java @@ -0,0 +1,94 @@ +/* + * 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.security.hadoop; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; + +import org.apache.commons.io.FileUtils; +import org.apache.solr.cloud.KerberosTestServices; + +/** + * A utility class which provides common functionality required to test kerberos integration. + */ +public class KerberosUtils { + + /** + * This method sets up Hadoop mini-kdc along with relevant Kerberos configuration files + * (e.g. jaas.conf) as well as system properties. + * + * @param baseDir The directory path which should be used by the Hadoop mini-kdc + * @return An instance of {@linkplain KerberosTestServices} + * @throws Exception in case of errors. + */ + static KerberosTestServices setupMiniKdc(Path baseDir) throws Exception { + System.setProperty("solr.jaas.debug", "true"); + String kdcDir = baseDir+File.separator+"minikdc"; + String solrClientPrincipal = "solr"; + File keytabFile = new File(kdcDir, "keytabs"); + KerberosTestServices tmp = KerberosTestServices.builder() + .withKdc(new File(kdcDir)) + .withJaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient") + .build(); + String solrServerPrincipal = "HTTP/127.0.0.1"; + tmp.start(); + tmp.getKdc().createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal); + + String jaas = "SolrClient {\n" + + " com.sun.security.auth.module.Krb5LoginModule required\n" + + " useKeyTab=true\n" + + " keyTab=\"" + keytabFile.getAbsolutePath() + "\"\n" + + " storeKey=true\n" + + " useTicketCache=false\n" + + " doNotPrompt=true\n" + + " debug=true\n" + + " principal=\"" + solrClientPrincipal + "\";\n" + + "};"; + + String jaasFilePath = kdcDir+File.separator+"jaas-client.conf"; + FileUtils.write(new File(jaasFilePath), jaas, StandardCharsets.UTF_8); + System.setProperty("java.security.auth.login.config", jaasFilePath); + System.setProperty("solr.kerberos.jaas.appname", "SolrClient"); // Get this app name from the jaas file + + System.setProperty("solr.kerberos.principal", solrServerPrincipal); + System.setProperty("solr.kerberos.keytab", keytabFile.getAbsolutePath()); + // Extracts 127.0.0.1 from HTTP/127.0.0.1@EXAMPLE.COM + System.setProperty("solr.kerberos.name.rules", "RULE:[1:$1@$0](.*EXAMPLE.COM)s/@.*//" + + "\nRULE:[2:$2@$0](.*EXAMPLE.COM)s/@.*//" + + "\nDEFAULT" + ); + + return tmp; + } + + /** + * This method stops the Hadoop mini-kdc instance as well as cleanup relevant Java system properties. + * + * @param kerberosTestServices An instance of Hadoop mini-kdc + */ + public static void cleanupMiniKdc (KerberosTestServices kerberosTestServices) { + System.clearProperty("java.security.auth.login.config"); + System.clearProperty("solr.kerberos.principal"); + System.clearProperty("solr.kerberos.keytab"); + System.clearProperty("solr.kerberos.name.rules"); + System.clearProperty("solr.jaas.debug"); + if (kerberosTestServices != null) { + kerberosTestServices.stop(); + } + } +} diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/TestRuleBasedAuthorizationWithKerberos.java b/solr/core/src/test/org/apache/solr/security/hadoop/TestRuleBasedAuthorizationWithKerberos.java new file mode 100644 index 000000000000..97827951db64 --- /dev/null +++ b/solr/core/src/test/org/apache/solr/security/hadoop/TestRuleBasedAuthorizationWithKerberos.java @@ -0,0 +1,83 @@ +/* + * 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.security.hadoop; + +import org.apache.lucene.util.Constants; +import org.apache.solr.client.solrj.SolrQuery; +import org.apache.solr.client.solrj.impl.CloudSolrClient; +import org.apache.solr.client.solrj.request.CollectionAdminRequest; +import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.cloud.AbstractDistribZkTestBase; +import org.apache.solr.cloud.KerberosTestServices; +import org.apache.solr.cloud.SolrCloudTestCase; +import org.apache.solr.common.SolrInputDocument; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class TestRuleBasedAuthorizationWithKerberos extends SolrCloudTestCase { + protected static final int NUM_SERVERS = 1; + protected static final int NUM_SHARDS = 1; + protected static final int REPLICATION_FACTOR = 1; + private static KerberosTestServices kerberosTestServices; + + @BeforeClass + public static void setupClass() throws Exception { + assumeFalse("Hadoop does not work on Windows", Constants.WINDOWS); + assumeFalse("FIXME: SOLR-8182: This test fails under Java 9", Constants.JRE_IS_MINIMUM_JAVA9); + + kerberosTestServices = KerberosUtils.setupMiniKdc(createTempDir()); + + configureCluster(NUM_SERVERS)// nodes + .withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_kerberos_authz_config.json")) + .addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf")) + .configure(); + } + + @AfterClass + public static void tearDownClass() throws Exception { + KerberosUtils.cleanupMiniKdc(kerberosTestServices); + kerberosTestServices = null; + } + + @Test + public void testCollectionCreateSearchDelete() throws Exception { + CloudSolrClient solrClient = cluster.getSolrClient(); + String collectionName = "testkerberoscollection_authz"; + + // create collection + CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, "conf1", + NUM_SHARDS, REPLICATION_FACTOR); + create.process(solrClient); + + SolrInputDocument doc = new SolrInputDocument(); + doc.setField("id", "1"); + solrClient.add(collectionName, doc); + solrClient.commit(collectionName); + + SolrQuery query = new SolrQuery(); + query.setQuery("*:*"); + QueryResponse rsp = solrClient.query(collectionName, query); + assertEquals(1, rsp.getResults().getNumFound()); + + CollectionAdminRequest.Delete deleteReq = CollectionAdminRequest.deleteCollection(collectionName); + deleteReq.process(solrClient); + AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName, + solrClient.getZkStateReader(), true, true, 330); + } + +} diff --git a/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java b/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java index 6ac9403cb627..8b250d0f9954 100644 --- a/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java +++ b/solr/core/src/test/org/apache/solr/security/hadoop/TestSolrCloudWithHadoopAuthPlugin.java @@ -16,10 +16,6 @@ */ package org.apache.solr.security.hadoop; -import java.io.File; -import java.nio.charset.StandardCharsets; - -import org.apache.commons.io.FileUtils; import org.apache.lucene.util.Constants; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.impl.CloudSolrClient; @@ -44,7 +40,7 @@ public static void setupClass() throws Exception { assumeFalse("Hadoop does not work on Windows", Constants.WINDOWS); assumeFalse("FIXME: SOLR-8182: This test fails under Java 9", Constants.JRE_IS_MINIMUM_JAVA9); - setupMiniKdc(); + kerberosTestServices = KerberosUtils.setupMiniKdc(createTempDir()); configureCluster(NUM_SERVERS)// nodes .withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_kerberos_config.json")) @@ -54,55 +50,10 @@ public static void setupClass() throws Exception { @AfterClass public static void tearDownClass() throws Exception { - System.clearProperty("java.security.auth.login.config"); - System.clearProperty("solr.kerberos.principal"); - System.clearProperty("solr.kerberos.keytab"); - System.clearProperty("solr.kerberos.name.rules"); - System.clearProperty("solr.jaas.debug"); - if (kerberosTestServices != null) { - kerberosTestServices.stop(); - } + KerberosUtils.cleanupMiniKdc(kerberosTestServices); kerberosTestServices = null; } - private static void setupMiniKdc() throws Exception { - System.setProperty("solr.jaas.debug", "true"); - String kdcDir = createTempDir()+File.separator+"minikdc"; - String solrClientPrincipal = "solr"; - File keytabFile = new File(kdcDir, "keytabs"); - kerberosTestServices = KerberosTestServices.builder() - .withKdc(new File(kdcDir)) - .withJaasConfiguration(solrClientPrincipal, keytabFile, "SolrClient") - .build(); - String solrServerPrincipal = "HTTP/127.0.0.1"; - kerberosTestServices.start(); - kerberosTestServices.getKdc().createPrincipal(keytabFile, solrServerPrincipal, solrClientPrincipal); - - String jaas = "SolrClient {\n" - + " com.sun.security.auth.module.Krb5LoginModule required\n" - + " useKeyTab=true\n" - + " keyTab=\"" + keytabFile.getAbsolutePath() + "\"\n" - + " storeKey=true\n" - + " useTicketCache=false\n" - + " doNotPrompt=true\n" - + " debug=true\n" - + " principal=\"" + solrClientPrincipal + "\";\n" - + "};"; - - String jaasFilePath = kdcDir+File.separator+"jaas-client.conf"; - FileUtils.write(new File(jaasFilePath), jaas, StandardCharsets.UTF_8); - System.setProperty("java.security.auth.login.config", jaasFilePath); - System.setProperty("solr.kerberos.jaas.appname", "SolrClient"); // Get this app name from the jaas file - - System.setProperty("solr.kerberos.principal", solrServerPrincipal); - System.setProperty("solr.kerberos.keytab", keytabFile.getAbsolutePath()); - // Extracts 127.0.0.1 from HTTP/127.0.0.1@EXAMPLE.COM - System.setProperty("solr.kerberos.name.rules", "RULE:[1:$1@$0](.*EXAMPLE.COM)s/@.*//" - + "\nRULE:[2:$2@$0](.*EXAMPLE.COM)s/@.*//" - + "\nDEFAULT" - ); - } - @Test public void testBasics() throws Exception { testCollectionCreateSearchDelete();