This is an automated email from the ASF dual-hosted git repository. mdrob pushed a commit to branch branch_8_8 in repository https://gitbox.apache.org/repos/asf/solr.git
commit 90671d8072bd09d4acaee0c390a53984b7474ebe Author: Mike Drob <md...@apache.org> AuthorDate: Mon Mar 15 15:50:45 2021 -0500 SOLR-15249 Properly set ZK ACLs on /security.json --- solr/CHANGES.txt | 4 ++- .../java/org/apache/solr/cloud/ZkController.java | 7 +++- .../authentication-and-authorization-plugins.adoc | 2 ++ .../src/basic-authentication-plugin.adoc | 9 +---- .../src/zookeeper-access-control.adoc | 38 +++++++++++++++------- .../common/cloud/SecurityAwareZkACLProvider.java | 9 ++--- .../org/apache/solr/common/cloud/SolrZkClient.java | 15 +++++++-- .../java/org/apache/solr/cloud/ZkTestServer.java | 37 +++++++++++++++------ 8 files changed, 82 insertions(+), 39 deletions(-) diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index f98c617..f651885 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -10,7 +10,9 @@ Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this r Bug Fixes --------------------- -(No changes) + +* SOLR-15249: Properly set ZK ACLs on /security.json (Mike Drob) + ================== 8.8.1 ================== diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java index 02ae1bc..475eb41 100644 --- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java +++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java @@ -134,6 +134,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.ELECTION_NODE_PROP; import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP; import static org.apache.solr.common.cloud.ZkStateReader.REJOIN_AT_HEAD_PROP; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; +import static org.apache.zookeeper.ZooDefs.Ids.OPEN_ACL_UNSAFE; /** * Handle ZooKeeper interactions. @@ -861,8 +862,12 @@ public class ZkController implements Closeable { cmdExecutor.ensureExists(ZkStateReader.SOLR_AUTOSCALING_NODE_LOST_PATH, zkClient); byte[] emptyJson = "{}".getBytes(StandardCharsets.UTF_8); cmdExecutor.ensureExists(ZkStateReader.CLUSTER_STATE, emptyJson, CreateMode.PERSISTENT, zkClient); - cmdExecutor.ensureExists(ZkStateReader.SOLR_SECURITY_CONF_PATH, emptyJson, CreateMode.PERSISTENT, zkClient); cmdExecutor.ensureExists(ZkStateReader.SOLR_AUTOSCALING_CONF_PATH, emptyJson, CreateMode.PERSISTENT, zkClient); + cmdExecutor.ensureExists(ZkStateReader.SOLR_SECURITY_CONF_PATH, emptyJson, CreateMode.PERSISTENT, zkClient); + if (zkClient.getACL(ZkStateReader.SOLR_SECURITY_CONF_PATH, null, true).equals(OPEN_ACL_UNSAFE)) { + log.warn("Contents of zookeeper /security.json are world-readable;" + + " consider setting up ACLs as described in https://solr.apache.org/guide/zookeeper-access-control.html"); + } bootstrapDefaultConfigSet(zkClient); } diff --git a/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc b/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc index 9e8e724..a7f3da5 100644 --- a/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc +++ b/solr/solr-ref-guide/src/authentication-and-authorization-plugins.adoc @@ -82,6 +82,8 @@ Note that this example defines the `KerberosPlugin` for authentication. You will This example also defines `security.json` on the command line, but you can also define a file locally and upload it to ZooKeeper. +NOTE: If you have defined `ZK_HOST` in `solr.in.sh`/`solr.in.cmd` (see <<setting-up-an-external-zookeeper-ensemble#updating-solr-include-files,instructions>>) you can omit `-z <zk host string>` from the above command. + [WARNING] ==== Whenever you use any security plugins and store `security.json` in ZooKeeper, we highly recommend that you implement access control in your ZooKeeper nodes. Information about how to enable this is available in the section <<zookeeper-access-control.adoc#zookeeper-access-control,ZooKeeper Access Control>>. diff --git a/solr/solr-ref-guide/src/basic-authentication-plugin.adoc b/solr/solr-ref-guide/src/basic-authentication-plugin.adoc index 64bef86..0deb84a 100644 --- a/solr/solr-ref-guide/src/basic-authentication-plugin.adoc +++ b/solr/solr-ref-guide/src/basic-authentication-plugin.adoc @@ -64,14 +64,7 @@ If `blockUnknown` does not appear in the `security.json` file, it will default t If `realm` is not defined, it will default to `solr`. -If you are using SolrCloud, you must upload `security.json` to ZooKeeper. You can use this example command, ensuring that the ZooKeeper port is correct: - -[source,bash] ----- -$ bin/solr zk cp file:path_to_local_security.json zk:/security.json -z localhost:9983 ----- - -NOTE: If you have defined `ZK_HOST` in `solr.in.sh`/`solr.in.cmd` (see <<setting-up-an-external-zookeeper-ensemble#updating-solr-include-files,instructions>>) you can omit `-z <zk host string>` from the above command. +If you are using SolrCloud, you must upload `security.json` to ZooKeeper. An example command and more information about securing your setup can be found at <<authentication-and-authorization-plugins#in-solrcloud-mode,Authentication and Authorization Plugins In SolrCloud Mode>>. === Caveats diff --git a/solr/solr-ref-guide/src/zookeeper-access-control.adoc b/solr/solr-ref-guide/src/zookeeper-access-control.adoc index 55ada59..6382cae 100644 --- a/solr/solr-ref-guide/src/zookeeper-access-control.adoc +++ b/solr/solr-ref-guide/src/zookeeper-access-control.adoc @@ -73,7 +73,7 @@ You control which ACLs will be added by configuring `zkACLProvider` property in You can always make you own implementation, but Solr comes with: * `org.apache.solr.common.cloud.DefaultZkACLProvider`: It returns a list of length one for all `zNodePath`-s. The single ACL entry in the list is "open-unsafe". This is the default. -* `org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider`: This lets you define your ACLs using system properties. Its `getACLsToAdd()` implementation does not use `zNodePath` for anything, so all znodes will get the same set of ACLs. It supports adding one or both of these options: +* `org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider`: This lets you define your ACLs using system properties. Its `getACLsToAdd()` implementation will apply only admin ACLs to pre-defined sensitive paths as defined by `SecurityAwareZkACLProvider` (`/security.json` and `/security/*`) and both admin and user ACLs to the rest of the contents. The two sets of roles will be defined as: ** A user that is allowed to do everything. *** The permission is `ALL` (corresponding to all of `CREATE`, `READ`, `WRITE`, `DELETE`, and `ADMIN`), and the schema is "digest". *** The username and password are defined by system properties `zkDigestUsername` and `zkDigestPassword`, respectively. @@ -97,8 +97,17 @@ There are two scripts that impact ZooKeeper ACLs: * For *nix systems: `bin/solr` & `server/scripts/cloud-scripts/zkcli.sh` * For Windows systems: `bin/solr.cmd` & `server/scripts/cloud-scripts/zkcli.bat` +[IMPORTANT] +Both the solr.in.* and the zkcli.* files will need to be updated with the same password for everything to work. The contents may appear redundant, but the scripts will not consult each other during operations. + These Solr scripts can enable use of ZooKeeper ACLs by setting the appropriate system properties: uncomment the following and replace the passwords with ones you choose to enable the above-described VM parameters ACL and credentials providers in the following files: +[.dynamic-tabs] +-- +[example.tab-pane#nix] +==== +[.tab-label]**nix* + .solr.in.sh [source,bash] ---- @@ -110,6 +119,21 @@ These Solr scripts can enable use of ZooKeeper ACLs by setting the appropriate s #SOLR_OPTS="$SOLR_OPTS $SOLR_ZK_CREDS_AND_ACLS" ---- +.zkcli.sh +[source,bash] +---- +# Settings for ZK ACL +#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider \ +# -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider \ +# -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \ +# -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD" +---- +==== + +[example.tab-pane#windows] +==== +[.tab-label]*Windows* + .solr.in.cmd [source,powershell] ---- @@ -121,16 +145,6 @@ REM -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEM REM set SOLR_OPTS=%SOLR_OPTS% %SOLR_ZK_CREDS_AND_ACLS% ---- -.zkcli.sh -[source,bash] ----- -# Settings for ZK ACL -#SOLR_ZK_CREDS_AND_ACLS="-DzkACLProvider=org.apache.solr.common.cloud.VMParamsAllAndReadonlyDigestZkACLProvider \ -# -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCredentialsDigestZkCredentialsProvider \ -# -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD \ -# -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD" ----- - .zkcli.bat [source,powershell] ---- @@ -140,6 +154,8 @@ REM -DzkCredentialsProvider=org.apache.solr.common.cloud.VMParamsSingleSetCrede REM -DzkDigestUsername=admin-user -DzkDigestPassword=CHANGEME-ADMIN-PASSWORD ^ REM -DzkDigestReadonlyUsername=readonly-user -DzkDigestReadonlyPassword=CHANGEME-READONLY-PASSWORD ---- +==== +-- == Changing ACL Schemes diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java b/solr/solrj/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java index 1c74d94..2fe2da9 100644 --- a/solr/solrj/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java +++ b/solr/solrj/src/java/org/apache/solr/common/cloud/SecurityAwareZkACLProvider.java @@ -22,7 +22,7 @@ import org.apache.zookeeper.data.ACL; /** * {@link ZkACLProvider} capable of returning a different set of - * {@link ACL}s for security-related znodes (default: subtree under /security) + * {@link ACL}s for security-related znodes (default: subtree under /security and security.json) * vs non-security-related znodes. */ public abstract class SecurityAwareZkACLProvider implements ZkACLProvider { @@ -42,11 +42,8 @@ public abstract class SecurityAwareZkACLProvider implements ZkACLProvider { } protected boolean isSecurityZNodePath(String zNodePath) { - if (zNodePath != null - && (zNodePath.equals(SECURITY_ZNODE_PATH) || zNodePath.startsWith(SECURITY_ZNODE_PATH + "/"))) { - return true; - } - return false; + return zNodePath != null + && (zNodePath.equals(ZkStateReader.SOLR_SECURITY_CONF_PATH) || zNodePath.equals(SECURITY_ZNODE_PATH) || zNodePath.startsWith(SECURITY_ZNODE_PATH + "/")); } /** diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java b/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java index 6943fc5..42ec0f2 100644 --- a/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java +++ b/solr/solrj/src/java/org/apache/solr/common/cloud/SolrZkClient.java @@ -798,11 +798,23 @@ public class SolrZkClient implements Closeable { } /** + * @return the ACLs on a single node in ZooKeeper. + */ + public List<ACL> getACL(String path, Stat stat, boolean retryOnConnLoss) throws KeeperException, InterruptedException { + if (retryOnConnLoss) { + return zkCmdExecutor.retryOperation(() -> keeper.getACL(path, stat)); + } else { + return keeper.getACL(path, stat); + } + } + + /** * Set the ACL on a single node in ZooKeeper. This will replace all existing ACL on that node. * * @param path path to set ACL on e.g. /solr/conf/solrconfig.xml * @param acls a list of {@link ACL}s to be applied * @param retryOnConnLoss true if the command should be retried on connection loss + * @return the stat of the node */ public Stat setACL(String path, List<ACL> acls, boolean retryOnConnLoss) throws InterruptedException, KeeperException { if (retryOnConnLoss) { @@ -821,9 +833,8 @@ public class SolrZkClient implements Closeable { try { setACL(path, getZkACLProvider().getACLsToAdd(path), true); log.debug("Updated ACL on {}", path); - } catch (NoNodeException e) { + } catch (NoNodeException ignored) { // If a node was deleted, don't bother trying to set ACLs on it. - return; } }); } diff --git a/solr/test-framework/src/java/org/apache/solr/cloud/ZkTestServer.java b/solr/test-framework/src/java/org/apache/solr/cloud/ZkTestServer.java index 952f330..138e260 100644 --- a/solr/test-framework/src/java/org/apache/solr/cloud/ZkTestServer.java +++ b/solr/test-framework/src/java/org/apache/solr/cloud/ZkTestServer.java @@ -70,6 +70,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; public class ZkTestServer { @@ -389,11 +390,17 @@ public class ZkTestServer { zkDb.close(); } - if (cnxnFactory != null && cnxnFactory.getLocalPort() != 0) { - waitForServerDown(getZkHost(), 30000); + if (cnxnFactory != null) { + try { + int port = cnxnFactory.getLocalPort(); + if (port > 0) { + waitForServerDown(getZkHost(), 30000); + } + } catch (NullPointerException ignored) { + // server never successfully started + } } } finally { - ObjectReleaseTracker.release(this); } } @@ -533,10 +540,12 @@ public class ZkTestServer { public void run(boolean solrFormat) throws InterruptedException, IOException { log.info("STARTING ZK TEST SERVER"); + AtomicReference<Throwable> zooError = new AtomicReference<>(); try { if (zooThread != null) { throw new IllegalStateException("ZK TEST SERVER IS ALREADY RUNNING"); } + Thread parentThread = Thread.currentThread(); // we don't call super.distribSetUp zooThread = new Thread("ZkTestServer Run Thread") { @@ -570,7 +579,8 @@ public class ZkTestServer { try { zkServer.runFromConfig(config); } catch (Throwable t) { - log.error("zkServer error", t); + zooError.set(t); + parentThread.interrupt(); } } }; @@ -582,15 +592,15 @@ public class ZkTestServer { int port = -1; try { port = getPort(); - } catch (IllegalStateException e) { - + } catch (IllegalStateException ignored) { + // Possibly fix this API to return null instead of throwing } while (port < 1) { Thread.sleep(100); try { port = getPort(); - } catch (IllegalStateException e) { - + } catch (IllegalStateException ignored) { + // Possibly fix this API to return null instead of throwing } if (cnt == 500) { throw new RuntimeException("Could not get the port for ZooKeeper server"); @@ -603,8 +613,15 @@ public class ZkTestServer { init(solrFormat); } catch (Exception e) { - log.error("Error trying to run ZK Test Server", e); - throw new RuntimeException(e); + RuntimeException toThrow = new RuntimeException("Could not get ZK port"); + Throwable t = zooError.get(); + if (t != null) { + toThrow.initCause(t); + toThrow.addSuppressed(e); + } else { + toThrow.initCause(e); + } + throw toThrow; } }