You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by th...@apache.org on 2021/07/16 15:16:26 UTC

[solr] branch main updated: SOLR-15525: Read ZK credentials from a file specified using a system property instead of exposing plain-text credentials (#222)

This is an automated email from the ASF dual-hosted git repository.

thelabdude pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/main by this push:
     new 9501d3f  SOLR-15525: Read ZK credentials from a file specified using a system property instead of exposing plain-text credentials (#222)
9501d3f is described below

commit 9501d3fced2253d60e51c2291ce189d20fe081a6
Author: Timothy Potter <th...@gmail.com>
AuthorDate: Fri Jul 16 09:16:15 2021 -0600

    SOLR-15525: Read ZK credentials from a file specified using a system property instead of exposing plain-text credentials (#222)
---
 solr/CHANGES.txt                                   |  3 ++
 .../VMParamsZkACLAndCredentialsProvidersTest.java  | 57 +++++++++++++++++++++-
 .../src/zookeeper-access-control.adoc              |  2 +
 .../VMParamsAllAndReadonlyDigestZkACLProvider.java | 16 ++++--
 ...eSetCredentialsDigestZkCredentialsProvider.java | 31 ++++++++++--
 5 files changed, 98 insertions(+), 11 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index e9cdb6f..82641c1 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -374,6 +374,9 @@ Improvements
 * SOLR-15499: StatsStream implement ParallelMetricsRollup to allow for tiered computation of SQL metrics
   over collection aliases backed by many collections, potentially with many shards in each (Timothy Potter)
 
+* SOLR-15525: Read ZK credentials from a file specified using a system property instead of exposing plain-text
+  credentials as system properties (Timothy Potter)
+
 Optimizations
 ---------------------
 * SOLR-15433: Replace transient core cache LRU by Caffeine cache. (Bruno Roustant)
diff --git a/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java b/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java
index 47408ab..3b441bf 100644
--- a/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/VMParamsZkACLAndCredentialsProvidersTest.java
@@ -16,10 +16,13 @@
  */
 package org.apache.solr.cloud;
 
+import java.io.FileWriter;
+import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.Charset;
-import java.nio.file.Path;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Properties;
 
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.cloud.SecurityAwareZkACLProvider;
@@ -70,7 +73,7 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
     zkServer.run(false);
     
     System.setProperty("zkHost", zkServer.getZkAddress());
-    
+
     setSecuritySystemProperties();
 
     SolrZkClient zkClient = new SolrZkClient(zkServer.getZkHost(),
@@ -155,6 +158,28 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
   }
 
   @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)) {
@@ -250,6 +275,33 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
     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 (FileWriter w = new FileWriter(tmp.toFile(), 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());
@@ -267,6 +319,7 @@ public class VMParamsZkACLAndCredentialsProvidersTest extends SolrTestCaseJ4 {
     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);
   }
   
 }
diff --git a/solr/solr-ref-guide/src/zookeeper-access-control.adoc b/solr/solr-ref-guide/src/zookeeper-access-control.adoc
index c4313d7..946a9f4 100644
--- a/solr/solr-ref-guide/src/zookeeper-access-control.adoc
+++ b/solr/solr-ref-guide/src/zookeeper-access-control.adoc
@@ -86,6 +86,7 @@ It supports at most one set of credentials.
 The username and password are defined by system properties `zkDigestUsername` and `zkDigestPassword`.
 This set of credentials will be added to the list of credentials returned by `getCredentials()` if both username and password are provided.
 ** If the one set of credentials above is not added to the list, this implementation will fall back to default behavior and use the (empty) credentials list from `DefaultZkCredentialsProvider`.
+** Alternatively, you can set the `zkDigestCredentialsFile` system property to load the `zkDigestUsername` and `zkDigestPassword` from a file instead of exposing the credentials as system properties. The provided file must be a Java properties file and contain both the `zkDigestUsername` and `zkDigestPassword` properties.
 
 === Controlling ACLs
 
@@ -110,6 +111,7 @@ The two sets of roles will be defined as:
 *** The permission is `READ` and the schema is `digest`.
 *** The username and password are defined by system properties `zkDigestReadonlyUsername` and `zkDigestReadonlyPassword`, respectively.
 *** This ACL will not be added to the list of ACLs unless both username and password are provided.
+** Alternatively, you can set the `zkDigestCredentialsFile` system property to load the `zkDigestUsername` and `zkDigestPassword` from a file instead of exposing the credentials as system properties. The provided file must be a Java properties file and contain both the `zkDigestUsername` and `zkDigestPassword` properties for the `ALL` user, as well as the `zkDigestReadonlyUsername` and `zkDigestReadonlyPassword` properties for the `READONLY` user.
 * `org.apache.solr.common.cloud.SaslZkACLProvider`: Requires SASL authentication.
 Gives all permissions for the user specified in system property `solr.authorization.superuser` (default: `solr`) when using SASL, and gives read permissions for anyone else.
 Designed for a setup where configurations have already been set up and will not be modified, or where configuration changes are controlled via Solr APIs.
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java b/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java
index 041762a..e75ceb8 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsAllAndReadonlyDigestZkACLProvider.java
@@ -19,6 +19,7 @@ package org.apache.solr.common.cloud;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Properties;
 
 import org.apache.solr.common.StringUtils;
 import org.apache.zookeeper.ZooDefs;
@@ -26,6 +27,9 @@ import org.apache.zookeeper.data.ACL;
 import org.apache.zookeeper.data.Id;
 import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
 
+import static org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider.DEFAULT_DIGEST_FILE_VM_PARAM_NAME;
+import static org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider.readCredentialsFile;
+
 public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkACLProvider {
 
   public static final String DEFAULT_DIGEST_READONLY_USERNAME_VM_PARAM_NAME = "zkDigestReadonlyUsername";
@@ -35,6 +39,7 @@ public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkAC
   final String zkDigestAllPasswordVMParamName;
   final String zkDigestReadonlyUsernameVMParamName;
   final String zkDigestReadonlyPasswordVMParamName;
+  final Properties credentialsProps;
   
   public VMParamsAllAndReadonlyDigestZkACLProvider() {
     this(
@@ -51,6 +56,9 @@ public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkAC
     this.zkDigestAllPasswordVMParamName = zkDigestAllPasswordVMParamName;
     this.zkDigestReadonlyUsernameVMParamName = zkDigestReadonlyUsernameVMParamName;
     this.zkDigestReadonlyPasswordVMParamName = zkDigestReadonlyPasswordVMParamName;
+
+    String pathToFile = System.getProperty(DEFAULT_DIGEST_FILE_VM_PARAM_NAME);
+    credentialsProps = (pathToFile != null) ? readCredentialsFile(pathToFile) : System.getProperties();
   }
 
   /**
@@ -70,10 +78,10 @@ public class VMParamsAllAndReadonlyDigestZkACLProvider extends SecurityAwareZkAC
   }
 
   protected List<ACL> createACLsToAdd(boolean includeReadOnly) {
-    String digestAllUsername = System.getProperty(zkDigestAllUsernameVMParamName);
-    String digestAllPassword = System.getProperty(zkDigestAllPasswordVMParamName);
-    String digestReadonlyUsername = System.getProperty(zkDigestReadonlyUsernameVMParamName);
-    String digestReadonlyPassword = System.getProperty(zkDigestReadonlyPasswordVMParamName);
+    String digestAllUsername = credentialsProps.getProperty(zkDigestAllUsernameVMParamName);
+    String digestAllPassword = credentialsProps.getProperty(zkDigestAllPasswordVMParamName);
+    String digestReadonlyUsername = credentialsProps.getProperty(zkDigestReadonlyUsernameVMParamName);
+    String digestReadonlyPassword = credentialsProps.getProperty(zkDigestReadonlyPasswordVMParamName);
 
     return createACLsToAdd(includeReadOnly,
         digestAllUsername, digestAllPassword,
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java b/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java
index 50f1771..a8bda51 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/VMParamsSingleSetCredentialsDigestZkCredentialsProvider.java
@@ -16,21 +16,38 @@
  */
 package org.apache.solr.common.cloud;
 
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Properties;
 
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.StringUtils;
 
 public class VMParamsSingleSetCredentialsDigestZkCredentialsProvider extends DefaultZkCredentialsProvider {
-  
+
+  public static final String DEFAULT_DIGEST_FILE_VM_PARAM_NAME = "zkDigestCredentialsFile";
   public static final String DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME = "zkDigestUsername";
   public static final String DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME = "zkDigestPassword";
+
+  static Properties readCredentialsFile(String pathToFile) throws SolrException {
+    Properties props = new Properties();
+    try (Reader reader = new InputStreamReader(new FileInputStream(pathToFile), StandardCharsets.UTF_8)) {
+      props.load(reader);
+    } catch (IOException ioExc) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, ioExc);
+    }
+    return props;
+  }
   
   final String zkDigestUsernameVMParamName;
   final String zkDigestPasswordVMParamName;
-  
+
   public VMParamsSingleSetCredentialsDigestZkCredentialsProvider() {
     this(DEFAULT_DIGEST_USERNAME_VM_PARAM_NAME, DEFAULT_DIGEST_PASSWORD_VM_PARAM_NAME);
   }
@@ -42,9 +59,13 @@ public class VMParamsSingleSetCredentialsDigestZkCredentialsProvider extends Def
 
   @Override
   protected Collection<ZkCredentials> createCredentials() {
-    List<ZkCredentials> result = new ArrayList<ZkCredentials>();
-    String digestUsername = System.getProperty(zkDigestUsernameVMParamName);
-    String digestPassword = System.getProperty(zkDigestPasswordVMParamName);
+    List<ZkCredentials> result = new ArrayList<>();
+
+    String pathToFile = System.getProperty(DEFAULT_DIGEST_FILE_VM_PARAM_NAME);
+    Properties props = (pathToFile != null) ? readCredentialsFile(pathToFile) : System.getProperties();
+
+    String digestUsername = props.getProperty(zkDigestUsernameVMParamName);
+    String digestPassword = props.getProperty(zkDigestPasswordVMParamName);
     if (!StringUtils.isEmpty(digestUsername) && !StringUtils.isEmpty(digestPassword)) {
       result.add(new ZkCredentials("digest",
           (digestUsername + ":" + digestPassword).getBytes(StandardCharsets.UTF_8)));