问题背景
zookeeper为维护人员提供了方便的四字命令。可以返回zookeeper集群的信息,主机的配置,甚至可以关闭zookeeper状态。因此有时候需要通过配置来禁用这些命令。
目前集群中使用的是zookeeper 3.4.6。发现在zoo.cfg
中做出如下配置:
4lw.commands.whitelist=ruok
经测试其他的四字命令仍然可以使用,该配置项没有效果。原因需要进一步研究。
Zookeeper3.4.6相关代码研究
org/apache/zookeeper/server/NettyServerCnxn.java
的checkFourLetterWord
方法
private boolean checkFourLetterWord(final Channel channel,
ChannelBuffer message, final int len) throws IOException
{
// 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 = cmd2String.get(len);
if (cmd == null) {
return false;
}
channel.setInterestOps(0).awaitUninterruptibly();
LOG.info("Processing " + cmd + " command from "
+ channel.getRemoteAddress());
packetReceived();
final PrintWriter pwriter = new PrintWriter(
new BufferedWriter(new SendBufferWriter()));
// 依次判断调用的是哪个四字命令
if (len == ruokCmd) {
RuokCommand ruok = new RuokCommand(pwriter);
ruok.start();
return true;
} else if (len == getTraceMaskCmd) {
TraceMaskCommand tmask = new TraceMaskCommand(pwriter);
tmask.start();
return true;
} else if (len == setTraceMaskCmd) {
ByteBuffer mask = ByteBuffer.allocate(4);
message.readBytes(mask);
bb.flip();
long traceMask = mask.getLong();
ZooTrace.setTextTraceLevel(traceMask);
SetTraceMaskCommand setMask = new SetTraceMaskCommand(pwriter, traceMask);
setMask.start();
return true;
} else if (len == enviCmd) {
EnvCommand env = new EnvCommand(pwriter);
env.start();
return true;
} else if (len == confCmd) {
ConfCommand ccmd = new ConfCommand(pwriter);
ccmd.start();
return true;
} else if (len == srstCmd) {
StatResetCommand strst = new StatResetCommand(pwriter);
strst.start();
return true;
} else if (len == crstCmd) {
CnxnStatResetCommand crst = new CnxnStatResetCommand(pwriter);
crst.start();
return true;
} else if (len == dumpCmd) {
DumpCommand dump = new DumpCommand(pwriter);
dump.start();
return true;
} else if (len == statCmd || len == srvrCmd) {
StatCommand stat = new StatCommand(pwriter, len);
stat.start();
return true;
} else if (len == consCmd) {
ConsCommand cons = new ConsCommand(pwriter);
cons.start();
return true;
} else if (len == wchpCmd || len == wchcCmd || len == wchsCmd) {
WatchCommand wcmd = new WatchCommand(pwriter, len);
wcmd.start();
return true;
} else if (len == mntrCmd) {
MonitorCommand mntr = new MonitorCommand(pwriter);
mntr.start();
return true;
} else if (len == isroCmd) {
IsroCommand isro = new IsroCommand(pwriter);
isro.start();
return true;
}
return false;
}
发现该方法根本没有检查四字命令是否在白名单中。下面研究下同样方法在zookeeper 3.5.5中的实现。
Zookeeper3.5.5 四字命令白名单的实现
我们找到org/apache/zookeeper/server/NettyServerCnxn.java
的checkFourLetterWord
方法
/** Return if four letter word found and responded to, otw false **/
private boolean checkFourLetterWord(final Channel channel, ByteBuf message, final int len) {
// 之前的代码省略...
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;
}
// 以后的代码省略...
}
如果四字命令是在白名单中的,该方法会执行四字命令,并返回true。否则会返回false。
继续查看FourLetterCommands
的isEnabled
方法。
此处的whiteListedCommands
为一个HashSet,第一次使用四字命令的时候才会读取配置文件,延迟初始化。
public synchronized static boolean isEnabled(String command) {
// 如果whiteListedCommands已经初始化,则检查command是否在whiteListedCommands中
if (whiteListInitialized) {
return whiteListedCommands.contains(command);
}
// 获取系统的zookeeper.4lw.commands.whitelist变量值
String commands = System.getProperty(ZOOKEEPER_4LW_COMMANDS_WHITELIST);
if (commands != null) {
// 白名单中多个命令以逗号分隔
String[] list = commands.split(",");
for (String cmd : list) {
// 如果配置的是星号,说明运行运行所有的四字命令,将cmd2String的所有value加入到whiteListedCommands中
if (cmd.trim().equals("*")) {
for (Map.Entry<Integer, String> entry : cmd2String.entrySet()) {
whiteListedCommands.add(entry.getValue());
}
break;
}
// 其余情况,把属性中配置的命令加入whiteListedCommands
if (!cmd.trim().isEmpty()) {
whiteListedCommands.add(cmd.trim());
}
}
}
// 由于isro和srvr是zookeeper内部使用的,因此需要额外将他们添加进来。
// 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")) {
// 如果zookeeper是只读模式,增加isro命令到白名单
whiteListedCommands.add("isro");
}
// zkServer.sh depends on "srvr".
// 增加srvr命令的支持,zkServer脚本的status命令需要用到
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));
//whiteListedCommands初始化完毕,返回结果,下次zookeeper重启前不会再重新生成whiteListedCommands
return whiteListedCommands.contains(command);
}
isEnabled方法中使用了whiteListedCommands
变量,该变量保存了允许使用的四字命令。但是4lw.commands.whitelist
配置是什么时候被写入系统变量的?
org/apache/zookeeper/server/quorum/QuorumPeerConfig.java
文件的parseProperties
方法:
public void parseProperties(Properties zkProp)
throws IOException, ConfigException {
// ...
if (...) {
...
}
...
else {
// QuorumPeerConfig一部分属性名称被设置为成员变量。除这些外其他的属性会被作为系统变量设置,加上zookeeper.前缀。当然4lw.commands.whitelist也被设置为了系统变量
System.setProperty("zookeeper." + key, value);
}
// ...
}
解决方法
有两个方案可用:
- 修改zookeeper 3.4.6的源码,加入类似的功能。需要测试是否影响原有功能。
- 升级zookeeper 为3.5.5稳定版。但需要测试和其他组件的兼容性。
网友评论