| /* |
| * 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.cloud; |
| |
| import java.io.IOException; |
| import java.io.OutputStreamWriter; |
| import java.lang.invoke.MethodHandles; |
| import java.nio.charset.Charset; |
| import java.nio.charset.StandardCharsets; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.util.Properties; |
| |
| import org.apache.solr.SolrTestCaseJ4; |
| import org.apache.solr.common.cloud.SecurityAwareZkACLProvider; |
| import org.apache.solr.common.cloud.SolrZkClient; |
| import org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider; |
| import org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider; |
| import org.apache.zookeeper.CreateMode; |
| import org.apache.zookeeper.KeeperException.NoAuthException; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE; |
| |
| public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 { |
| |
| private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| private static final Charset DATA_ENCODING = Charset.forName("UTF-8"); |
| |
| protected ZkTestServer zkServer; |
| |
| protected Path zkDir; |
| |
| @BeforeClass |
| public static void beforeClass() { |
| System.setProperty("solrcloud.skip.autorecovery", "true"); |
| } |
| |
| @AfterClass |
| public static void afterClass() throws InterruptedException { |
| System.clearProperty("solrcloud.skip.autorecovery"); |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| if (log.isInfoEnabled()) { |
| log.info("####SETUP_START {}", getTestName()); |
| } |
| createTempDir(); |
| |
| zkDir = createTempDir().resolve("zookeeper/server1/data"); |
| log.info("ZooKeeper dataDir:{}", zkDir); |
| zkServer = new ZkTestServer(zkDir); |
| zkServer.run(false); |
| |
| System.setProperty("zkHost", zkServer.getZkAddress()); |
| |
| setSecuritySystemProperties(); |
| |
| SolrZkClient zkClient = new SolrZkClient(zkServer.getZkHost(), |
| AbstractZkTestCase.TIMEOUT, AbstractZkTestCase.TIMEOUT, null, null, null); |
| zkClient.makePath("/solr", false, true); |
| zkClient.close(); |
| |
| zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT); |
| zkClient.create("/protectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false); |
| zkClient.makePath("/protectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false); |
| |
| zkClient.create(SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false); |
| zkClient.close(); |
| |
| clearSecuritySystemProperties(); |
| |
| zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT); |
| // Currently no credentials on ZK connection, because those same VM-params are used for adding ACLs, and here we want |
| // no (or completely open) ACLs added. Therefore hack your way into being authorized for creating anyway |
| zkClient.getSolrZooKeeper().addAuthInfo("digest", ("connectAndAllACLUsername:connectAndAllACLPassword") |
| .getBytes(StandardCharsets.UTF_8)); |
| zkClient.create("/unprotectedCreateNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false); |
| zkClient.makePath("/unprotectedMakePathNode", "content".getBytes(DATA_ENCODING), CreateMode.PERSISTENT, false); |
| zkClient.close(); |
| |
| if (log.isInfoEnabled()) { |
| log.info("####SETUP_END {}", getTestName()); |
| } |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| zkServer.shutdown(); |
| |
| clearSecuritySystemProperties(); |
| |
| super.tearDown(); |
| } |
| |
| @Test |
| public void testNoCredentials() throws Exception { |
| useNoCredentials(); |
| |
| SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT); |
| try { |
| doTest(zkClient, |
| false, false, false, false, false, |
| false, false, false, false, false); |
| } finally { |
| zkClient.close(); |
| } |
| } |
| |
| @Test |
| public void testWrongCredentials() throws Exception { |
| useWrongCredentials(); |
| |
| SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT); |
| try { |
| doTest(zkClient, |
| false, false, false, false, false, |
| false, false, false, false, false); |
| } finally { |
| zkClient.close(); |
| } |
| } |
| |
| @Test |
| public void testAllCredentials() throws Exception { |
| useAllCredentials(); |
| |
| SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT); |
| try { |
| doTest(zkClient, |
| true, true, true, true, true, |
| true, true, true, true, true); |
| } finally { |
| zkClient.close(); |
| } |
| } |
| |
| @Test |
| public void testReadonlyCredentials() throws Exception { |
| useReadonlyCredentials(); |
| |
| SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT); |
| try { |
| doTest(zkClient, |
| true, true, false, false, false, |
| false, false, false, false, false); |
| } finally { |
| zkClient.close(); |
| } |
| } |
| |
| @Test |
| public void testReadonlyCredentialsFromFile() throws Exception { |
| useReadonlyCredentialsFromFile(); |
| |
| try (SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) { |
| doTest(zkClient, |
| true, true, false, false, false, |
| false, false, false, false, false); |
| } |
| } |
| |
| @Test |
| public void testAllCredentialsFromFile() throws Exception { |
| useAllCredentialsFromFile(); |
| |
| try (SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) { |
| doTest(zkClient, |
| true, true, true, true, true, |
| true, true, true, true, true); |
| } |
| } |
| |
| @Test |
| public void testRepairACL() throws Exception { |
| clearSecuritySystemProperties(); |
| try (SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) { |
| // Currently no credentials on ZK connection, because those same VM-params are used for adding ACLs, and here we want |
| // no (or completely open) ACLs added. Therefore hack your way into being authorized for creating anyway |
| zkClient.getSolrZooKeeper().addAuthInfo("digest", ("connectAndAllACLUsername:connectAndAllACLPassword") |
| .getBytes(StandardCharsets.UTF_8)); |
| |
| zkClient.create("/security.json", "{}".getBytes(StandardCharsets.UTF_8), CreateMode.PERSISTENT, false); |
| assertEquals(OPEN_ACL_UNSAFE, zkClient.getACL("/security.json", null, false)); |
| } |
| |
| setSecuritySystemProperties(); |
| try (SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) { |
| ZkController.createClusterZkNodes(zkClient); |
| assertNotEquals(OPEN_ACL_UNSAFE, zkClient.getACL("/security.json", null, false)); |
| } |
| |
| useReadonlyCredentials(); |
| try (SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), AbstractZkTestCase.TIMEOUT)) { |
| NoAuthException e = assertThrows(NoAuthException.class, () -> zkClient.getData("/security.json", null, null, false)); |
| assertEquals("/security.json", e.getPath()); |
| } |
| } |
| |
| protected static void doTest( |
| SolrZkClient zkClient, |
| boolean getData, boolean list, boolean create, boolean setData, boolean delete, |
| boolean secureGet, boolean secureList, boolean secureCreate, boolean secureSet, boolean secureDelete) throws Exception { |
| doTest(zkClient, "/protectedCreateNode", getData, list, create, setData, delete); |
| doTest(zkClient, "/protectedMakePathNode", getData, list, create, setData, delete); |
| doTest(zkClient, "/unprotectedCreateNode", true, true, true, true, delete); |
| doTest(zkClient, "/unprotectedMakePathNode", true, true, true, true, delete); |
| doTest(zkClient, SecurityAwareZkACLProvider.SECURITY_ZNODE_PATH, secureGet, secureList, secureCreate, secureSet, secureDelete); |
| } |
| |
| protected static void doTest(SolrZkClient zkClient, String path, boolean getData, boolean list, boolean create, boolean setData, boolean delete) throws Exception { |
| try { |
| zkClient.getData(path, null, null, false); |
| if (!getData) fail("NoAuthException expected "); |
| } catch (NoAuthException nae) { |
| if (getData) fail("No NoAuthException expected"); |
| // expected |
| } |
| |
| try { |
| zkClient.getChildren(path, null, false); |
| if (!list) fail("NoAuthException expected "); |
| } catch (NoAuthException nae) { |
| if (list) fail("No NoAuthException expected"); |
| // expected |
| } |
| |
| try { |
| zkClient.create(path + "/subnode", null, CreateMode.PERSISTENT, false); |
| if (!create) fail("NoAuthException expected "); |
| else { |
| zkClient.delete(path + "/subnode", -1, false); |
| } |
| } catch (NoAuthException nae) { |
| if (create) { |
| nae.printStackTrace(); |
| fail("No NoAuthException expected"); |
| } |
| // expected |
| } |
| |
| try { |
| zkClient.makePath(path + "/subnode/subsubnode", false); |
| if (!create) fail("NoAuthException expected "); |
| else { |
| zkClient.delete(path + "/subnode/subsubnode", -1, false); |
| zkClient.delete(path + "/subnode", -1, false); |
| } |
| } catch (NoAuthException nae) { |
| if (create) fail("No NoAuthException expected"); |
| // expected |
| } |
| |
| try { |
| zkClient.setData(path, (byte[])null, false); |
| if (!setData) fail("NoAuthException expected "); |
| } catch (NoAuthException nae) { |
| if (setData) fail("No NoAuthException expected"); |
| // expected |
| } |
| |
| try { |
| // Actually about the ACLs on /solr, but that is protected |
| zkClient.delete(path, -1, false); |
| if (!delete) fail("NoAuthException expected "); |
| } catch (NoAuthException nae) { |
| if (delete) fail("No NoAuthException expected"); |
| // expected |
| } |
| |
| } |
| |
| private void useNoCredentials() { |
| clearSecuritySystemProperties(); |
| } |
| |
| private void useWrongCredentials() { |
| clearSecuritySystemProperties(); |
| |
| System.setProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME, VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName()); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "connectAndAllACLUsername"); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "connectAndAllACLPasswordWrong"); |
| } |
| |
| private void useAllCredentials() { |
| clearSecuritySystemProperties(); |
| |
| System.setProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME, VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName()); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "connectAndAllACLUsername"); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "connectAndAllACLPassword"); |
| } |
| |
| private void useReadonlyCredentials() { |
| clearSecuritySystemProperties(); |
| |
| System.setProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME, VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName()); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "readonlyACLUsername"); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "readonlyACLPassword"); |
| } |
| |
| private void useReadonlyCredentialsFromFile() throws IOException { |
| useCredentialsFromFile("readonlyACLUsername", "readonlyACLPassword"); |
| } |
| |
| private void useAllCredentialsFromFile() throws IOException { |
| useCredentialsFromFile("connectAndAllACLUsername", "connectAndAllACLPassword"); |
| } |
| |
| private void useCredentialsFromFile(String username, String password) throws IOException { |
| clearSecuritySystemProperties(); |
| |
| System.setProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME, VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName()); |
| |
| Properties props = new Properties(); |
| props.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, username); |
| props.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, password); |
| saveCredentialsFile(props); |
| } |
| |
| private void saveCredentialsFile(Properties props) throws IOException { |
| Path tmp = createTempFile("zk-creds", "properties"); |
| try (OutputStreamWriter w = new OutputStreamWriter(Files.newOutputStream(tmp), StandardCharsets.UTF_8)) { |
| props.store(w, "test"); |
| } |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_FILE_VM_PARAM_NAME, tmp.toAbsolutePath().toString()); |
| } |
| |
| private void setSecuritySystemProperties() { |
| System.setProperty(SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME, VMParamsAllAndReadonlyDigestZkACLProvider.class.getName()); |
| System.setProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME, VMParamsSingleSetCredentialsDigestZkCredentialsProvider.class.getName()); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, "connectAndAllACLUsername"); |
| System.setProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME, "connectAndAllACLPassword"); |
| System.setProperty(VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME, "readonlyACLUsername"); |
| System.setProperty(VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME, "readonlyACLPassword"); |
| } |
| |
| private void clearSecuritySystemProperties() { |
| System.clearProperty(SolrZkClient.ZK_ACL_PROVIDER_CLASS_NAME_VM_PARAM_NAME); |
| System.clearProperty(SolrZkClient.ZK_CRED_PROVIDER_CLASS_NAME_VM_PARAM_NAME); |
| System.clearProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME); |
| System.clearProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME); |
| System.clearProperty(VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME); |
| System.clearProperty(VMParamsAllAndReadonlyDigestZkACLProvider.DEFAULT_DIGEST_READONLY_PASSWORD_VM_PARAM_NAME); |
| System.clearProperty(VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_FILE_VM_PARAM_NAME); |
| } |
| |
| } |