You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ja...@apache.org on 2021/11/19 09:02:28 UTC

[lucene-solr] branch branch_8x updated: SOLR-15768 Tune zookeeper request handler permissions (#2604)

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

janhoy pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new c2f26ac  SOLR-15768 Tune zookeeper request handler permissions (#2604)
c2f26ac is described below

commit c2f26ac784945dca6d096b58f2d0e98196562894
Author: Jan Høydahl <ja...@users.noreply.github.com>
AuthorDate: Fri Nov 19 10:02:07 2021 +0100

    SOLR-15768 Tune zookeeper request handler permissions (#2604)
---
 solr/CHANGES.txt                                   |  2 +
 .../solr/handler/admin/ZookeeperInfoHandler.java   | 26 +++++--
 .../solr/handler/admin/ZookeeperReadAPI.java       | 91 ++++++++++++++++------
 3 files changed, 91 insertions(+), 28 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 9c509d5..cfaca6f 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -17,6 +17,8 @@ Bug Fixes
 
 * SOLR-15804: Admin UI once again can show files in the Core/Collection -> Files screen. Fixed regression from 8.11.0 (Karl Stoney, janhoy)
 
+* SOLR-15768: Set default permissions for the zookeeper endpoints so users won't need to configure custom permissions for these (janhoy)
+
 * SOLR-15774: Avoid weird off-by-one errors with Angular's 'chosen' select box directive for the security and schema-designer screens in Admin UI (Timothy Potter)
 
 ==================  8.11.0 ==================
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java
index 3396e8b..851a85b 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperInfoHandler.java
@@ -46,6 +46,7 @@ import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.OnReconnect;
 import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.MapSolrParams;
@@ -58,6 +59,8 @@ import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.JSONResponseWriter;
 import org.apache.solr.response.RawResponseWriter;
 import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.PermissionNameProvider;
 import org.apache.solr.util.SimplePostTool.BAOS;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.WatchedEvent;
@@ -79,7 +82,8 @@ import static org.apache.solr.common.params.CommonParams.WT;
  *
  * @since solr 4.0
  */
-public final class ZookeeperInfoHandler extends RequestHandlerBase {
+public final class ZookeeperInfoHandler extends RequestHandlerBase implements PermissionNameProvider {
+  private static final String PARAM_DETAIL = "detail";
   private final CoreContainer cores;
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -110,6 +114,18 @@ public final class ZookeeperInfoHandler extends RequestHandlerBase {
     none, name, status
   }
 
+  @Override
+  public PermissionNameProvider.Name getPermissionName(AuthorizationContext request) {
+    SolrParams params = request.getParams();
+    String path = params.get(PATH, "");
+    String detail = params.get(PARAM_DETAIL, "false");
+    if ("/security.json".equalsIgnoreCase(path) && "true".equalsIgnoreCase(detail)) {
+      return PermissionNameProvider.Name.SECURITY_READ_PERM;
+    } else {
+      return PermissionNameProvider.Name.ZK_READ_PERM;
+    }
+  }
+
   /**
    * Holds state of a single page of collections requested from the cloud panel.
    */
@@ -184,15 +200,15 @@ public final class ZookeeperInfoHandler extends RequestHandlerBase {
       boolean hasDownedShard = false; // means one or more shards is down
       boolean replicaInRecovery = false;
 
-      Map<String, Object> shards = (Map<String, Object>) collectionState.get("shards");
+      Map<String, Object> shards = (Map<String, Object>) collectionState.get(DocCollection.SHARDS);
       for (Object o : shards.values()) {
         boolean hasActive = false;
         Map<String, Object> shard = (Map<String, Object>) o;
-        Map<String, Object> replicas = (Map<String, Object>) shard.get("replicas");
+        Map<String, Object> replicas = (Map<String, Object>) shard.get(Slice.REPLICAS);
         for (Object value : replicas.values()) {
           Map<String, Object> replicaState = (Map<String, Object>) value;
           Replica.State coreState = Replica.State.getState((String) replicaState.get(ZkStateReader.STATE_PROP));
-          String nodeName = (String) replicaState.get("node_name");
+          String nodeName = (String) replicaState.get(ZkStateReader.NODE_NAME_PROP);
 
           // state can lie to you if the node is offline, so need to reconcile with live_nodes too
           if (!liveNodes.contains(nodeName))
@@ -379,7 +395,7 @@ public final class ZookeeperInfoHandler extends RequestHandlerBase {
       throw new SolrException(ErrorCode.BAD_REQUEST, "Illegal parameter \"addr\"");
     }
 
-    String detailS = params.get("detail");
+    String detailS = params.get(PARAM_DETAIL);
     boolean detail = detailS != null && detailS.equals("true");
 
     String dumpS = params.get("dump");
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
index 0220ca5..68f6fee 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/ZookeeperReadAPI.java
@@ -45,6 +45,7 @@ import org.apache.zookeeper.data.Stat;
 import static org.apache.solr.common.params.CommonParams.OMIT_HEADER;
 import static org.apache.solr.common.params.CommonParams.WT;
 import static org.apache.solr.response.RawResponseWriter.CONTENT;
+import static org.apache.solr.security.PermissionNameProvider.Name.SECURITY_READ_PERM;
 import static org.apache.solr.security.PermissionNameProvider.Name.ZK_READ_PERM;
 
 /**
@@ -58,41 +59,42 @@ import static org.apache.solr.security.PermissionNameProvider.Name.ZK_READ_PERM;
 
 public class ZookeeperReadAPI {
   private final CoreContainer coreContainer;
+  private final SolrParams rawWtParams;
 
   public ZookeeperReadAPI(CoreContainer coreContainer) {
     this.coreContainer = coreContainer;
+    Map<String, String> map = new HashMap<>(1);
+    map.put(WT, "raw");
+    map.put(OMIT_HEADER, "true");
+    rawWtParams = new MapSolrParams(map);
   }
+
+  /**
+   * Request contents of a znode, except security.json
+   */
   @EndPoint(path = "/cluster/zk/data/*",
       method = SolrRequest.METHOD.GET,
       permission = ZK_READ_PERM)
   public void readNode(SolrQueryRequest req, SolrQueryResponse rsp) {
     String path = req.getPathTemplateValues().get("*");
     if (path == null || path.isEmpty()) path = "/";
-    byte[] d = null;
-    try {
-      d = coreContainer.getZkController().getZkClient().getData(path, null, null, false);
-    } catch (KeeperException.NoNodeException e) {
-      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No such node: " + path);
-    } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error", e);
-    }
-    if (d == null || d.length == 0) {
-      rsp.add(path, null);
-      return;
-    }
-
-    Map<String, String> map = new HashMap<>(1);
-    map.put(WT, "raw");
-    map.put(OMIT_HEADER, "true");
-    req.setParams(SolrParams.wrapDefaults(new MapSolrParams(map), req.getParams()));
-
-    String mime = BinaryResponseParser.BINARY_CONTENT_TYPE;
+    readNodeAndAddToResponse(path, req, rsp);
+  }
 
-    if (d[0] == '{') mime = CommonParams.JSON_MIME;
-    if (d[0] == '<' || d[1] == '?') mime = XMLResponseParser.XML_CONTENT_TYPE;
-    rsp.add(CONTENT, new ContentStreamBase.ByteArrayStream(d, null, mime));
+  /**
+   * Request contents of the security.json node
+   */
+  @EndPoint(path = "/cluster/zk/data/security.json",
+      method = SolrRequest.METHOD.GET,
+      permission = SECURITY_READ_PERM)
+  public void readSecurityJsonNode(SolrQueryRequest req, SolrQueryResponse rsp) {
+    String path = "/security.json";
+    readNodeAndAddToResponse(path, req, rsp);
   }
 
+  /**
+   * List the children of a certain zookeeper znode
+   */
   @EndPoint(path = "/cluster/zk/ls/*",
       method = SolrRequest.METHOD.GET,
       permission = ZK_READ_PERM)
@@ -118,7 +120,7 @@ public class ZookeeperReadAPI {
       }
       rsp.add(path, (MapWriter) ew -> {
         for (Map.Entry<String, Stat> e : stats.entrySet()) {
-         ew.put(e.getKey(), (MapWriter) ew1 -> printStat(ew1, e.getValue()));
+          ew.put(e.getKey(), (MapWriter) ew1 -> printStat(ew1, e.getValue()));
         }
       });
     } catch (KeeperException.NoNodeException e) {
@@ -130,6 +132,49 @@ public class ZookeeperReadAPI {
     }
   }
 
+  /**
+   * Simple mime type guessing based on first character of the response
+   */
+  private String guessMime(byte firstByte) {
+    switch(firstByte) {
+      case '{':
+        return CommonParams.JSON_MIME;
+      case '<':
+      case '?':
+        return XMLResponseParser.XML_CONTENT_TYPE;
+      default:
+        return BinaryResponseParser.BINARY_CONTENT_TYPE;
+    }
+  }
+
+  /**
+   * Reads content of a znode and adds it to the response
+   */
+  private void readNodeAndAddToResponse(String zkPath, SolrQueryRequest req, SolrQueryResponse rsp) {
+    byte[] d = readPathFromZookeeper(zkPath);
+    if (d == null || d.length == 0) {
+      rsp.add(zkPath, null);
+      return;
+    }
+    req.setParams(SolrParams.wrapDefaults(rawWtParams, req.getParams()));
+    rsp.add(CONTENT, new ContentStreamBase.ByteArrayStream(d, null, guessMime(d[0])));
+  }
+
+  /**
+   * Reads a single node from zookeeper and return as byte array
+   */
+  private byte[] readPathFromZookeeper(String path) {
+    byte[] d;
+    try {
+      d = coreContainer.getZkController().getZkClient().getData(path, null, null, false);
+    } catch (KeeperException.NoNodeException e) {
+      throw new SolrException(SolrException.ErrorCode.NOT_FOUND, "No such node: " + path);
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error", e);
+    }
+    return d;
+  }
+
   private void printStat(MapWriter.EntryWriter ew, Stat stat) throws IOException {
     ew.put("version", stat.getVersion());
     ew.put("aversion", stat.getAversion());