mirror of https://github.com/apache/lucene.git
Create znode upfront and fix chroot handling in delegation token feature
This commit is contained in:
parent
8e2dab7315
commit
b091934f9e
|
@ -46,6 +46,8 @@ import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
|
|||
import org.apache.solr.common.cloud.SolrZkClient;
|
||||
import org.apache.solr.common.cloud.ZkACLProvider;
|
||||
import org.apache.solr.common.cloud.ZkCredentialsProvider;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.data.ACL;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -65,8 +67,12 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
|
|||
if (conf != null && "zookeeper".equals(conf.getInitParameter("signer.secret.provider"))) {
|
||||
SolrZkClient zkClient =
|
||||
(SolrZkClient)conf.getServletContext().getAttribute(KerberosPlugin.DELEGATION_TOKEN_ZK_CLIENT);
|
||||
conf.getServletContext().setAttribute("signer.secret.provider.zookeeper.curator.client",
|
||||
getCuratorClient(zkClient));
|
||||
try {
|
||||
conf.getServletContext().setAttribute("signer.secret.provider.zookeeper.curator.client",
|
||||
getCuratorClient(zkClient));
|
||||
} catch (InterruptedException | KeeperException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
super.init(conf);
|
||||
}
|
||||
|
@ -147,7 +153,7 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
|
|||
newAuthHandler.setAuthHandler(authHandler);
|
||||
}
|
||||
|
||||
protected CuratorFramework getCuratorClient(SolrZkClient zkClient) {
|
||||
protected CuratorFramework getCuratorClient(SolrZkClient zkClient) throws InterruptedException, KeeperException {
|
||||
// should we try to build a RetryPolicy off of the ZkController?
|
||||
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
|
||||
if (zkClient == null) {
|
||||
|
@ -161,6 +167,17 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
|
|||
SolrZkToCuratorCredentialsACLs curatorToSolrZk = new SolrZkToCuratorCredentialsACLs(zkClient);
|
||||
final int connectionTimeoutMs = 30000; // this value is currently hard coded, see SOLR-7561.
|
||||
|
||||
// Create /security znode upfront. Without this, the curator framework creates this directory path
|
||||
// without the appropriate ACL configuration. This issue is possibly related to HADOOP-11973
|
||||
try {
|
||||
zkClient.makePath(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, CreateMode.PERSISTENT, true);
|
||||
|
||||
} catch (KeeperException ex) {
|
||||
if (ex.code() != KeeperException.Code.NODEEXISTS) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
curatorFramework = CuratorFrameworkFactory.builder()
|
||||
.namespace(zkNamespace)
|
||||
.connectString(zkConnectionString)
|
||||
|
@ -178,12 +195,15 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
|
|||
* Convert Solr Zk Credentials/ACLs to Curator versions
|
||||
*/
|
||||
protected static class SolrZkToCuratorCredentialsACLs {
|
||||
private final String zkChroot;
|
||||
private final ACLProvider aclProvider;
|
||||
private final List<AuthInfo> authInfos;
|
||||
|
||||
public SolrZkToCuratorCredentialsACLs(SolrZkClient zkClient) {
|
||||
this.aclProvider = createACLProvider(zkClient);
|
||||
this.authInfos = createAuthInfo(zkClient);
|
||||
String zkHost = zkClient.getZkServerAddress();
|
||||
this.zkChroot = zkHost.contains("/")? zkHost.substring(zkHost.indexOf("/")): null;
|
||||
}
|
||||
|
||||
public ACLProvider getACLProvider() { return aclProvider; }
|
||||
|
@ -199,8 +219,20 @@ public class DelegationTokenKerberosFilter extends DelegationTokenAuthentication
|
|||
|
||||
@Override
|
||||
public List<ACL> getAclForPath(String path) {
|
||||
List<ACL> acls = zkACLProvider.getACLsToAdd(path);
|
||||
return acls;
|
||||
List<ACL> acls = null;
|
||||
|
||||
// The logic in SecurityAwareZkACLProvider does not work when
|
||||
// the Solr zkPath is chrooted (e.g. /solr instead of /). This
|
||||
// due to the fact that the getACLsToAdd(..) callback provides
|
||||
// an absolute path (instead of relative path to the chroot) and
|
||||
// the string comparison in SecurityAwareZkACLProvider fails.
|
||||
if (zkACLProvider instanceof SecurityAwareZkACLProvider && zkChroot != null) {
|
||||
acls = zkACLProvider.getACLsToAdd(path.replace(zkChroot, ""));
|
||||
} else {
|
||||
acls = zkACLProvider.getACLsToAdd(path);
|
||||
}
|
||||
|
||||
return acls;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
|
|||
import org.apache.solr.common.cloud.SolrZkClient;
|
||||
import org.apache.solr.common.cloud.ZkACLProvider;
|
||||
import org.apache.solr.common.cloud.ZkCredentialsProvider;
|
||||
import org.apache.zookeeper.CreateMode;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.data.ACL;
|
||||
|
||||
/**
|
||||
|
@ -62,8 +64,12 @@ public class HadoopAuthFilter extends DelegationTokenAuthenticationFilter {
|
|||
if (conf != null && "zookeeper".equals(conf.getInitParameter("signer.secret.provider"))) {
|
||||
SolrZkClient zkClient =
|
||||
(SolrZkClient)conf.getServletContext().getAttribute(DELEGATION_TOKEN_ZK_CLIENT);
|
||||
conf.getServletContext().setAttribute("signer.secret.provider.zookeeper.curator.client",
|
||||
getCuratorClient(zkClient));
|
||||
try {
|
||||
conf.getServletContext().setAttribute("signer.secret.provider.zookeeper.curator.client",
|
||||
getCuratorClient(zkClient));
|
||||
} catch (KeeperException | InterruptedException e) {
|
||||
throw new ServletException(e);
|
||||
}
|
||||
}
|
||||
super.init(conf);
|
||||
}
|
||||
|
@ -125,7 +131,7 @@ public class HadoopAuthFilter extends DelegationTokenAuthenticationFilter {
|
|||
newAuthHandler.setAuthHandler(authHandler);
|
||||
}
|
||||
|
||||
protected CuratorFramework getCuratorClient(SolrZkClient zkClient) {
|
||||
protected CuratorFramework getCuratorClient(SolrZkClient zkClient) throws KeeperException, InterruptedException {
|
||||
// should we try to build a RetryPolicy off of the ZkController?
|
||||
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
|
||||
if (zkClient == null) {
|
||||
|
@ -139,6 +145,17 @@ public class HadoopAuthFilter extends DelegationTokenAuthenticationFilter {
|
|||
SolrZkToCuratorCredentialsACLs curatorToSolrZk = new SolrZkToCuratorCredentialsACLs(zkClient);
|
||||
final int connectionTimeoutMs = 30000; // this value is currently hard coded, see SOLR-7561.
|
||||
|
||||
// Create /security znode upfront. Without this, the curator framework creates this directory path
|
||||
// without the appropriate ACL configuration. This issue is possibly related to HADOOP-11973
|
||||
try {
|
||||
zkClient.makePath(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, CreateMode.PERSISTENT, true);
|
||||
|
||||
} catch (KeeperException ex) {
|
||||
if (ex.code() != KeeperException.Code.NODEEXISTS) {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
curatorFramework = CuratorFrameworkFactory.builder()
|
||||
.namespace(zkNamespace)
|
||||
.connectString(zkConnectionString)
|
||||
|
@ -156,12 +173,15 @@ public class HadoopAuthFilter extends DelegationTokenAuthenticationFilter {
|
|||
* Convert Solr Zk Credentials/ACLs to Curator versions
|
||||
*/
|
||||
protected static class SolrZkToCuratorCredentialsACLs {
|
||||
private final String zkChroot;
|
||||
private final ACLProvider aclProvider;
|
||||
private final List<AuthInfo> authInfos;
|
||||
|
||||
public SolrZkToCuratorCredentialsACLs(SolrZkClient zkClient) {
|
||||
this.aclProvider = createACLProvider(zkClient);
|
||||
this.authInfos = createAuthInfo(zkClient);
|
||||
String zkHost = zkClient.getZkServerAddress();
|
||||
this.zkChroot = zkHost.contains("/")? zkHost.substring(zkHost.indexOf("/")): null;
|
||||
}
|
||||
|
||||
public ACLProvider getACLProvider() { return aclProvider; }
|
||||
|
@ -177,8 +197,20 @@ public class HadoopAuthFilter extends DelegationTokenAuthenticationFilter {
|
|||
|
||||
@Override
|
||||
public List<ACL> getAclForPath(String path) {
|
||||
List<ACL> acls = zkACLProvider.getACLsToAdd(path);
|
||||
return acls;
|
||||
List<ACL> acls = null;
|
||||
|
||||
// The logic in SecurityAwareZkACLProvider does not work when
|
||||
// the Solr zkPath is chrooted (e.g. /solr instead of /). This
|
||||
// due to the fact that the getACLsToAdd(..) callback provides
|
||||
// an absolute path (instead of relative path to the chroot) and
|
||||
// the string comparison in SecurityAwareZkACLProvider fails.
|
||||
if (zkACLProvider instanceof SecurityAwareZkACLProvider && zkChroot != null) {
|
||||
acls = zkACLProvider.getACLsToAdd(path.replace(zkChroot, ""));
|
||||
} else {
|
||||
acls = zkACLProvider.getACLsToAdd(path);
|
||||
}
|
||||
|
||||
return acls;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -142,6 +142,7 @@ public class HadoopAuthPlugin extends AuthenticationPlugin {
|
|||
authFilter.init(conf);
|
||||
|
||||
} catch (ServletException e) {
|
||||
log.error("Error initializing " + getClass().getSimpleName(), e);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, "Error initializing " + getClass().getName() + ": "+e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* 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.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.lucene.util.Constants;
|
||||
import org.apache.solr.cloud.MiniSolrCloudCluster;
|
||||
import org.apache.solr.cloud.SolrCloudTestCase;
|
||||
import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
|
||||
import org.apache.solr.common.cloud.ZkCredentialsProvider;
|
||||
import org.apache.solr.common.cloud.ZkStateReader;
|
||||
import org.apache.zookeeper.KeeperException;
|
||||
import org.apache.zookeeper.KeeperException.Code;
|
||||
import org.apache.zookeeper.WatchedEvent;
|
||||
import org.apache.zookeeper.Watcher;
|
||||
import org.apache.zookeeper.ZooDefs;
|
||||
import org.apache.zookeeper.ZooKeeper;
|
||||
import org.apache.zookeeper.data.ACL;
|
||||
import org.apache.zookeeper.data.Id;
|
||||
import org.apache.zookeeper.data.Stat;
|
||||
import org.apache.zookeeper.server.ServerCnxn;
|
||||
import org.apache.zookeeper.server.auth.AuthenticationProvider;
|
||||
import org.apache.zookeeper.server.auth.ProviderRegistry;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestZkAclsWithHadoopAuth extends SolrCloudTestCase {
|
||||
protected static final int NUM_SERVERS = 1;
|
||||
protected static final int NUM_SHARDS = 1;
|
||||
protected static final int REPLICATION_FACTOR = 1;
|
||||
|
||||
@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);
|
||||
|
||||
System.setProperty("zookeeper.authProvider.1", DummyZKAuthProvider.class.getName());
|
||||
System.setProperty("zkCredentialsProvider", DummyZkCredentialsProvider.class.getName());
|
||||
System.setProperty("zkACLProvider", DummyZkAclProvider.class.getName());
|
||||
|
||||
ProviderRegistry.initialize();
|
||||
|
||||
configureCluster(NUM_SERVERS)// nodes
|
||||
.withSolrXml(MiniSolrCloudCluster.DEFAULT_CLOUD_SOLR_XML)
|
||||
.withSecurityJson(TEST_PATH().resolve("security").resolve("hadoop_simple_auth_with_delegation.json"))
|
||||
.addConfig("conf1", TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
|
||||
.configure();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownClass() {
|
||||
System.clearProperty("zookeeper.authProvider.1");
|
||||
System.clearProperty("zkCredentialsProvider");
|
||||
System.clearProperty("zkACLProvider");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testZkAcls() throws Exception {
|
||||
ZooKeeper keeper = null;
|
||||
try {
|
||||
keeper = new ZooKeeper(cluster.getZkServer().getZkAddress(), (int) TimeUnit.MINUTES.toMillis(1), new Watcher() {
|
||||
@Override
|
||||
public void process(WatchedEvent arg0) {
|
||||
// Do nothing
|
||||
}
|
||||
});
|
||||
|
||||
keeper.addAuthInfo("dummyauth", "solr".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Test well known paths.
|
||||
checkNonSecurityACLs(keeper, "/solr.xml");
|
||||
checkSecurityACLs(keeper, "/security/token");
|
||||
checkSecurityACLs(keeper, "/security");
|
||||
|
||||
// Now test all ZK tree.
|
||||
String zkHost = cluster.getSolrClient().getZkHost();
|
||||
String zkChroot = zkHost.contains("/")? zkHost.substring(zkHost.indexOf("/")): null;
|
||||
walkZkTree(keeper, zkChroot, "/");
|
||||
|
||||
} finally {
|
||||
if (keeper != null) {
|
||||
keeper.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void walkZkTree (ZooKeeper keeper, String zkChroot, String path) throws Exception {
|
||||
if (isSecurityZNode(zkChroot, path)) {
|
||||
checkSecurityACLs(keeper, path);
|
||||
} else {
|
||||
checkNonSecurityACLs(keeper, path);
|
||||
}
|
||||
|
||||
List<String> children = keeper.getChildren(path, false);
|
||||
for (String child : children) {
|
||||
String subpath = path.endsWith("/") ? path + child : path + "/" + child;
|
||||
walkZkTree(keeper, zkChroot, subpath);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSecurityZNode(String zkChroot, String path) {
|
||||
String temp = path;
|
||||
if (zkChroot != null) {
|
||||
temp = path.replace(zkChroot, "");
|
||||
}
|
||||
return !ZkStateReader.SOLR_SECURITY_CONF_PATH.equals(path) &&
|
||||
temp.startsWith(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH);
|
||||
}
|
||||
|
||||
private void checkSecurityACLs(ZooKeeper keeper, String path) throws Exception {
|
||||
List<ACL> acls = keeper.getACL(path, new Stat());
|
||||
String message = String.format(Locale.ROOT, "Path %s ACLs found %s", path, acls);
|
||||
assertEquals(message, 1, acls.size());
|
||||
assertTrue(message, acls.contains(new ACL(ZooDefs.Perms.ALL, new Id("dummyauth", "solr"))));
|
||||
}
|
||||
|
||||
private void checkNonSecurityACLs(ZooKeeper keeper, String path) throws Exception {
|
||||
List<ACL> acls = keeper.getACL(path, new Stat());
|
||||
String message = String.format(Locale.ROOT, "Path %s ACLs found %s", path, acls);
|
||||
assertEquals(message, 2, acls.size());
|
||||
assertTrue(message, acls.contains(new ACL(ZooDefs.Perms.ALL, new Id("dummyauth", "solr"))));
|
||||
assertTrue(message, acls.contains(new ACL(ZooDefs.Perms.READ, new Id("world", "anyone"))));
|
||||
}
|
||||
|
||||
public static class DummyZKAuthProvider implements AuthenticationProvider {
|
||||
public static final String zkSuperUser = "zookeeper";
|
||||
public static final Collection<String> validUsers = Arrays.asList(zkSuperUser, "solr", "foo");
|
||||
|
||||
@Override
|
||||
public String getScheme() {
|
||||
return "dummyauth";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Code handleAuthentication(ServerCnxn arg0, byte[] arg1) {
|
||||
String userName = new String(arg1, StandardCharsets.UTF_8);
|
||||
|
||||
if (validUsers.contains(userName)) {
|
||||
if (zkSuperUser.equals(userName)) {
|
||||
arg0.addAuthInfo(new Id("super", ""));
|
||||
}
|
||||
arg0.addAuthInfo(new Id(getScheme(), userName));
|
||||
return KeeperException.Code.OK;
|
||||
}
|
||||
|
||||
return KeeperException.Code.AUTHFAILED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAuthenticated() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(String arg0) {
|
||||
return (arg0 != null) && validUsers.contains(arg0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(String arg0, String arg1) {
|
||||
return arg0.equals(arg1);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyZkCredentialsProvider implements ZkCredentialsProvider {
|
||||
public static final Collection<ZkCredentials> solrCreds =
|
||||
Arrays.asList(new ZkCredentials("dummyauth", "solr".getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
@Override
|
||||
public Collection<ZkCredentials> getCredentials() {
|
||||
return solrCreds;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyZkAclProvider extends SecurityAwareZkACLProvider {
|
||||
|
||||
@Override
|
||||
protected List<ACL> createNonSecurityACLsToAdd() {
|
||||
List<ACL> result = new ArrayList<>(2);
|
||||
result.add(new ACL(ZooDefs.Perms.ALL, new Id("dummyauth", "solr")));
|
||||
result.add(new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ACL> createSecurityACLsToAdd() {
|
||||
List<ACL> ret = new ArrayList<ACL>();
|
||||
ret.add(new ACL(ZooDefs.Perms.ALL, new Id("dummyauth", "solr")));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -86,6 +86,8 @@ public class MiniSolrCloudCluster {
|
|||
" <int name=\"leaderVoteWait\">10000</int>\n" +
|
||||
" <int name=\"distribUpdateConnTimeout\">${distribUpdateConnTimeout:45000}</int>\n" +
|
||||
" <int name=\"distribUpdateSoTimeout\">${distribUpdateSoTimeout:340000}</int>\n" +
|
||||
" <str name=\"zkCredentialsProvider\">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str> \n" +
|
||||
" <str name=\"zkACLProvider\">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str> \n" +
|
||||
" </solrcloud>\n" +
|
||||
" <metrics>\n" +
|
||||
" <reporter name=\"default\" class=\"org.apache.solr.metrics.reporters.SolrJmxReporter\">\n" +
|
||||
|
|
Loading…
Reference in New Issue