Repository: zookeeper Updated Branches: refs/heads/master d13d2f08c -> 5fe68506f
ZOOKEEPER-2693: DOS attack on wchp/wchc four letter words (4lw) The previous patch is not valid anymore as design has changed. Update patch based on phunt 's feedback. See ZOOKEEPER-2693 JIRA for details. This patch is still targeting for branch-3.5 / master. Patch for branch-3.4 will be created shortly. The gist of the patch is: * Introduced a new configuration option which encodes a white list of 4lw commands. A 4lw command that is not on the white list will not be enabled on server side. * For branch-3.5, the default value of the configuration option is empty. We want users to use the alternative AdminServer anyway. Author: Michael Han <[email protected]> Reviewers: Rakesh Radhakrishnan <rakeshrapache.org>, Mohammad Arshad <[email protected]>, Flavio Junqueira <[email protected]>, Edward Ribeiro <[email protected]>, Abraham Fine <[email protected]> Closes #179 from hanm/ZOOKEEPER-2693 and squashes the following commits: 1ffd114 [Michael Han] Tweak tests. b4c421d [Michael Han] ZOOKEEPER-2693: DOS attack on wchp/wchc four letter words (4lw). Introduce a new configuration option 4lw.commands.whitelist that disables all 4lw (except srvr) by default to avoid the exploits reported on wchp/wchc commands. Project: http://git-wip-us.apache.org/repos/asf/zookeeper/repo Commit: http://git-wip-us.apache.org/repos/asf/zookeeper/commit/5fe68506 Tree: http://git-wip-us.apache.org/repos/asf/zookeeper/tree/5fe68506 Diff: http://git-wip-us.apache.org/repos/asf/zookeeper/diff/5fe68506 Branch: refs/heads/master Commit: 5fe68506f217246c7ebd96803f9c78e13ec2f11a Parents: d13d2f0 Author: Michael Han <[email protected]> Authored: Sat Mar 4 10:22:03 2017 -0800 Committer: Michael Han <[email protected]> Committed: Sat Mar 4 10:22:03 2017 -0800 ---------------------------------------------------------------------- .../content/xdocs/zookeeperAdmin.xml | 57 ++++- .../apache/zookeeper/server/NIOServerCnxn.java | 21 +- .../zookeeper/server/NettyServerCnxn.java | 22 +- .../server/command/FourLetterCommands.java | 86 ++++++- .../zookeeper/server/command/NopCommand.java | 41 +++ .../test/org/apache/zookeeper/ZKTestCase.java | 3 + .../test/FourLetterWordsWhiteListTest.java | 251 +++++++++++++++++++ 7 files changed, 466 insertions(+), 15 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml ---------------------------------------------------------------------- diff --git a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml index f9dff1e..6db8e40 100644 --- a/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml +++ b/src/docs/src/documentation/content/xdocs/zookeeperAdmin.xml @@ -1155,6 +1155,40 @@ server.3=zoo3:2888:3888</programlisting> </listitem> </varlistentry> + <varlistentry> + <term>4lw.commands.whitelist</term> + + <listitem> + <para>(Java system property: <emphasis + role="bold">zookeeper.4lw.commands.whitelist</emphasis>)</para> + + <para><emphasis role="bold">New in 3.5.3:</emphasis> + A list of comma separated <ulink url="#sc_4lw">Four Letter Words</ulink> + commands that user wants to use. A valid Four Letter Words + command must be put in this list else ZooKeeper server will + not enable the command. + By default the whitelist only contains "srvr" command + which zkServer.sh uses. The rest of four letter word commands are disabled + by default. + </para> + + <para>Here's an example of the configuration that enables stat, ruok, conf, and isro + command while disabling the rest of Four Letter Words command:</para> + <programlisting> + 4lw.commands.whitelist=stat, ruok, conf, isro + </programlisting> + + <para>If you really need enable all four letter word commands by default, you can use + the asterisk option so you don't have to include every command one by one in the list. + As an example, this will enable all four letter word commands: + </para> + <programlisting> + 4lw.commands.whitelist=* + </programlisting> + + </listitem> + </varlistentry> + </variablelist> <para></para> </section> @@ -1639,7 +1673,7 @@ server.3=zoo3:2888:3888</programlisting> <section id="sc_zkCommands"> <title>ZooKeeper Commands</title> - <section> + <section id="sc_4lw"> <title>The Four Letter Words</title> <para>ZooKeeper responds to a small set of commands. Each command is composed of four letters. You issue the commands to ZooKeeper via telnet @@ -1650,7 +1684,16 @@ server.3=zoo3:2888:3888</programlisting> while "srvr" and "cons" give extended details on server and connections respectively.</para> - <variablelist> + <para><emphasis role="bold">New in 3.5.3:</emphasis> + Four Letter Words need to be explicitly white listed before using. + Please refer <emphasis role="bold">4lw.commands.whitelist</emphasis> + described in <ulink url="#sc_clusterOptions"> + cluster configuration section</ulink> for details. + Moving forward, Four Letter Words will be deprecated, please use + <ulink url="#sc_adminserver">AdminServer</ulink> instead. + </para> + + <variablelist> <varlistentry> <term>conf</term> @@ -2125,6 +2168,16 @@ server.3=zoo3:2888:3888</programlisting> usage limit that would cause the system to swap.</para> </listitem> </varlistentry> + + <varlistentry> + <term>Publicly accessible deployment</term> + <listitem> + <para> + A ZooKeeper ensemble is expected to operate in a trusted computing environment. + It is thus recommended to deploy ZooKeeper behind a firewall. + </para> + </listitem> + </varlistentry> </variablelist> </section> http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java ---------------------------------------------------------------------- diff --git a/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java b/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java index 76b9be0..6dae02d 100644 --- a/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java +++ b/src/java/main/org/apache/zookeeper/server/NIOServerCnxn.java @@ -47,6 +47,7 @@ import org.apache.zookeeper.server.NIOServerCnxnFactory.SelectorThread; import org.apache.zookeeper.server.command.CommandExecutor; import org.apache.zookeeper.server.command.FourLetterCommands; import org.apache.zookeeper.server.command.SetTraceMaskCommand; +import org.apache.zookeeper.server.command.NopCommand; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -478,12 +479,11 @@ public class NIOServerCnxn extends ServerCnxn { { // We take advantage of the limited size of the length to look // for cmds. They are all 4-bytes which fits inside of an int - String cmd = FourLetterCommands.getCmdMapView().get(len); - if (cmd == null) { + if (!FourLetterCommands.isKnown(len)) { return false; } - LOG.info("Processing " + cmd + " command from " - + sock.socket().getRemoteSocketAddress()); + + String cmd = FourLetterCommands.getCommandString(len); packetReceived(); /** cancel the selection key to remove the socket handling @@ -505,6 +505,19 @@ public class NIOServerCnxn extends ServerCnxn { final PrintWriter pwriter = new PrintWriter( new BufferedWriter(new SendBufferWriter())); + + // ZOOKEEPER-2693: don't execute 4lw if it's not enabled. + if (!FourLetterCommands.isEnabled(cmd)) { + LOG.debug("Command {} is not executed because it is not in the whitelist.", cmd); + NopCommand nopCmd = new NopCommand(pwriter, this, cmd + + " is not executed because it is not in the whitelist."); + nopCmd.start(); + return true; + } + + LOG.info("Processing " + cmd + " command from " + + sock.socket().getRemoteSocketAddress()); + if (len == FourLetterCommands.setTraceMaskCmd) { incomingBuffer = ByteBuffer.allocate(8); int rc = sock.read(incomingBuffer); http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java ---------------------------------------------------------------------- diff --git a/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java b/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java index 9b6f28e..cf5bd8a 100644 --- a/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java +++ b/src/java/main/org/apache/zookeeper/server/NettyServerCnxn.java @@ -41,6 +41,7 @@ import org.apache.zookeeper.proto.ReplyHeader; import org.apache.zookeeper.proto.WatcherEvent; import org.apache.zookeeper.server.command.CommandExecutor; import org.apache.zookeeper.server.command.FourLetterCommands; +import org.apache.zookeeper.server.command.NopCommand; import org.apache.zookeeper.server.command.SetTraceMaskCommand; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; @@ -267,17 +268,30 @@ public class NettyServerCnxn extends ServerCnxn { { // We take advantage of the limited size of the length to look // for cmds. They are all 4-bytes which fits inside of an int - String cmd = FourLetterCommands.getCmdMapView().get(len); - if (cmd == null) { + if (!FourLetterCommands.isKnown(len)) { return false; } + + String cmd = FourLetterCommands.getCommandString(len); + channel.setInterestOps(0).awaitUninterruptibly(); - LOG.info("Processing " + cmd + " command from " - + channel.getRemoteAddress()); packetReceived(); final PrintWriter pwriter = new PrintWriter( new BufferedWriter(new SendBufferWriter())); + + // ZOOKEEPER-2693: don't execute 4lw if it's not enabled. + if (!FourLetterCommands.isEnabled(cmd)) { + LOG.debug("Command {} is not executed because it is not in the whitelist.", cmd); + NopCommand nopCmd = new NopCommand(pwriter, this, cmd + + " is not executed because it is not in the whitelist."); + nopCmd.start(); + return true; + } + + LOG.info("Processing " + cmd + " command from " + + channel.getRemoteAddress()); + if (len == FourLetterCommands.setTraceMaskCmd) { ByteBuffer mask = ByteBuffer.allocate(8); message.readBytes(mask); http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java ---------------------------------------------------------------------- diff --git a/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java b/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java index 0364f44..82e09dc 100644 --- a/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java +++ b/src/java/main/org/apache/zookeeper/server/command/FourLetterCommands.java @@ -18,10 +18,15 @@ package org.apache.zookeeper.server.command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.nio.ByteBuffer; -import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.HashSet; +import java.util.Set; +import java.util.Arrays; /** * This class contains constants for all the four letter commands @@ -153,11 +158,82 @@ public class FourLetterCommands { */ public final static int telnetCloseCmd = 0xfff4fffd; - final static HashMap<Integer, String> cmd2String = - new HashMap<Integer, String>(); + private static final String ZOOKEEPER_4LW_COMMANDS_WHITELIST = "zookeeper.4lw.commands.whitelist"; + + private static final Logger LOG = LoggerFactory.getLogger(FourLetterCommands.class); + + private static final Map<Integer, String> cmd2String = new HashMap<Integer, String>(); + + private static final Set<String> whiteListedCommands = new HashSet<String>(); + + private static boolean whiteListInitialized = false; + + // @VisibleForTesting + public static void resetWhiteList() { + whiteListInitialized = false; + whiteListedCommands.clear(); + } + + /** + * Return the string representation of the specified command code. + */ + public static String getCommandString(int command) { + return cmd2String.get(command); + } + + /** + * Check if the specified command code is from a known command. + * + * @param command The integer code of command. + * @return true if the specified command is known, false otherwise. + */ + public static boolean isKnown(int command) { + return cmd2String.containsKey(command); + } + + /** + * Check if the specified command is enabled. + * + * In ZOOKEEPER-2693 we introduce a configuration option to only + * allow a specific set of white listed commands to execute. + * A command will only be executed if it is also configured + * in the white list. + * + * @param command The command string. + * @return true if the specified command is enabled + */ + public static boolean isEnabled(String command) { + if (whiteListInitialized) { + return whiteListedCommands.contains(command); + } + + String commands = System.getProperty(ZOOKEEPER_4LW_COMMANDS_WHITELIST); + if (commands != null) { + String[] list = commands.split(","); + for (String cmd : list) { + if (cmd.trim().equals("*")) { + for (Map.Entry<Integer, String> entry : cmd2String.entrySet()) { + whiteListedCommands.add(entry.getValue()); + } + break; + } + if (!cmd.trim().isEmpty()) { + whiteListedCommands.add(cmd.trim()); + } + } + } - public static Map<Integer, String> getCmdMapView() { - return Collections.unmodifiableMap(cmd2String); + // It is sad that isro and srvr are used by ZooKeeper itself. Need fix this + // before deprecating 4lw. + if (System.getProperty("readonlymode.enabled", "false").equals("true")) { + whiteListedCommands.add("isro"); + } + // zkServer.sh depends on "srvr". + whiteListedCommands.add("srvr"); + whiteListInitialized = true; + LOG.info("The list of known four letter word commands is : {}", Arrays.asList(cmd2String)); + LOG.info("The list of enabled four letter word commands is : {}", Arrays.asList(whiteListedCommands)); + return whiteListedCommands.contains(command); } // specify all of the commands that are available http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/java/main/org/apache/zookeeper/server/command/NopCommand.java ---------------------------------------------------------------------- diff --git a/src/java/main/org/apache/zookeeper/server/command/NopCommand.java b/src/java/main/org/apache/zookeeper/server/command/NopCommand.java new file mode 100644 index 0000000..4924fcf --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/command/NopCommand.java @@ -0,0 +1,41 @@ +/** + * 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.zookeeper.server.command; + +import java.io.PrintWriter; + +import org.apache.zookeeper.server.ServerCnxn; + +/** + * A command that does not do anything except reply to client with predefined message. + * It is used to inform clients who execute none white listed four letter word commands. + */ +public class NopCommand extends AbstractFourLetterCommand { + private String msg; + + public NopCommand(PrintWriter pw, ServerCnxn serverCnxn, String msg) { + super(pw, serverCnxn); + this.msg = msg; + } + + @Override + public void commandRun() { + pw.println(msg); + } +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/java/test/org/apache/zookeeper/ZKTestCase.java ---------------------------------------------------------------------- diff --git a/src/java/test/org/apache/zookeeper/ZKTestCase.java b/src/java/test/org/apache/zookeeper/ZKTestCase.java index 4776f8b..54a6be5 100644 --- a/src/java/test/org/apache/zookeeper/ZKTestCase.java +++ b/src/java/test/org/apache/zookeeper/ZKTestCase.java @@ -51,6 +51,9 @@ public class ZKTestCase { // accidentally attempting to start multiple admin servers on the // same port. System.setProperty("zookeeper.admin.enableServer", "false"); + // ZOOKEEPER-2693 disables all 4lw by default. + // Here we enable the 4lw which ZooKeeper tests depends. + System.setProperty("zookeeper.4lw.commands.whitelist", "*"); testName = method.getName(); LOG.info("STARTING " + testName); } http://git-wip-us.apache.org/repos/asf/zookeeper/blob/5fe68506/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java ---------------------------------------------------------------------- diff --git a/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java b/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java new file mode 100644 index 0000000..f5d6967 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/test/FourLetterWordsWhiteListTest.java @@ -0,0 +1,251 @@ +/** + * 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.zookeeper.test; + +import java.io.IOException; + +import org.apache.zookeeper.TestableZooKeeper; +import org.apache.zookeeper.common.X509Exception.SSLContextException; + +import static org.apache.zookeeper.client.FourLetterWordMain.send4LetterWord; + +import org.apache.zookeeper.server.command.FourLetterCommands; +import org.junit.Assert; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class FourLetterWordsWhiteListTest extends ClientBase { + protected static final Logger LOG = + LoggerFactory.getLogger(FourLetterWordsWhiteListTest.class); + + /* + * ZOOKEEPER-2693: test white list of four letter words. + * For 3.5.x default white list is empty. Verify that is + * the case (except 'stat' command which is enabled in ClientBase + * which other tests depend on.). + */ + @Test(timeout=30000) + public void testFourLetterWordsAllDisabledByDefault() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "stat"); + startServer(); + + // Default white list for 3.5.x is empty, so all command should fail. + verifyAllCommandsFail(); + + TestableZooKeeper zk = createClient(); + + verifyAllCommandsFail(); + + zk.getData("/", true, null); + + verifyAllCommandsFail(); + + zk.close(); + + verifyFuzzyMatch("stat", "Outstanding"); + verifyAllCommandsFail(); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableSomeCommands() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "stat, ruok, isro"); + startServer(); + // stat, ruok and isro are white listed. + verifyFuzzyMatch("stat", "Outstanding"); + verifyExactMatch("ruok", "imok"); + verifyExactMatch("isro", "rw"); + + // Rest of commands fail. + verifyExactMatch("conf", generateExpectedMessage("conf")); + verifyExactMatch("cons", generateExpectedMessage("cons")); + verifyExactMatch("crst", generateExpectedMessage("crst")); + verifyExactMatch("dirs", generateExpectedMessage("dirs")); + verifyExactMatch("dump", generateExpectedMessage("dump")); + verifyExactMatch("envi", generateExpectedMessage("envi")); + verifyExactMatch("gtmk", generateExpectedMessage("gtmk")); + verifyExactMatch("stmk", generateExpectedMessage("stmk")); + verifyExactMatch("srst", generateExpectedMessage("srst")); + verifyExactMatch("wchc", generateExpectedMessage("wchc")); + verifyExactMatch("wchp", generateExpectedMessage("wchp")); + verifyExactMatch("wchs", generateExpectedMessage("wchs")); + verifyExactMatch("mntr", generateExpectedMessage("mntr")); + } + + @Test(timeout=30000) + public void testISROEnabledWhenReadOnlyModeEnabled() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "stat"); + System.setProperty("readonlymode.enabled", "true"); + startServer(); + verifyExactMatch("isro", "rw"); + System.clearProperty("readonlymode.enabled"); + } + + @Test(timeout=30000) + public void testFourLetterWordsInvalidConfiguration() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "foo bar" + + " foo,,, " + + "bar :.,@#$%^&*() , , , , bar, bar, stat, "); + startServer(); + + // Just make sure we are good when admin made some mistakes in config file. + verifyAllCommandsFail(); + // But still, what's valid in white list will get through. + verifyFuzzyMatch("stat", "Outstanding"); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableAllCommandsThroughAsterisk() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", "*"); + startServer(); + verifyAllCommandsSuccess(); + } + + @Test(timeout=30000) + public void testFourLetterWordsEnableAllCommandsThroughExplicitList() throws Exception { + stopServer(); + FourLetterCommands.resetWhiteList(); + System.setProperty("zookeeper.4lw.commands.whitelist", + "ruok, envi, conf, stat, srvr, cons, dump," + + "wchs, wchp, wchc, srst, crst, " + + "dirs, mntr, gtmk, isro, stmk"); + startServer(); + verifyAllCommandsSuccess(); + } + + + private void verifyAllCommandsSuccess() throws Exception { + verifyExactMatch("ruok", "imok"); + verifyFuzzyMatch("envi", "java.version"); + verifyFuzzyMatch("conf", "clientPort"); + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("dump", "Session"); + verifyFuzzyMatch("wchs", "watches"); + verifyFuzzyMatch("wchp", ""); + verifyFuzzyMatch("wchc", ""); + + verifyFuzzyMatch("srst", "reset"); + verifyFuzzyMatch("crst", "reset"); + + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("gtmk", "306"); + verifyFuzzyMatch("isro", "rw"); + + TestableZooKeeper zk = createClient(); + String sid = getHexSessionId(zk.getSessionId()); + + verifyFuzzyMatch("stat", "queued"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", sid); + verifyFuzzyMatch("dump", sid); + verifyFuzzyMatch("dirs", "size"); + + zk.getData("/", true, null); + + verifyFuzzyMatch("stat", "queued"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", sid); + verifyFuzzyMatch("dump", sid); + + verifyFuzzyMatch("wchs", "watching 1"); + verifyFuzzyMatch("wchp", sid); + verifyFuzzyMatch("wchc", sid); + verifyFuzzyMatch("dirs", "size"); + zk.close(); + + verifyExactMatch("ruok", "imok"); + verifyFuzzyMatch("envi", "java.version"); + verifyFuzzyMatch("conf", "clientPort"); + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("dump", "Session"); + verifyFuzzyMatch("wchs", "watch"); + verifyFuzzyMatch("wchp", ""); + verifyFuzzyMatch("wchc", ""); + + verifyFuzzyMatch("srst", "reset"); + verifyFuzzyMatch("crst", "reset"); + + verifyFuzzyMatch("stat", "Outstanding"); + verifyFuzzyMatch("srvr", "Outstanding"); + verifyFuzzyMatch("cons", "queued"); + verifyFuzzyMatch("mntr", "zk_server_state\tstandalone"); + verifyFuzzyMatch("mntr", "num_alive_connections"); + verifyFuzzyMatch("stat", "Connections"); + verifyFuzzyMatch("srvr", "Connections"); + verifyFuzzyMatch("dirs", "size"); + } + + private void verifyAllCommandsFail() throws Exception { + verifyExactMatch("ruok", generateExpectedMessage("ruok")); + verifyExactMatch("conf", generateExpectedMessage("conf")); + verifyExactMatch("cons", generateExpectedMessage("cons")); + verifyExactMatch("crst", generateExpectedMessage("crst")); + verifyExactMatch("dirs", generateExpectedMessage("dirs")); + verifyExactMatch("dump", generateExpectedMessage("dump")); + verifyExactMatch("envi", generateExpectedMessage("envi")); + verifyExactMatch("gtmk", generateExpectedMessage("gtmk")); + verifyExactMatch("stmk", generateExpectedMessage("stmk")); + verifyExactMatch("srst", generateExpectedMessage("srst")); + verifyExactMatch("wchc", generateExpectedMessage("wchc")); + verifyExactMatch("wchp", generateExpectedMessage("wchp")); + verifyExactMatch("wchs", generateExpectedMessage("wchs")); + verifyExactMatch("mntr", generateExpectedMessage("mntr")); + verifyExactMatch("isro", generateExpectedMessage("isro")); + + // srvr is enabled by default due to the sad fact zkServer.sh uses it. + verifyFuzzyMatch("srvr", "Outstanding"); + } + + private String sendRequest(String cmd) throws IOException, SSLContextException { + HostPort hpobj = ClientBase.parseHostPortList(hostPort).get(0); + return send4LetterWord(hpobj.host, hpobj.port, cmd); + } + + private void verifyFuzzyMatch(String cmd, String expected) throws IOException, SSLContextException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected " + expected + " got " + resp); + Assert.assertTrue(resp.contains(expected)); + } + + private String generateExpectedMessage(String command) { + return command + " is not executed because it is not in the whitelist."; + } + + private void verifyExactMatch(String cmd, String expected) throws IOException, SSLContextException { + String resp = sendRequest(cmd); + LOG.info("cmd " + cmd + " expected an exact match of " + expected + "; got " + resp); + Assert.assertTrue(resp.trim().equals(expected)); + } +}
