From 7d6c9cd680d9d417780a49b5f939ecf877cf669b Mon Sep 17 00:00:00 2001 From: Mohammad Arshad Date: Thu, 13 Sep 2018 01:20:27 +0530 Subject: [PATCH 1/4] ZOOKEEPER-1260:Audit logging in ZooKeeper Servers. --- conf/log4j.properties | 17 + .../zookeeper/audit/AuditConstants.java | 37 ++ .../zookeeper/audit/ZKAuditLogFormatter.java | 56 +++ .../apache/zookeeper/audit/ZKAuditLogger.java | 133 ++++++ .../zookeeper/server/util/AuthUtil.java | 42 ++ .../zookeeper/audit/AuditLogPerfReading.java | 74 +++ .../audit/ZKAuditLogFormatterTest.java | 45 ++ .../audit/ZKAuditLoggerPerformance.java | 151 +++++++ .../zookeeper/audit/ZKAuditLoggerTest.java | 423 ++++++++++++++++++ .../zookeeper/server/util/AuthUtilTest.java | 57 +++ .../content/xdocs/zookeeperAuditLogs.xml | 205 +++++++++ .../resources/images/zkAuditLogs.jpg | Bin 0 -> 28450 bytes .../main/java/org/apache/zookeeper/Login.java | 7 + .../java/org/apache/zookeeper/ZKUtil.java | 38 ++ .../apache/zookeeper/cli/GetAclCommand.java | 24 +- .../org/apache/zookeeper/server/DataTree.java | 18 + .../server/FinalRequestProcessor.java | 159 ++++++- .../org/apache/zookeeper/server/Request.java | 28 ++ .../apache/zookeeper/server/ServerCnxn.java | 23 + .../zookeeper/server/ServerCnxnFactory.java | 10 + .../zookeeper/server/ZooKeeperServerMain.java | 12 + .../server/quorum/QuorumPeerMain.java | 14 + 22 files changed, 1549 insertions(+), 24 deletions(-) create mode 100644 src/java/main/org/apache/zookeeper/audit/AuditConstants.java create mode 100644 src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java create mode 100644 src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java create mode 100644 src/java/main/org/apache/zookeeper/server/util/AuthUtil.java create mode 100644 src/java/test/org/apache/zookeeper/audit/AuditLogPerfReading.java create mode 100644 src/java/test/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java create mode 100644 src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java create mode 100644 src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java create mode 100644 src/java/test/org/apache/zookeeper/server/util/AuthUtilTest.java create mode 100644 zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml create mode 100644 zookeeper-docs/src/documentation/resources/images/zkAuditLogs.jpg diff --git a/conf/log4j.properties b/conf/log4j.properties index 4a2ede95503..c938a2b7e28 100644 --- a/conf/log4j.properties +++ b/conf/log4j.properties @@ -63,3 +63,20 @@ log4j.appender.TRACEFILE.File=${zookeeper.tracelog.dir}/${zookeeper.tracelog.fil log4j.appender.TRACEFILE.layout=org.apache.log4j.PatternLayout ### Notice we are including log4j's NDC here (%x) log4j.appender.TRACEFILE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L][%x] - %m%n +# +# zk audit logging +# +zookeeper.auditlog.file=zookeeper_audit.log +zookeeper.auditlog.threshold=INFO +audit.logger=INFO, RFAAUDIT +log4j.logger.org.apache.zookeeper.audit.ZKAuditLogger=${audit.logger} +log4j.additivity.org.apache.zookeeper.audit.ZKAuditLogger=false +log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender +log4j.appender.RFAAUDIT.File=${zookeeper.log.dir}/${zookeeper.auditlog.file} +log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout +log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n +log4j.appender.RFAAUDIT.Threshold=${zookeeper.auditlog.threshold} + +# Max log file size of 10MB +log4j.appender.RFAAUDIT.MaxFileSize=10MB +log4j.appender.RFAAUDIT.MaxBackupIndex=10 diff --git a/src/java/main/org/apache/zookeeper/audit/AuditConstants.java b/src/java/main/org/apache/zookeeper/audit/AuditConstants.java new file mode 100644 index 00000000000..63c15d511cd --- /dev/null +++ b/src/java/main/org/apache/zookeeper/audit/AuditConstants.java @@ -0,0 +1,37 @@ +/** + * 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.audit; + +public class AuditConstants { + public static final String SUCCESS = "success"; + public static final String FAILURE = "failure"; + // operation is performed, result is not known yet + public static final String INVOKED = "invoked"; + public static final String KEY_VAL_SEPARATOR = "="; + public static final char PAIR_SEPARATOR = '\t'; + + public static final String OP_START = "serverStart"; + public static final String OP_STOP = "serverStop"; + public static final String OP_CREATE = "create"; + public static final String OP_DELETE = "delete"; + public static final String OP_SETDATA = "setData"; + public static final String OP_SETACL = "setAcl"; + public static final String OP_MULTI_OP = "multiOperation"; + public static final String OP_RECONFIG = "reconfig"; + public static final String OP_DEL_EZNODE_EXP = "ephemeralZNodeDeletionOnSessionCloseOrExpire"; +} diff --git a/src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java b/src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java new file mode 100644 index 00000000000..c81cb4be5b2 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java @@ -0,0 +1,56 @@ +/** + * 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.audit; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class ZKAuditLogFormatter { + private Map fieldVsValue = new LinkedHashMap(); + + /** + * Add fields to be logged + */ + public void addField(String key, String value) { + fieldVsValue.put(key, value); + } + + /** + * Gives the string to be logged, ignores fields with null values + */ + public String format() { + StringBuilder buffer = new StringBuilder(); + boolean first = true; + for (Entry entry : fieldVsValue.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + if (null != value) { + // if first field then no need to add the tabs + if (first) { + first = false; + } else { + buffer.append(AuditConstants.PAIR_SEPARATOR); + } + buffer.append(key.toLowerCase()).append(AuditConstants.KEY_VAL_SEPARATOR) + .append(value); + } + } + return buffer.toString(); + } +} diff --git a/src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java b/src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java new file mode 100644 index 00000000000..959015e2a2a --- /dev/null +++ b/src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java @@ -0,0 +1,133 @@ +/** + * 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.audit; + +import org.apache.zookeeper.server.ServerCnxnFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZKAuditLogger { + public static final String SYSPROP_AUDIT_ENABLE = "zookeeper.audit.enable"; + private static final Logger LOG = LoggerFactory.getLogger(ZKAuditLogger.class); + // By default audit logging is disabled + private static boolean auditEnabled = Boolean.getBoolean(SYSPROP_AUDIT_ENABLE); + + /** + * @return true if audit log is enabled + */ + public static boolean isAuditEnabled() { + return auditEnabled; + } + + // @VisisbleForTesting + public static void setAuditEnabled(boolean auditEnabled) { + ZKAuditLogger.auditEnabled = auditEnabled; + } + + /** + * + * Prints audit log based on log level specified + * + */ + public static enum LogLevel { + ERROR { + @Override + public void printLog(String logMsg) { + LOG.error(logMsg); + } + }, + INFO { + @Override + public void printLog(String logMsg) { + LOG.info(logMsg); + } + }; + public abstract void printLog(String logMsg); + } + + public static enum Keys { + USER, OPERATION, RESULT, IP, ACL, ZNODE, SESSION; + } + + public static void logInvoked(String user, String operation) { + log(LogLevel.INFO, user, operation, AuditConstants.INVOKED); + } + + public static void logSuccess(String user, String operation) { + log(LogLevel.INFO, user, operation, AuditConstants.SUCCESS); + } + + public static void logFailure(String user, String operation) { + log(LogLevel.ERROR, user, operation, AuditConstants.FAILURE); + } + + private static void log(LogLevel level, String user, String operation, String logType) { + level.printLog(createLog(user, operation, null, null, null, null, logType)); + } + + public static void logSuccess(String user, String operation, String znode, String acl, + String session, String ip) { + LogLevel.INFO.printLog( + createLog(user, operation, znode, acl, session, ip, AuditConstants.SUCCESS)); + } + + public static void logFailure(String user, String operation, String znode, String acl, + String session, String ip) { + LogLevel.ERROR.printLog( + createLog(user, operation, znode, acl, session, ip, AuditConstants.FAILURE)); + } + + /** + * A helper api for creating an audit log string. + */ + public static String createLog(String user, String operation, String znode, String acl, + String session, String ip, String status) { + ZKAuditLogFormatter fmt = new ZKAuditLogFormatter(); + fmt.addField(Keys.SESSION.name(), session); + fmt.addField(Keys.USER.name(), user); + fmt.addField(Keys.IP.name(), ip); + fmt.addField(Keys.OPERATION.name(), operation); + fmt.addField(Keys.ZNODE.name(), znode); + fmt.addField(Keys.ACL.name(), acl); + fmt.addField(Keys.RESULT.name(), status); + return fmt.format(); + } + + /** + * add audit log for server start and register server stop log + */ + public static void addZKStartStopAuditLog() { + if (isAuditEnabled()) { + ZKAuditLogger.logSuccess(getZKUser(), AuditConstants.OP_START); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + ZKAuditLogger.logInvoked(getZKUser(), AuditConstants.OP_STOP); + } + }); + } + } + + /** + * User who has started the ZooKeeper server user, it will be the logged-in + * user. If no user logged-in then system user + */ + public static String getZKUser() { + return ServerCnxnFactory.getUserName(); + } +} diff --git a/src/java/main/org/apache/zookeeper/server/util/AuthUtil.java b/src/java/main/org/apache/zookeeper/server/util/AuthUtil.java new file mode 100644 index 00000000000..abde0361c45 --- /dev/null +++ b/src/java/main/org/apache/zookeeper/server/util/AuthUtil.java @@ -0,0 +1,42 @@ +/** + * 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.util; + +import org.apache.zookeeper.data.Id; + +public class AuthUtil { + /** + * Gives user name + * + * @param id + * contains scheme and authentication info + * @return returns null if authentication scheme does not exist or + * authentication provider returns null as user + */ + public static String getUser(Id id) { + if ("digest".equals(id.getScheme())) { + /** + * format is already enforced in server code. so no need to check it + * again, just assume it is in correct format + */ + return id.getId().split(":")[0]; + } else { + return id.getId(); + } + } +} diff --git a/src/java/test/org/apache/zookeeper/audit/AuditLogPerfReading.java b/src/java/test/org/apache/zookeeper/audit/AuditLogPerfReading.java new file mode 100644 index 00000000000..46d4e6534eb --- /dev/null +++ b/src/java/test/org/apache/zookeeper/audit/AuditLogPerfReading.java @@ -0,0 +1,74 @@ +/** + * 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.audit; + +/** + * Audit log performance reading + */ +public class AuditLogPerfReading { + // time taken by create operations + private long create; + // time taken by setData operations + private long setData; + // time taken by delete operations + private long delete; + + public long getCreate() { + return create; + } + + public void setCreate(long create) { + this.create = create; + } + + public long getSetData() { + return setData; + } + + public void setSetData(long setData) { + this.setData = setData; + } + + public long getDelete() { + return delete; + } + + public void setDelete(long delete) { + this.delete = delete; + } + + public String report() { + StringBuilder builder = new StringBuilder(); + builder.append("create="); + builder.append(create); + builder.append(" ms\n"); + builder.append("setData="); + builder.append(setData); + builder.append(" ms\n"); + builder.append("delete="); + builder.append(delete); + builder.append(" ms\n"); + return builder.toString(); + } + + @Override + public String toString() { + return "create=" + create + ", setData=" + setData + ", delete=" + + delete; + } +} \ No newline at end of file diff --git a/src/java/test/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java b/src/java/test/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java new file mode 100644 index 00000000000..4d30b1e19e1 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java @@ -0,0 +1,45 @@ +/** + * 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.audit; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class ZKAuditLogFormatterTest { + + @Test + public void testFormat() { + ZKAuditLogFormatter formatter = new ZKAuditLogFormatter(); + formatter.addField("k1", "Value1"); + formatter.addField("k2", "Value2"); + String actual = formatter.format(); + String expected = "k1=Value1\tk2=Value2"; + assertEquals(expected, actual); + } + + @Test + public void testFormatShouldIgnoreKeyIfValueIsNull() { + ZKAuditLogFormatter formatter = new ZKAuditLogFormatter(); + formatter.addField("k1", null); + formatter.addField("k2", "Value2"); + String actual = formatter.format(); + String expected = "k2=Value2"; + assertEquals(expected, actual); + } +} diff --git a/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java b/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java new file mode 100644 index 00000000000..20ebf81e4b7 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java @@ -0,0 +1,151 @@ +/** + * 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.audit; + +import java.io.IOException; +import java.util.concurrent.TimeoutException; + +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.ZooDefs.Ids; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.common.Time; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ZKAuditLoggerPerformance { + private static final Logger LOG = LoggerFactory + .getLogger(ZKAuditLoggerPerformance.class); + private ZooKeeper zkClient; + private String parentPath; + private int numberOfRecords; + + public ZKAuditLoggerPerformance(ZooKeeper zkClient, String parentPath, + int numberOfRecords) { + this.zkClient = zkClient; + this.parentPath = parentPath; + this.numberOfRecords = numberOfRecords; + } + + public void create() throws Exception { + for (int i = 0; i < numberOfRecords; i++) { + zkClient.create(getPath(i), "0123456789".getBytes(), + Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + + } + } + + public void setData() throws Exception { + for (int i = 0; i < numberOfRecords; i++) { + zkClient.setData(getPath(i), "9876543210".getBytes(), -1); + } + } + + public void delete() throws Exception { + for (int i = 0; i < numberOfRecords; i++) { + zkClient.delete(getPath(i), -1); + } + } + + public AuditLogPerfReading doOperations() throws Exception { + AuditLogPerfReading perfReading = new AuditLogPerfReading(); + // create + long startTime = Time.currentElapsedTime(); + create(); + perfReading.setCreate(Time.currentElapsedTime() - startTime); + + // setData + startTime = Time.currentElapsedTime(); + setData(); + perfReading.setSetData(Time.currentElapsedTime() - startTime); + + // delete + startTime = Time.currentElapsedTime(); + delete(); + perfReading.setDelete(Time.currentElapsedTime() - startTime); + return perfReading; + } + + private String getPath(int i) { + return parentPath + "zNode" + i; + } + + public static void main(String[] args) { + if (args.length != 3) { + System.err.println( + "USAGE: ZKAuditLoggerPerformance connectionString parentPath numberOfRecords"); + System.exit(1); + } + String cxnString = args[0]; + CountdownWatcher watcher = new CountdownWatcher(); + ZooKeeper zkClient = null; + try { + zkClient = new ZooKeeper(cxnString, 60000, watcher); + watcher.waitForConnected(30000); + } catch (InterruptedException | TimeoutException | IOException e) { + String msg = "ZooKeeper client can not connect to " + cxnString; + logErrorAndExit(e, msg); + } + String parentPath = args[1]; + try { + Stat exists = zkClient.exists(parentPath, false); + if (exists == null) { + System.err.println( + "Parent path '" + parentPath + "' must exist."); + System.exit(1); + } + } catch (KeeperException | InterruptedException e1) { + String msg = "Error while checking the existence of parent path"; + logErrorAndExit(e1, msg); + } + int recordCount = 0; + try { + recordCount = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + String msg = "Failed to parse '" + args[2] + "' to integer"; + LOG.error(msg, e); + System.err.println(msg); + System.exit(1); + } + ZKAuditLoggerPerformance auditLoggingPerf = new ZKAuditLoggerPerformance( + zkClient, + parentPath, recordCount); + AuditLogPerfReading doOperations = null; + try { + doOperations = auditLoggingPerf.doOperations(); + } catch (Exception e) { + String msg = "Error while doing operations."; + LOG.error(msg, e); + System.err.println(msg); + System.exit(1); + } + System.out + .println("Time taken for " + recordCount + " operations are:"); + System.out.println(doOperations.report()); + System.exit(0); + } + + private static void logErrorAndExit(Exception e, String msg) { + LOG.error(msg, e); + System.err.println(msg + ", error=" + e.getMessage()); + System.exit(1); + } +} diff --git a/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java b/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java new file mode 100644 index 00000000000..4210240e818 --- /dev/null +++ b/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java @@ -0,0 +1,423 @@ +/** + * 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.audit; + +import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.LineNumberReader; +import java.io.StringReader; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Layout; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.SimpleLayout; +import org.apache.log4j.WriterAppender; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.KeeperException.Code; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.PortAssignment; +import org.apache.zookeeper.ZKUtil; +import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.data.ACL; +import org.apache.zookeeper.data.Stat; +import org.apache.zookeeper.server.Request; +import org.apache.zookeeper.server.ServerCnxn; +import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; +import org.apache.zookeeper.test.ClientBase; +import org.apache.zookeeper.test.ClientBase.CountdownWatcher; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class ZKAuditLoggerTest extends QuorumPeerTestBase { + private static final Logger LOG = Logger.getLogger(ZKAuditLoggerTest.class); + private static int SERVER_COUNT = 3; + private MainThread[] mt; + private ZooKeeper zk; + + @Test + public void testAuditLogs() throws Exception { + System.setProperty(ZKAuditLogger.SYSPROP_AUDIT_ENABLE, "true"); + // setup the logger to capture all logs + Layout layout = new SimpleLayout(); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + WriterAppender appender = new WriterAppender(layout, os); + appender.setImmediateFlush(true); + appender.setThreshold(Level.INFO); + Logger zlogger = Logger.getLogger(ZKAuditLogger.class); + zlogger.addAppender(appender); + try { + mt = startQuorum(); + CountdownWatcher watcher = new CountdownWatcher(); + zk = new ZooKeeper( + "127.0.0.1:" + mt[0].getQuorumPeer().getClientPort(), + ClientBase.CONNECTION_TIMEOUT, watcher); + watcher.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + String expectedAuditLog = getStartLog(); + List logs = readAuditLog(os, SERVER_COUNT); + verifyLogs(expectedAuditLog, logs); + + String path = "/a"; + verifyCreateAuditLogs(os, path); + verifySetDataAuditLogs(os, path); + verifySetACLAuditLogs(os, path); + verifyMultiOperationAuditLogs(os); + verifyDeleteAuditLogs(os, path); + verifyEphemralZNodeAuditLogs(os); + + } finally { + zlogger.removeAppender(appender); + os.close(); + } + } + + private void verifyEphemralZNodeAuditLogs(ByteArrayOutputStream os) + throws Exception { + String ephemralPath = "/ephemral"; + CountdownWatcher watcher2 = new CountdownWatcher(); + ZooKeeper zk2 = new ZooKeeper( + "127.0.0.1:" + mt[0].getQuorumPeer().getClientPort(), + ClientBase.CONNECTION_TIMEOUT, watcher2); + watcher2.waitForConnected(ClientBase.CONNECTION_TIMEOUT); + zk2.create(ephemralPath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.EPHEMERAL); + String session2 = "0x" + Long.toHexString(zk2.getSessionId()); + verifyLog(getAuditLog(AuditConstants.OP_CREATE, ephemralPath, + AuditConstants.SUCCESS, null, + session2), readAuditLog(os)); + zk2.close(); + waitForDeletion(zk, ephemralPath, 100); + // verify that ephemeral node deletion on session close are captured + // in audit log + // Because these operations are done by ZooKeeper server itself, + // there are no IP user is zkServer user, not any client user + verifyLogs( + getAuditLog(AuditConstants.OP_DEL_EZNODE_EXP, ephemralPath, + AuditConstants.SUCCESS, + null, session2, ZKAuditLogger.getZKUser(), null), + readAuditLog(os, SERVER_COUNT)); + } + + private void verifyMultiOperationAuditLogs(ByteArrayOutputStream os) + throws InterruptedException, KeeperException, IOException { + List ops = new ArrayList(); + + String multiop = "/b"; + Op create = Op.create(multiop, "".getBytes(), + ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + Op setData = Op.setData(multiop, "newData".getBytes(), -1); + // check does nothing so it is audit logged + Op check = Op.check(multiop, -1); + Op delete = Op.delete(multiop, -1); + + ops.add(create); + ops.add(setData); + ops.add(check); + ops.add(delete); + + zk.multi(ops); + List multiOpLogs = readAuditLog(os, 3); + // verify that each multi operation success is logged + verifyLog(getAuditLog(AuditConstants.OP_CREATE, multiop), + multiOpLogs.get(0)); + verifyLog(getAuditLog(AuditConstants.OP_SETDATA, multiop), + multiOpLogs.get(1)); + verifyLog(getAuditLog(AuditConstants.OP_DELETE, multiop), + multiOpLogs.get(2)); + + ops = new ArrayList(); + ops.add(create); + ops.add(create); + try { + zk.multi(ops); + } catch (KeeperException exception) { + Code code = exception.code(); + assertEquals(Code.NODEEXISTS, code); + } + + // Verify that multi operation failure is logged, and there is no path + // mentioned in the audit log + verifyLog(getAuditLog(AuditConstants.OP_MULTI_OP, null, + AuditConstants.FAILURE), + readAuditLog(os)); + } + + private void verifyDeleteAuditLogs(ByteArrayOutputStream os, String path) + throws InterruptedException, IOException, KeeperException { + try { + zk.delete(path, -100); + } catch (KeeperException exception) { + Code code = exception.code(); + assertEquals(Code.BADVERSION, code); + } + verifyLog(getAuditLog(AuditConstants.OP_DELETE, path, + AuditConstants.FAILURE), + readAuditLog(os)); + zk.delete(path, -1); + verifyLog(getAuditLog(AuditConstants.OP_DELETE, path), + readAuditLog(os)); + } + + private void verifySetACLAuditLogs(ByteArrayOutputStream os, String path) + throws InterruptedException, IOException, KeeperException { + ArrayList openAclUnsafe = ZooDefs.Ids.OPEN_ACL_UNSAFE; + try { + zk.setACL(path, openAclUnsafe, -100); + } catch (KeeperException exception) { + Code code = exception.code(); + assertEquals(Code.BADVERSION, code); + } + verifyLog(getAuditLog(AuditConstants.OP_SETACL, path, + AuditConstants.FAILURE, + ZKUtil.aclToString(openAclUnsafe)), readAuditLog(os)); + zk.setACL(path, openAclUnsafe, -1); + verifyLog(getAuditLog(AuditConstants.OP_SETACL, path, + AuditConstants.SUCCESS, + ZKUtil.aclToString(openAclUnsafe)), readAuditLog(os)); + } + + private void verifySetDataAuditLogs(ByteArrayOutputStream os, String path) + throws InterruptedException, IOException, KeeperException { + try { + zk.setData(path, "newData".getBytes(), -100); + } catch (KeeperException exception) { + Code code = exception.code(); + assertEquals(Code.BADVERSION, code); + } + verifyLog(getAuditLog(AuditConstants.OP_SETDATA, path, + AuditConstants.FAILURE), + readAuditLog(os)); + zk.setData(path, "newdata".getBytes(), -1); + verifyLog(getAuditLog(AuditConstants.OP_SETDATA, path), + readAuditLog(os)); + } + + private void verifyCreateAuditLogs(ByteArrayOutputStream os, String path) + throws KeeperException, InterruptedException, IOException { + zk.create(path, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + // success log + verifyLog(getAuditLog(AuditConstants.OP_CREATE, path), + readAuditLog(os)); + try { + zk.create(path, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT); + } catch (KeeperException exception) { + Code code = exception.code(); + assertEquals(Code.NODEEXISTS, code); + } + // Verify create operation log + verifyLog(getAuditLog(AuditConstants.OP_CREATE, path, + AuditConstants.FAILURE), + readAuditLog(os)); + } + + private String getStartLog() { + /** + *
+         * user=userName operation=ZooKeeperServer start  result=success
+         * 
+ */ + return ZKAuditLogger.createLog(ZKAuditLogger.getZKUser(), + AuditConstants.OP_START, null, + null, null, null, AuditConstants.SUCCESS); + } + + private String getAuditLog(String operation, String znode) { + return getAuditLog(operation, znode, AuditConstants.SUCCESS); + } + + private String getAuditLog(String operation, String znode, String status) { + return getAuditLog(operation, znode, status, null); + } + + private String getAuditLog(String operation, String znode, String status, + String acl) { + String session = getSession(); + return getAuditLog(operation, znode, status, acl, session); + } + + private String getAuditLog(String operation, String znode, String status, + String acl, + String session) { + String user = getUser(); + String ip = getIp(); + return getAuditLog(operation, znode, status, acl, session, user, ip); + } + + private String getAuditLog(String operation, String znode, String status, + String acl, + String session, String user, String ip) { + + String auditLog = ZKAuditLogger.createLog(user, operation, znode, acl, + session, ip, status); + LOG.info("expected audit log for operation '" + operation + "' is '" + + auditLog + "'"); + return auditLog; + } + + private String getSession() { + return "0x" + Long.toHexString(zk.getSessionId()); + } + + private String getUser() { + ServerCnxn next = getServerCnxn(); + Request request = new Request(next, -1, -1, -1, null, + next.getAuthInfo()); + return request.getUsers(); + } + + private String getIp() { + ServerCnxn next = getServerCnxn(); + InetSocketAddress remoteSocketAddress = next.getRemoteSocketAddress(); + InetAddress address = remoteSocketAddress.getAddress(); + return address.getHostAddress(); + } + + private ServerCnxn getServerCnxn() { + Iterable connections = mt[0].getQuorumPeer() + .getActiveServer() + .getServerCnxnFactory().getConnections(); + ServerCnxn next = connections.iterator().next(); + return next; + } + + private void verifyLog(String expectedLog, String log) { + String searchString = " - "; + int logStartIndex = log.indexOf(searchString); + String auditLog = log.substring(logStartIndex + searchString.length()); + assertEquals(expectedLog, auditLog); + + } + + private void verifyLogs(String expectedLog, List logs) { + for (String log : logs) { + verifyLog(expectedLog, log); + } + } + + private String readAuditLog(ByteArrayOutputStream os) throws IOException { + return readAuditLog(os, 1).get(0); + } + + private List readAuditLog(ByteArrayOutputStream os, + int numberOfLogEntry) + throws IOException { + return readAuditLog(os, numberOfLogEntry, false); + } + + private List readAuditLog(ByteArrayOutputStream os, + int numberOfLogEntry, + boolean skipEphemralDeletion) throws IOException { + List logs = new ArrayList(); + LineNumberReader r = new LineNumberReader( + new StringReader(os.toString())); + String line = ""; + while ((line = r.readLine()) != null) { + if (skipEphemralDeletion + && line.contains(AuditConstants.OP_DEL_EZNODE_EXP)) { + continue; + } + logs.add(line); + } + os.reset(); + assertEquals( + "Expected number of log entries are not generated. Logs are " + + logs, + numberOfLogEntry, logs.size()); + return logs; + + } + + private MainThread[] startQuorum() throws IOException { + final int clientPorts[] = new int[SERVER_COUNT]; + StringBuilder sb = new StringBuilder(); + String server; + + for (int i = 0; i < SERVER_COUNT; i++) { + clientPorts[i] = PortAssignment.unique(); + server = "server." + i + "=127.0.0.1:" + PortAssignment.unique() + + ":" + + PortAssignment.unique() + ":participant;127.0.0.1:" + + clientPorts[i]; + sb.append(server + "\n"); + + } + String currentQuorumCfgSection = sb.toString(); + MainThread mt[] = new MainThread[SERVER_COUNT]; + + // start all the servers + for (int i = 0; i < SERVER_COUNT; i++) { + mt[i] = new MainThread(i, clientPorts[i], currentQuorumCfgSection, + false); + mt[i].start(); + } + + // ensure all servers started + for (int i = 0; i < SERVER_COUNT; i++) { + Assert.assertTrue("waiting for server " + i + " being up", + ClientBase.waitForServerUp("127.0.0.1:" + clientPorts[i], + CONNECTION_TIMEOUT)); + } + return mt; + } + + private void waitForDeletion(ZooKeeper zooKeeper, String path, long timeout) + throws Exception { + long elapsedTime = 0; + long waitInterval = 10; + Stat exists = zooKeeper.exists(path, false); + while (exists != null && elapsedTime < timeout) { + try { + Thread.sleep(waitInterval); + } catch (InterruptedException e) { + Assert.fail("CurrentEpoch update failed"); + } + elapsedTime = elapsedTime + waitInterval; + exists = zooKeeper.exists(path, false); + } + Assert.assertNull("Node " + path + " not deleted in " + timeout + " ms", + exists); + } + + @After + public void tearDown() { + System.clearProperty(ZKAuditLogger.SYSPROP_AUDIT_ENABLE); + System.clearProperty("zookeeper.admin.enableServer"); + for (int i = 0; i < SERVER_COUNT; i++) { + try { + if (mt[i] != null) { + mt[i].shutdown(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/java/test/org/apache/zookeeper/server/util/AuthUtilTest.java b/src/java/test/org/apache/zookeeper/server/util/AuthUtilTest.java new file mode 100644 index 00000000000..5688923bc7f --- /dev/null +++ b/src/java/test/org/apache/zookeeper/server/util/AuthUtilTest.java @@ -0,0 +1,57 @@ +/** + * 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.util; + +import static org.junit.Assert.*; + +import org.apache.zookeeper.data.Id; +import org.junit.Test; + +public class AuthUtilTest { + + @Test + public void testGetUserFromAllAuthenticationScheme() { + System.setProperty("zookeeper.authProvider.sasl", + "org.apache.zookeeper.server.auth.SASLAuthenticationProvider"); + System.setProperty("zookeeper.authProvider.x509", + "org.apache.zookeeper.server.auth.X509AuthenticationProvider"); + + String user = "zkUser"; + Id id = new Id("digest", user + ":password"); + String result = AuthUtil.getUser(id); + assertEquals(user, result); + + String principal = "zkCli/hadoop.hadoop.com"; + id = new Id("sasl", principal); + assertEquals(principal, AuthUtil.getUser(id)); + + String ip = "192.168.1.2"; + id = new Id("ip", ip); + assertEquals(ip, AuthUtil.getUser(id)); + + String certificate = "CN=host-192.168.1.2,OU=OrganizationUnit,O=Organization,L=Location,ST=State,C=IN"; + id = new Id("x509", certificate); + assertEquals(certificate, AuthUtil.getUser(id)); + } + + public void testGetUserShouldReturnNullIfAuthenticationNotConfigured() { + Id id = new Id("invalid Authentication Scheme", "user"); + String result = AuthUtil.getUser(id); + assertNull(result); + } +} diff --git a/zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml b/zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml new file mode 100644 index 00000000000..4771b63533b --- /dev/null +++ b/zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml @@ -0,0 +1,205 @@ + + + +
+ ZooKeeper Audit Logging + + + Licensed 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. + + + + This document contains information about Audit Logs in ZooKeeper. + + +
+ ZooKeeper Audit Logs + Apache ZooKeeper supports audit logs form version 3.5.5. By default audit logs are disabled. To enable audit + logs configure audit.enable=true in conf/zoo.cfg. Audit logs are not logged on all the ZooKeeper servers, but logged + only on the servers where client is connected as depicted in below figure. + + + + + + The audit log captures detailed information for the operations that are selected to be audited. The audit + information is written as a set of key=value pairs for the following keys + + Audit Log Content + + + + Key + Value + + + + + session + client session id + + + user + + comma separated list of users who are associate with a client session. To know who is taken as user in audit logs + refer section + + + + + ip + client IP address + + + operation + any one of the selected operations for audit. Possible values are + (serverStart| serverStop| create| delete| setData| setAcl| multiOperation| reconfig| ephemeralZNodeDeleteOnSessionClose) + + + + znode + path of the znode + + + acl + String representation of znode ACL like cdrwa(create, delete,read, write, admin). This is logged + only for setAcl operation + + + result + result of the operation. Possible values are (success|failure|invoked). Result "invoked" is used + for serverStop operation because stop is logged before ensuring that server actually stopped. + + + + +
+ Below are sample audit logs for all operations, where client is connected from 192.168.1.2, client principal is + zkcli@HADOOP.COM, server principal is zookeeper/192.168.1.3@HADOOP.COM + + user=zookeeper/192.168.1.3 operation=serverStart result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/a result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/a result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/a result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/a result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setAcl znode=/a acl=world:anyone:cdrwa result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setAcl znode=/a acl=world:anyone:cdrwa result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/b result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/b result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/b result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=multiOperation result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/a result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/a result=success + session=0x19344730001 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/ephemral result=success + session=0x19344730001 user=zookeeper/192.168.1.3 operation=ephemeralZNodeDeletionOnSessionCloseOrExpire znode=/ephemral result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=reconfig znode=/zookeeper/config result=success + user=zookeeper/192.168.1.3 operation=serverStop result=invoked + +
+
+ ZooKeeper Audit Log Configuration + By default audit logs are disabled. To enable audit logs configure audit.enable=true in conf/zoo.cfg. Audit + logging is done using log4j. Following is the default log4j configuration for audit logs in conf/log4j.properties + + + # + # zk audit logging + # + zookeeper.auditlog.file=zookeeper_audit.log + zookeeper.auditlog.threshold=INFO + audit.logger=INFO, RFAAUDIT + log4j.logger.org.apache.zookeeper.audit.ZKAuditLogger=${audit.logger} + log4j.additivity.org.apache.zookeeper.audit.ZKAuditLogger=false + log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender + log4j.appender.RFAAUDIT.File=${zookeeper.log.dir}/${zookeeper.auditlog.file} + log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout + log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n + log4j.appender.RFAAUDIT.Threshold=${zookeeper.auditlog.threshold} + + # Max log file size of 10MB + log4j.appender.RFAAUDIT.MaxFileSize=10MB + log4j.appender.RFAAUDIT.MaxBackupIndex=10 + + Change above configuration to customize the auditlog file, number of backups, max file size etc. +
+
+ Who is taken as user in audit logs? + By default there are only four authentication provider + + + IPAuthenticationProvider + + + SASLAuthenticationProvider + + + X509AuthenticationProvider + + + DigestAuthenticationProvider + + + User is decided based on the configured authentication provider. + + + When IPAuthenticationProvider is configured then authenticated IP is taken as user + + + When SASLAuthenticationProvider is configured then client principal is taken as user + + + + When X509AuthenticationProvider is configured then client certificate is taken as user + + + + When DigestAuthenticationProvider is configured then authenticated user is user + + + + For custom authentication provider whatever is stored in org.apache.zookeeper.data.Id.id is taken as user. + Generally only user name is stored in this field but it is up to the custom authentication provider what they store + in it. For audit logging value of org.apache.zookeeper.data.Id.id would be taken as user. + + In ZooKeeper Server not all the operations are done by clients but some operations are done by the server + itself. For example when client closes the session, ephemeral znodes are delete by the Server. These deletion are + not done by clients directly but it is done the server itself these are called system operations. For these system + operations the user associated with the ZooKeeper server are taken as user while audit logging these operations. For + example if in ZooKeeper server principal is zookeeper/hadoop.hadoop.com@HADOOP.COM then this becomes the system user + and all the system operations will be logged with this user name. + + user=zookeeper/hadoop.hadoop.com@HADOOP.COM operation=serverStart result=success + If there is no user associate with ZooKeeper server then the user who started the ZooKeeper server is taken as + the user. For example if server started by root then root is taken as the system user + + user=root operation=serverStart result=success + Single client can attach multiple authentication schemes to a session, in this case all authenticated schemes + will taken taken as user and will be presented as comma separated list. For example if a client is authenticate with + principal zkcli@HADOOP.COM and ip 127.0.0.1 then create znode audit log will be as: + session=0x10c0bcb0000 user=zkcli@HADOOP.COM,127.0.0.1 ip=127.0.0.1 operation=create znode=/a result=success +
+
diff --git a/zookeeper-docs/src/documentation/resources/images/zkAuditLogs.jpg b/zookeeper-docs/src/documentation/resources/images/zkAuditLogs.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fd6c376561017daf00514803e6dfb82fdf315553 GIT binary patch literal 28450 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 r!1_v#Gx8jN*"); + } + return principal; + } + public String getLoginContextName() { return loginContextName; } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java index a6abf2f42e1..73fdf18780a 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java @@ -27,6 +27,7 @@ import org.apache.zookeeper.AsyncCallback.VoidCallback; import org.apache.zookeeper.KeeperException.Code; import org.apache.zookeeper.common.PathUtils; +import org.apache.zookeeper.data.ACL; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -168,4 +169,41 @@ private static void visitSubTreeDFSHelper(ZooKeeper zk, final String path, return; // ignore } } + + /** + * @param perms + * ACL permissions + * @return string representation of permissions + */ + public static String getPermString(int perms) { + StringBuilder p = new StringBuilder(); + if ((perms & ZooDefs.Perms.CREATE) != 0) { + p.append('c'); + } + if ((perms & ZooDefs.Perms.DELETE) != 0) { + p.append('d'); + } + if ((perms & ZooDefs.Perms.READ) != 0) { + p.append('r'); + } + if ((perms & ZooDefs.Perms.WRITE) != 0) { + p.append('w'); + } + if ((perms & ZooDefs.Perms.ADMIN) != 0) { + p.append('a'); + } + return p.toString(); + } + + public static String aclToString(List acls) { + StringBuilder sb = new StringBuilder(); + for (ACL acl : acls) { + sb.append(acl.getId().getScheme()); + sb.append(":"); + sb.append(acl.getId().getId()); + sb.append(":"); + sb.append(getPermString(acl.getPerms())); + } + return sb.toString(); + } } \ No newline at end of file diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetAclCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetAclCommand.java index 54306cd0c6a..1485b238e63 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetAclCommand.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetAclCommand.java @@ -23,7 +23,7 @@ import org.apache.commons.cli.Parser; import org.apache.commons.cli.PosixParser; import org.apache.zookeeper.KeeperException; -import org.apache.zookeeper.ZooDefs; +import org.apache.zookeeper.ZKUtil; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; @@ -75,7 +75,7 @@ public boolean exec() throws CliException { for (ACL a : acl) { out.println(a.getId() + ": " - + getPermString(a.getPerms())); + + ZKUtil.getPermString(a.getPerms())); } if (cl.hasOption("s")) { @@ -83,24 +83,4 @@ public boolean exec() throws CliException { } return false; } - - private static String getPermString(int perms) { - StringBuilder p = new StringBuilder(); - if ((perms & ZooDefs.Perms.CREATE) != 0) { - p.append('c'); - } - if ((perms & ZooDefs.Perms.DELETE) != 0) { - p.append('d'); - } - if ((perms & ZooDefs.Perms.READ) != 0) { - p.append('r'); - } - if ((perms & ZooDefs.Perms.WRITE) != 0) { - p.append('w'); - } - if ((perms & ZooDefs.Perms.ADMIN) != 0) { - p.append('a'); - } - return p.toString(); - } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java index 2e24d639bce..4d1ed22d603 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java @@ -35,6 +35,8 @@ import org.apache.zookeeper.Watcher.WatcherType; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.audit.AuditConstants; +import org.apache.zookeeper.audit.ZKAuditLogger; import org.apache.zookeeper.common.PathTrie; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; @@ -1045,8 +1047,10 @@ void killSession(long session, long zxid) { HashSet list = ephemerals.remove(session); if (list != null) { for (String path : list) { + boolean deleted = false; try { deleteNode(path, zxid); + deleted = true; if (LOG.isDebugEnabled()) { LOG .debug("Deleting ephemeral node " + path @@ -1058,6 +1062,20 @@ void killSession(long session, long zxid) { + " while removing ephemeral for dead session 0x" + Long.toHexString(session)); } + String sessionHex = "0x" + Long.toHexString(session); + if (deleted) { + if (ZKAuditLogger.isAuditEnabled()) { + ZKAuditLogger.logSuccess(ZKAuditLogger.getZKUser(), + AuditConstants.OP_DEL_EZNODE_EXP, path, null, + sessionHex, null); + } + } else { + if (ZKAuditLogger.isAuditEnabled()) { + ZKAuditLogger.logFailure(ZKAuditLogger.getZKUser(), + AuditConstants.OP_DEL_EZNODE_EXP, path, null, + sessionHex, null); + } + } } } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java index f544c0bab2d..a0f4c968d27 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java @@ -39,13 +39,19 @@ import org.apache.zookeeper.OpResult.ErrorResult; import org.apache.zookeeper.OpResult.SetDataResult; import org.apache.zookeeper.Watcher.WatcherType; +import org.apache.zookeeper.ZKUtil; import org.apache.zookeeper.ZooDefs; import org.apache.zookeeper.ZooDefs.OpCode; +import org.apache.zookeeper.audit.AuditConstants; +import org.apache.zookeeper.audit.ZKAuditLogger; +import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; import org.apache.zookeeper.proto.CheckWatchesRequest; import org.apache.zookeeper.proto.Create2Response; +import org.apache.zookeeper.proto.CreateRequest; import org.apache.zookeeper.proto.CreateResponse; +import org.apache.zookeeper.proto.DeleteRequest; import org.apache.zookeeper.proto.ExistsRequest; import org.apache.zookeeper.proto.ExistsResponse; import org.apache.zookeeper.proto.GetACLRequest; @@ -58,7 +64,9 @@ import org.apache.zookeeper.proto.GetDataResponse; import org.apache.zookeeper.proto.RemoveWatchesRequest; import org.apache.zookeeper.proto.ReplyHeader; +import org.apache.zookeeper.proto.SetACLRequest; import org.apache.zookeeper.proto.SetACLResponse; +import org.apache.zookeeper.proto.SetDataRequest; import org.apache.zookeeper.proto.SetDataResponse; import org.apache.zookeeper.proto.SetWatches; import org.apache.zookeeper.proto.SyncRequest; @@ -162,6 +170,7 @@ public void processRequest(Request request) { Record rsp = null; try { if (request.getHdr() != null && request.getHdr().getType() == OpCode.error) { + addFailedTxnAduitLog(request); /* * When local session upgrading is disabled, leader will * reject the ephemeral node creation due to session expire. @@ -210,7 +219,7 @@ public void processRequest(Request request) { case OpCode.multi: { lastOp = "MULT"; rsp = new MultiResponse() ; - + boolean multiFailed = false; for (ProcessTxnResult subTxnResult : rc.multiResult) { OpResult subResult ; @@ -221,35 +230,44 @@ public void processRequest(Request request) { break; case OpCode.create: subResult = new CreateResult(subTxnResult.path); + addSuccessAudit(request, cnxn, AuditConstants.OP_CREATE, subTxnResult.path); break; case OpCode.create2: case OpCode.createTTL: case OpCode.createContainer: subResult = new CreateResult(subTxnResult.path, subTxnResult.stat); + addSuccessAudit(request, cnxn, AuditConstants.OP_CREATE, subTxnResult.path); break; case OpCode.delete: case OpCode.deleteContainer: subResult = new DeleteResult(); + addSuccessAudit(request, cnxn, AuditConstants.OP_DELETE, subTxnResult.path); break; case OpCode.setData: subResult = new SetDataResult(subTxnResult.stat); + addSuccessAudit(request, cnxn, AuditConstants.OP_SETDATA, subTxnResult.path); break; case OpCode.error: subResult = new ErrorResult(subTxnResult.err) ; + multiFailed = true; break; default: + multiFailed = true; throw new IOException("Invalid type of op"); } ((MultiResponse)rsp).add(subResult); } - + if (multiFailed) { + addFailureAudit(request, cnxn, AuditConstants.OP_MULTI_OP, null); + } break; } case OpCode.create: { lastOp = "CREA"; rsp = new CreateResponse(rc.path); err = Code.get(rc.err); + addAuditLog(request, cnxn, AuditConstants.OP_CREATE, rc.path, null, err); break; } case OpCode.create2: @@ -258,30 +276,36 @@ public void processRequest(Request request) { lastOp = "CREA"; rsp = new Create2Response(rc.path, rc.stat); err = Code.get(rc.err); + addAuditLog(request, cnxn, AuditConstants.OP_CREATE, rc.path, null, err); break; } case OpCode.delete: case OpCode.deleteContainer: { lastOp = "DELE"; err = Code.get(rc.err); + addAuditLog(request, cnxn, AuditConstants.OP_DELETE, rc.path, null, err); break; } case OpCode.setData: { lastOp = "SETD"; rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); + addAuditLog(request, cnxn, AuditConstants.OP_SETDATA, rc.path, null, err); break; } case OpCode.reconfig: { lastOp = "RECO"; rsp = new GetDataResponse(((QuorumZooKeeperServer)zks).self.getQuorumVerifier().toString().getBytes(), rc.stat); err = Code.get(rc.err); + addAuditLog(request, cnxn, AuditConstants.OP_RECONFIG, rc.path, null, err); break; } case OpCode.setACL: { lastOp = "SETA"; rsp = new SetACLResponse(rc.stat); err = Code.get(rc.err); + addAuditLog(request, cnxn, AuditConstants.OP_SETACL, rc.path, getACLs(request), + err); break; } case OpCode.closeSession: { @@ -514,4 +538,135 @@ public void shutdown() { LOG.info("shutdown of request processor complete"); } + private void addSuccessAudit(Request request, ServerCnxn cnxn, String op, + String path) { + addSuccessAudit(request, cnxn, op, path, null); + } + + private void addSuccessAudit(Request request, ServerCnxn cnxn, String op, + String path, + String acl) { + if (!ZKAuditLogger.isAuditEnabled()) { + return; + } + ZKAuditLogger.logSuccess(request.getUsers(), op, path, acl, + cnxn.getSessionIdHex(), + cnxn.getHostAddress()); + } + + private void addFailureAudit(Request request, ServerCnxn cnxn, String op, + String path) { + addFailureAudit(request, cnxn, op, path, null); + } + + private void addFailureAudit(Request request, ServerCnxn cnxn, String op, + String path, + String acl) { + if (!ZKAuditLogger.isAuditEnabled()) { + return; + } + ZKAuditLogger.logFailure(request.getUsers(), op, path, acl, + cnxn.getSessionIdHex(), + cnxn.getHostAddress()); + } + + private void addAuditLog(Request request, ServerCnxn cnxn, String op, + String path, String acl, + Code err) { + if (!ZKAuditLogger.isAuditEnabled()) { + return; + } + if (err == Code.OK) { + ZKAuditLogger.logSuccess(request.getUsers(), op, path, acl, + cnxn.getSessionIdHex(), + cnxn.getHostAddress()); + } else { + ZKAuditLogger.logFailure(request.getUsers(), op, path, acl, + cnxn.getSessionIdHex(), + cnxn.getHostAddress()); + } + } + + private String getACLs(Request request) { + ByteBuffer reqData = request.request.duplicate(); + reqData.rewind(); + SetACLRequest setACLRequest = new SetACLRequest(); + try { + ByteBufferInputStream.byteBuffer2Record(reqData, setACLRequest); + } catch (IOException e) { + e.printStackTrace(); + } + return ZKUtil.aclToString(setACLRequest.getAcl()); + } + + private void addFailedTxnAduitLog(Request request) { + if (!ZKAuditLogger.isAuditEnabled()) { + return; + } + String op = AuditConstants.OP_CREATE; + String path = null; + long sessionId = -1; + String address = null; + String acls = null; + ByteBuffer reqData = request.request.duplicate(); + reqData.rewind(); + try { + sessionId = request.cnxn.getSessionId(); + switch (request.type) { + case OpCode.create: + case OpCode.create2: + case OpCode.createContainer: + op = AuditConstants.OP_CREATE; + CreateRequest createRequest = new CreateRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, createRequest); + path = createRequest.getPath(); + break; + case OpCode.delete: + case OpCode.deleteContainer: + op = AuditConstants.OP_DELETE; + // path = new String(request.request.array()); + DeleteRequest deleteRequest = new DeleteRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, deleteRequest); + path = deleteRequest.getPath(); + break; + case OpCode.setData: + op = AuditConstants.OP_SETDATA; + SetDataRequest setDataRequest = new SetDataRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, + setDataRequest); + path = setDataRequest.getPath(); + break; + case OpCode.setACL: + op = AuditConstants.OP_SETACL; + SetACLRequest setACLRequest = new SetACLRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, setACLRequest); + path = setACLRequest.getPath(); + acls = ZKUtil.aclToString(setACLRequest.getAcl()); + break; + case OpCode.multi: + op = AuditConstants.OP_MULTI_OP; + break; + case OpCode.reconfig: + op = AuditConstants.OP_RECONFIG; + break; + default: + // This should not happen as audit is logged only from known + // operations. This is added to fix find bug issue. + op = "Unknown"; + break; + } + + if (request.cnxn.getRemoteSocketAddress() != null + && request.cnxn.getRemoteSocketAddress() + .getAddress() != null) { + address = request.cnxn.getRemoteSocketAddress().getAddress() + .getHostAddress(); + } + ZKAuditLogger.logFailure(request.getUsers(), op, path, acls, + "0x" + Long.toHexString(sessionId), address); + } catch (Throwable e) { + LOG.error("Failed to audit log request {} failure", request.type, + e); + } + } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/Request.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/Request.java index ede9280441a..fea8e4bc639 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/Request.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/Request.java @@ -27,6 +27,7 @@ import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.Id; import org.apache.zookeeper.server.quorum.flexible.QuorumVerifier; +import org.apache.zookeeper.server.util.AuthUtil; import org.apache.zookeeper.txn.TxnHeader; /** @@ -297,4 +298,31 @@ public void setException(KeeperException e) { public KeeperException getException() { return e; } + + /** + * Returns comma separated list of users authenticated in the current + * session + */ + public String getUsers() { + if (authInfo == null) { + return (String) null; + } + if (authInfo.size() == 1) { + return AuthUtil.getUser(authInfo.get(0)); + } + StringBuilder users = new StringBuilder(); + boolean first = true; + for (Id id : authInfo) { + String user = AuthUtil.getUser(id); + if (user != null) { + if (first) { + first = false; + } else { + users.append(","); + } + users.append(user); + } + } + return users.toString(); + } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java index c60503fe7fc..4ad21f64f2c 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.security.cert.Certificate; @@ -391,4 +392,26 @@ public void cleanupWriterSocket(PrintWriter pwriter) { } } } + + /** + * Returns the IP address or empty string. + */ + public String getHostAddress() { + InetSocketAddress remoteSocketAddress = getRemoteSocketAddress(); + if (remoteSocketAddress == null) { + return ""; + } + InetAddress address = remoteSocketAddress.getAddress(); + if (address == null) { + return ""; + } + return address.getHostAddress(); + } + + /** + * Get session id in hexadecimal notation. + */ + public String getSessionIdHex() { + return "0x" + Long.toHexString(getSessionId()); + } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java index b9582ce2683..1fe42b8d2be 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java @@ -54,6 +54,7 @@ public abstract class ServerCnxnFactory { */ static final ByteBuffer closeConn = ByteBuffer.allocate(0); + private static String loginUser = System.getProperty("user.name", ""); public abstract int getLocalPort(); public abstract Iterable getConnections(); @@ -238,10 +239,19 @@ protected void configureSaslLogin() throws IOException { try { saslServerCallbackHandler = new SaslServerCallbackHandler(Configuration.getConfiguration()); login = new Login(serverSection, saslServerCallbackHandler, new ZKConfig() ); + loginUser = login.getUserName(); login.startThreadIfNeeded(); } catch (LoginException e) { throw new IOException("Could not configure server because SASL configuration did not allow the " + " ZooKeeper server to authenticate itself properly: " + e); } } + + /** + * User who has started the ZooKeeper server user, it will be the logged-in + * user. If no user logged-in then system user + */ + public static String getUserName() { + return loginUser; + } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java index 12b8dcd9204..cb9a1cc4ff4 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ZooKeeperServerMain.java @@ -25,6 +25,8 @@ import javax.management.JMException; import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.audit.AuditConstants; +import org.apache.zookeeper.audit.ZKAuditLogger; import org.apache.zookeeper.jmx.ManagedUtil; import org.apache.zookeeper.server.admin.AdminServer; import org.apache.zookeeper.server.admin.AdminServer.AdminServerException; @@ -66,21 +68,26 @@ public static void main(String[] args) { LOG.error("Invalid arguments, exiting abnormally", e); LOG.info(USAGE); System.err.println(USAGE); + addServerStartFailureAuditLog(); System.exit(2); } catch (ConfigException e) { LOG.error("Invalid config, exiting abnormally", e); System.err.println("Invalid config, exiting abnormally"); + addServerStartFailureAuditLog(); System.exit(2); } catch (DatadirException e) { LOG.error("Unable to access datadir, exiting abnormally", e); System.err.println("Unable to access datadir, exiting abnormally"); + addServerStartFailureAuditLog(); System.exit(3); } catch (AdminServerException e) { LOG.error("Unable to start AdminServer, exiting abnormally", e); System.err.println("Unable to start AdminServer, exiting abnormally"); + addServerStartFailureAuditLog(); System.exit(4); } catch (Exception e) { LOG.error("Unexpected exception, exiting abnormally", e); + addServerStartFailureAuditLog(); System.exit(1); } LOG.info("Exiting normally"); @@ -156,6 +163,7 @@ public void runFromConfig(ServerConfig config) Integer.getInteger("znode.container.maxPerMinute", 10000) ); containerManager.start(); + ZKAuditLogger.addZKStartStopAuditLog(); // Watch status of ZooKeeper server. It will do a graceful shutdown // if the server is not running or hits an internal error. @@ -182,6 +190,10 @@ public void runFromConfig(ServerConfig config) } } + private static void addServerStartFailureAuditLog() { + ZKAuditLogger.logFailure(ZKAuditLogger.getZKUser(), AuditConstants.OP_START); + } + /** * Shutdown the serving instance */ diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java index f4f6b63133c..252dec9d579 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/QuorumPeerMain.java @@ -25,6 +25,8 @@ import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.zookeeper.audit.AuditConstants; +import org.apache.zookeeper.audit.ZKAuditLogger; import org.apache.zookeeper.jmx.ManagedUtil; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZKDatabase; @@ -84,27 +86,38 @@ public static void main(String[] args) { LOG.error("Invalid arguments, exiting abnormally", e); LOG.info(USAGE); System.err.println(USAGE); + addServerStartFailureAuditLog(); System.exit(2); } catch (ConfigException e) { LOG.error("Invalid config, exiting abnormally", e); System.err.println("Invalid config, exiting abnormally"); + addServerStartFailureAuditLog(); System.exit(2); } catch (DatadirException e) { LOG.error("Unable to access datadir, exiting abnormally", e); System.err.println("Unable to access datadir, exiting abnormally"); + addServerStartFailureAuditLog(); System.exit(3); } catch (AdminServerException e) { LOG.error("Unable to start AdminServer, exiting abnormally", e); System.err.println("Unable to start AdminServer, exiting abnormally"); + addServerStartFailureAuditLog(); System.exit(4); } catch (Exception e) { LOG.error("Unexpected exception, exiting abnormally", e); + addServerStartFailureAuditLog(); System.exit(1); } LOG.info("Exiting normally"); System.exit(0); } + private static void addServerStartFailureAuditLog() { + if (ZKAuditLogger.isAuditEnabled()) { + ZKAuditLogger.logFailure(ZKAuditLogger.getZKUser(), AuditConstants.OP_START); + } + } + protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException { @@ -203,6 +216,7 @@ public void runFromConfig(QuorumPeerConfig config) quorumPeer.initialize(); quorumPeer.start(); + ZKAuditLogger.addZKStartStopAuditLog(); quorumPeer.join(); } catch (InterruptedException e) { // warn, but generally this is ok From da20cac0c6730d78abef0e2fb00f77d4618eb330 Mon Sep 17 00:00:00 2001 From: Mohammad Arshad Date: Thu, 13 Sep 2018 02:09:18 +0530 Subject: [PATCH 2/4] ZOOKEEPER-1260: Review comment fix --- .../main/java/org/apache/zookeeper/ZKUtil.java | 15 +++++++++++++++ .../zookeeper/server/FinalRequestProcessor.java | 11 ++++++----- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java index 73fdf18780a..dfbfddb1b32 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java @@ -20,8 +20,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Deque; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import org.apache.zookeeper.AsyncCallback.StringCallback; import org.apache.zookeeper.AsyncCallback.VoidCallback; @@ -33,6 +35,7 @@ public class ZKUtil { private static final Logger LOG = LoggerFactory.getLogger(ZKUtil.class); + private static final Map permCache = new HashMap(); /** * Recursively delete the node with the given path. *

@@ -176,6 +179,18 @@ private static void visitSubTreeDFSHelper(ZooKeeper zk, final String path, * @return string representation of permissions */ public static String getPermString(int perms) { + String permString = permCache.get(perms); + if (permString != null) { + return permString; + } + permString = constructPermString(perms); + if (!permString.isEmpty()) { + permCache.put(perms, permString); + } + return permString; + } + + private static String constructPermString(int perms) { StringBuilder p = new StringBuilder(); if ((perms & ZooDefs.Perms.CREATE) != 0) { p.append('c'); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java index a0f4c968d27..edafba6f671 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java @@ -170,7 +170,7 @@ public void processRequest(Request request) { Record rsp = null; try { if (request.getHdr() != null && request.getHdr().getType() == OpCode.error) { - addFailedTxnAduitLog(request); + addFailedTxnAuditLog(request); /* * When local session upgrading is disabled, leader will * reject the ephemeral node creation due to session expire. @@ -304,8 +304,10 @@ public void processRequest(Request request) { lastOp = "SETA"; rsp = new SetACLResponse(rc.stat); err = Code.get(rc.err); - addAuditLog(request, cnxn, AuditConstants.OP_SETACL, rc.path, getACLs(request), - err); + /** Here audit enable check is done to avoid getACLs() call in case audit is disabled. */ + if (ZKAuditLogger.isAuditEnabled()) { + addAuditLog(request, cnxn, AuditConstants.OP_SETACL, rc.path, getACLs(request), err); + } break; } case OpCode.closeSession: { @@ -599,7 +601,7 @@ private String getACLs(Request request) { return ZKUtil.aclToString(setACLRequest.getAcl()); } - private void addFailedTxnAduitLog(Request request) { + private void addFailedTxnAuditLog(Request request) { if (!ZKAuditLogger.isAuditEnabled()) { return; } @@ -624,7 +626,6 @@ private void addFailedTxnAduitLog(Request request) { case OpCode.delete: case OpCode.deleteContainer: op = AuditConstants.OP_DELETE; - // path = new String(request.request.array()); DeleteRequest deleteRequest = new DeleteRequest(); ByteBufferInputStream.byteBuffer2Record(reqData, deleteRequest); path = deleteRequest.getPath(); From 163d595a46d10d314a1f4d86cdce397e71400b1d Mon Sep 17 00:00:00 2001 From: Mohammad Arshad Date: Mon, 23 Sep 2019 01:29:51 +0530 Subject: [PATCH 3/4] Rebasing it --- .../content/xdocs/zookeeperAuditLogs.xml | 205 ------------------ .../main/resources/markdown/html/header.html | 3 + .../markdown}/images/zkAuditLogs.jpg | Bin .../src/main/resources/markdown/index.md | 1 + .../main/resources/markdown/zookeeperAdmin.md | 5 + .../resources/markdown/zookeeperAuditLogs.md | 124 +++++++++++ .../zookeeper/audit/AuditConstants.java | 0 .../zookeeper/audit/ZKAuditLogFormatter.java | 0 .../apache/zookeeper/audit/ZKAuditLogger.java | 19 +- .../org/apache/zookeeper/server/DataTree.java | 8 +- .../server/FinalRequestProcessor.java | 204 ++++++++++------- .../zookeeper/server/util/AuthUtil.java | 0 .../zookeeper/audit/AuditLogPerfReading.java | 0 .../audit/ZKAuditLogFormatterTest.java | 0 .../audit/ZKAuditLoggerPerformance.java | 0 .../zookeeper/audit/ZKAuditLoggerTest.java | 76 ++++--- .../zookeeper/server/util/AuthUtilTest.java | 0 17 files changed, 308 insertions(+), 337 deletions(-) delete mode 100644 zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml rename zookeeper-docs/src/{documentation/resources => main/resources/markdown}/images/zkAuditLogs.jpg (100%) create mode 100644 zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md rename {src/java/main => zookeeper-server/src/main/java}/org/apache/zookeeper/audit/AuditConstants.java (100%) rename {src/java/main => zookeeper-server/src/main/java}/org/apache/zookeeper/audit/ZKAuditLogFormatter.java (100%) rename {src/java/main => zookeeper-server/src/main/java}/org/apache/zookeeper/audit/ZKAuditLogger.java (87%) rename {src/java/main => zookeeper-server/src/main/java}/org/apache/zookeeper/server/util/AuthUtil.java (100%) rename {src/java/test => zookeeper-server/src/test/java}/org/apache/zookeeper/audit/AuditLogPerfReading.java (100%) rename {src/java/test => zookeeper-server/src/test/java}/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java (100%) rename {src/java/test => zookeeper-server/src/test/java}/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java (100%) rename {src/java/test => zookeeper-server/src/test/java}/org/apache/zookeeper/audit/ZKAuditLoggerTest.java (86%) rename {src/java/test => zookeeper-server/src/test/java}/org/apache/zookeeper/server/util/AuthUtilTest.java (100%) diff --git a/zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml b/zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml deleted file mode 100644 index 4771b63533b..00000000000 --- a/zookeeper-docs/src/documentation/content/xdocs/zookeeperAuditLogs.xml +++ /dev/null @@ -1,205 +0,0 @@ - - - -

- ZooKeeper Audit Logging - - - Licensed 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. - - - - This document contains information about Audit Logs in ZooKeeper. - - -
- ZooKeeper Audit Logs - Apache ZooKeeper supports audit logs form version 3.5.5. By default audit logs are disabled. To enable audit - logs configure audit.enable=true in conf/zoo.cfg. Audit logs are not logged on all the ZooKeeper servers, but logged - only on the servers where client is connected as depicted in below figure. - - - - - - The audit log captures detailed information for the operations that are selected to be audited. The audit - information is written as a set of key=value pairs for the following keys - - Audit Log Content - - - - Key - Value - - - - - session - client session id - - - user - - comma separated list of users who are associate with a client session. To know who is taken as user in audit logs - refer section - - - - - ip - client IP address - - - operation - any one of the selected operations for audit. Possible values are - (serverStart| serverStop| create| delete| setData| setAcl| multiOperation| reconfig| ephemeralZNodeDeleteOnSessionClose) - - - - znode - path of the znode - - - acl - String representation of znode ACL like cdrwa(create, delete,read, write, admin). This is logged - only for setAcl operation - - - result - result of the operation. Possible values are (success|failure|invoked). Result "invoked" is used - for serverStop operation because stop is logged before ensuring that server actually stopped. - - - - -
- Below are sample audit logs for all operations, where client is connected from 192.168.1.2, client principal is - zkcli@HADOOP.COM, server principal is zookeeper/192.168.1.3@HADOOP.COM - - user=zookeeper/192.168.1.3 operation=serverStart result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/a result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/a result=failure - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/a result=failure - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/a result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setAcl znode=/a acl=world:anyone:cdrwa result=failure - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setAcl znode=/a acl=world:anyone:cdrwa result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/b result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/b result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/b result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=multiOperation result=failure - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/a result=failure - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/a result=success - session=0x19344730001 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/ephemral result=success - session=0x19344730001 user=zookeeper/192.168.1.3 operation=ephemeralZNodeDeletionOnSessionCloseOrExpire znode=/ephemral result=success - session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=reconfig znode=/zookeeper/config result=success - user=zookeeper/192.168.1.3 operation=serverStop result=invoked - -
-
- ZooKeeper Audit Log Configuration - By default audit logs are disabled. To enable audit logs configure audit.enable=true in conf/zoo.cfg. Audit - logging is done using log4j. Following is the default log4j configuration for audit logs in conf/log4j.properties - - - # - # zk audit logging - # - zookeeper.auditlog.file=zookeeper_audit.log - zookeeper.auditlog.threshold=INFO - audit.logger=INFO, RFAAUDIT - log4j.logger.org.apache.zookeeper.audit.ZKAuditLogger=${audit.logger} - log4j.additivity.org.apache.zookeeper.audit.ZKAuditLogger=false - log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender - log4j.appender.RFAAUDIT.File=${zookeeper.log.dir}/${zookeeper.auditlog.file} - log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout - log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n - log4j.appender.RFAAUDIT.Threshold=${zookeeper.auditlog.threshold} - - # Max log file size of 10MB - log4j.appender.RFAAUDIT.MaxFileSize=10MB - log4j.appender.RFAAUDIT.MaxBackupIndex=10 - - Change above configuration to customize the auditlog file, number of backups, max file size etc. -
-
- Who is taken as user in audit logs? - By default there are only four authentication provider - - - IPAuthenticationProvider - - - SASLAuthenticationProvider - - - X509AuthenticationProvider - - - DigestAuthenticationProvider - - - User is decided based on the configured authentication provider. - - - When IPAuthenticationProvider is configured then authenticated IP is taken as user - - - When SASLAuthenticationProvider is configured then client principal is taken as user - - - - When X509AuthenticationProvider is configured then client certificate is taken as user - - - - When DigestAuthenticationProvider is configured then authenticated user is user - - - - For custom authentication provider whatever is stored in org.apache.zookeeper.data.Id.id is taken as user. - Generally only user name is stored in this field but it is up to the custom authentication provider what they store - in it. For audit logging value of org.apache.zookeeper.data.Id.id would be taken as user. - - In ZooKeeper Server not all the operations are done by clients but some operations are done by the server - itself. For example when client closes the session, ephemeral znodes are delete by the Server. These deletion are - not done by clients directly but it is done the server itself these are called system operations. For these system - operations the user associated with the ZooKeeper server are taken as user while audit logging these operations. For - example if in ZooKeeper server principal is zookeeper/hadoop.hadoop.com@HADOOP.COM then this becomes the system user - and all the system operations will be logged with this user name. - - user=zookeeper/hadoop.hadoop.com@HADOOP.COM operation=serverStart result=success - If there is no user associate with ZooKeeper server then the user who started the ZooKeeper server is taken as - the user. For example if server started by root then root is taken as the system user - - user=root operation=serverStart result=success - Single client can attach multiple authentication schemes to a session, in this case all authenticated schemes - will taken taken as user and will be presented as comma separated list. For example if a client is authenticate with - principal zkcli@HADOOP.COM and ip 127.0.0.1 then create znode audit log will be as: - session=0x10c0bcb0000 user=zkcli@HADOOP.COM,127.0.0.1 ip=127.0.0.1 operation=create znode=/a result=success -
-
diff --git a/zookeeper-docs/src/main/resources/markdown/html/header.html b/zookeeper-docs/src/main/resources/markdown/html/header.html index fe26830bb1a..64f5f6daa50 100644 --- a/zookeeper-docs/src/main/resources/markdown/html/header.html +++ b/zookeeper-docs/src/main/resources/markdown/html/header.html @@ -104,6 +104,9 @@ + diff --git a/zookeeper-docs/src/documentation/resources/images/zkAuditLogs.jpg b/zookeeper-docs/src/main/resources/markdown/images/zkAuditLogs.jpg similarity index 100% rename from zookeeper-docs/src/documentation/resources/images/zkAuditLogs.jpg rename to zookeeper-docs/src/main/resources/markdown/images/zkAuditLogs.jpg diff --git a/zookeeper-docs/src/main/resources/markdown/index.md b/zookeeper-docs/src/main/resources/markdown/index.md index 081cfc85f19..cecc40390a2 100644 --- a/zookeeper-docs/src/main/resources/markdown/index.md +++ b/zookeeper-docs/src/main/resources/markdown/index.md @@ -49,6 +49,7 @@ archives. + [Hierarchical quorums](zookeeperHierarchicalQuorums.html) + [Observers](zookeeperObservers.html) - non-voting ensemble members that easily improve ZooKeeper's scalability + [Dynamic Reconfiguration](zookeeperReconfig.html) - a guide on how to use dynamic reconfiguration in ZooKeeper + + [Audit Logging](zookeeperAuditLogs.html) - a guide on how to configure audit logs in ZooKeeper Server and what contents are logged. + **Contributors** Documents for Developers Contributing to the ZooKeeper Open Source Project + [ZooKeeper Internals](zookeeperInternals.html) - assorted topics on the inner workings of ZooKeeper diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md index 73a6f9d5f90..e8cc7e2f390 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAdmin.md @@ -715,6 +715,11 @@ property, when available, is noted below. and restart ZooKeeper process so ZooKeeper can continue normal data consistency check during recovery process. Default value is false. +* *audit.enabled* : + (Java system property: **zookeeper.audit.enabled**) + **New in 3.5.7:** + By default audit logs are disabled. Set to "true" to enable it. Default value is "false". + See the [ZooKeeper audit logs](zookeeperAuditLogs.html) for more information. diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md new file mode 100644 index 00000000000..79ebca7efec --- /dev/null +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md @@ -0,0 +1,124 @@ + + +# ZooKeeper Audit Logging + +* [ZooKeeper Audit Logs](#ch_auditLogs) +* [ZooKeeper Audit Log Configuration](#ch_reconfig_format) +* [Who is taken as user in audit logs?](#ch_zkAuditUser) + + +## ZooKeeper Audit Logs + +Apache ZooKeeper supports audit logs form version 3.5.7. By default audit logs are disabled. To enable audit logs configure audit.enable=true in conf/zoo.cfg. Audit logs are not logged on all the ZooKeeper servers, but logged only on the servers where client is connected as depicted in below figure. + +![Audit Logs](images/zkAuditLogs.jpg) + + +The audit log captures detailed information for the operations that are selected to be audited. The audit information is written as a set of key=value pairs for the following keys + +| Key | Value | +| ----- | ----- | +|session | client session id | +|user | comma separated list of users who are associate with a client session. For more on this, see [Who is taken as user in audit logs](#ch_zkAuditUser). +|ip | client IP address +|operation | any one of the selected operations for audit. Possible values are(serverStart, serverStop, create, delete, setData, setAcl, multiOperation, reconfig, ephemeralZNodeDeleteOnSessionClose) +|znode | path of the znode +|znode type | type of znode in case of creation operation +|acl | String representation of znode ACL like cdrwa(create, delete,read, write, admin). This is logged only for setAcl operation +|result | result of the operation. Possible values are (success/failure/invoked). Result "invoked" is used for serverStop operation because stop is logged before ensuring that server actually stopped. + +Below are sample audit logs for all operations, where client is connected from 192.168.1.2, client principal is zkcli@HADOOP.COM, server principal is zookeeper/192.168.1.3@HADOOP.COM + + user=zookeeper/192.168.1.3 operation=serverStart result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/a znode_type=persistent result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/a znode_type=persistent result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/a result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/a result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setAcl znode=/a acl=world:anyone:cdrwa result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setAcl znode=/a acl=world:anyone:cdrwa result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/b znode_type=persistent result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=setData znode=/b result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/b result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=multiOperation result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/a result=failure + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=delete znode=/a result=success + session=0x19344730001 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=create znode=/ephemral znode_type=ephemral result=success + session=0x19344730001 user=zookeeper/192.168.1.3 operation=ephemeralZNodeDeletionOnSessionCloseOrExpire znode=/ephemral result=success + session=0x19344730000 user=192.168.1.2,zkcli@HADOOP.COM ip=192.168.1.2 operation=reconfig znode=/zookeeper/config result=success + user=zookeeper/192.168.1.3 operation=serverStop result=invoked + + + +## ZooKeeper Audit Log Configuration + +By default audit logs are disabled. To enable audit logs configure audit.enable=true in conf/zoo.cfg. Audit logging is done using log4j. Following is the default log4j configuration for audit logs in conf/log4j.properties + + # + # zk audit logging + # + zookeeper.auditlog.file=zookeeper_audit.log + zookeeper.auditlog.threshold=INFO + audit.logger=INFO, RFAAUDIT + log4j.logger.org.apache.zookeeper.audit.ZKAuditLogger=${audit.logger} + log4j.additivity.org.apache.zookeeper.audit.ZKAuditLogger=false + log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender + log4j.appender.RFAAUDIT.File=${zookeeper.log.dir}/${zookeeper.auditlog.file} + log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout + log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n + log4j.appender.RFAAUDIT.Threshold=${zookeeper.auditlog.threshold} + + # Max log file size of 10MB + log4j.appender.RFAAUDIT.MaxFileSize=10MB + log4j.appender.RFAAUDIT.MaxBackupIndex=10 + +Change above configuration to customize the auditlog file, number of backups, max file size etc. + + + +## Who is taken as user in audit logs? + +By default there are only four authentication provider: + +* IPAuthenticationProvider +* SASLAuthenticationProvider +* X509AuthenticationProvider +* DigestAuthenticationProvider + +User is decided based on the configured authentication provider: + +* When IPAuthenticationProvider is configured then authenticated IP is taken as user +* When SASLAuthenticationProvider is configured then client principal is taken as user +* When X509AuthenticationProvider is configured then client certificate is taken as user +* When DigestAuthenticationProvider is configured then authenticated user is user + +For custom authentication provider whatever is stored in org.apache.zookeeper.data.Id.id is taken as user. Generally only user name is stored in this field but it is up to the custom authentication provider what they store in it. For audit logging value of org.apache.zookeeper.data.Id.id would be taken as user. + +In ZooKeeper Server not all the operations are done by clients but some operations are done by the server itself. For example when client closes the session, ephemeral znodes are deleted by the Server. These deletion are not done by clients directly but it is done the server itself these are called system operations. For these system operations the user associated with the ZooKeeper server are taken as user while audit logging these operations. For example if in ZooKeeper server principal is zookeeper/hadoop.hadoop.com@HADOOP.COM then this becomes the system user and all the system operations will be logged with this user name. + + user=zookeeper/hadoop.hadoop.com@HADOOP.COM operation=serverStart result=success + + +If there is no user associate with ZooKeeper server then the user who started the ZooKeeper server is taken as the user. For example if server started by root then root is taken as the system user + + user=root operation=serverStart result=success + + +Single client can attach multiple authentication schemes to a session, in this case all authenticated schemes will taken taken as user and will be presented as comma separated list. For example if a client is authenticate with principal zkcli@HADOOP.COM and ip 127.0.0.1 then create znode audit log will be as: + + session=0x10c0bcb0000 user=zkcli@HADOOP.COM,127.0.0.1 ip=127.0.0.1 operation=create znode=/a result=success + + diff --git a/src/java/main/org/apache/zookeeper/audit/AuditConstants.java b/zookeeper-server/src/main/java/org/apache/zookeeper/audit/AuditConstants.java similarity index 100% rename from src/java/main/org/apache/zookeeper/audit/AuditConstants.java rename to zookeeper-server/src/main/java/org/apache/zookeeper/audit/AuditConstants.java diff --git a/src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java b/zookeeper-server/src/main/java/org/apache/zookeeper/audit/ZKAuditLogFormatter.java similarity index 100% rename from src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java rename to zookeeper-server/src/main/java/org/apache/zookeeper/audit/ZKAuditLogFormatter.java diff --git a/src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java b/zookeeper-server/src/main/java/org/apache/zookeeper/audit/ZKAuditLogger.java similarity index 87% rename from src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java rename to zookeeper-server/src/main/java/org/apache/zookeeper/audit/ZKAuditLogger.java index 959015e2a2a..9042653cc07 100644 --- a/src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/audit/ZKAuditLogger.java @@ -61,7 +61,7 @@ public void printLog(String logMsg) { } public static enum Keys { - USER, OPERATION, RESULT, IP, ACL, ZNODE, SESSION; + USER, OPERATION, RESULT, IP, ACL, ZNODE, SESSION, ZNODE_TYPE; } public static void logInvoked(String user, String operation) { @@ -77,32 +77,33 @@ public static void logFailure(String user, String operation) { } private static void log(LogLevel level, String user, String operation, String logType) { - level.printLog(createLog(user, operation, null, null, null, null, logType)); + level.printLog(createLog(user, operation, null, null, null, null, null, logType)); } public static void logSuccess(String user, String operation, String znode, String acl, - String session, String ip) { - LogLevel.INFO.printLog( - createLog(user, operation, znode, acl, session, ip, AuditConstants.SUCCESS)); + String createMode, String session, String ip) { + LogLevel.INFO.printLog(createLog(user, operation, znode, acl, createMode, session, ip, + AuditConstants.SUCCESS)); } public static void logFailure(String user, String operation, String znode, String acl, - String session, String ip) { - LogLevel.ERROR.printLog( - createLog(user, operation, znode, acl, session, ip, AuditConstants.FAILURE)); + String createMode, String session, String ip) { + LogLevel.ERROR.printLog(createLog(user, operation, znode, acl, createMode, session, ip, + AuditConstants.FAILURE)); } /** * A helper api for creating an audit log string. */ public static String createLog(String user, String operation, String znode, String acl, - String session, String ip, String status) { + String createMode, String session, String ip, String status) { ZKAuditLogFormatter fmt = new ZKAuditLogFormatter(); fmt.addField(Keys.SESSION.name(), session); fmt.addField(Keys.USER.name(), user); fmt.addField(Keys.IP.name(), ip); fmt.addField(Keys.OPERATION.name(), operation); fmt.addField(Keys.ZNODE.name(), znode); + fmt.addField(Keys.ZNODE_TYPE.name(), createMode); fmt.addField(Keys.ACL.name(), acl); fmt.addField(Keys.RESULT.name(), status); return fmt.format(); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java index 4d1ed22d603..cc648b516d3 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java @@ -1066,14 +1066,14 @@ void killSession(long session, long zxid) { if (deleted) { if (ZKAuditLogger.isAuditEnabled()) { ZKAuditLogger.logSuccess(ZKAuditLogger.getZKUser(), - AuditConstants.OP_DEL_EZNODE_EXP, path, null, - sessionHex, null); + AuditConstants.OP_DEL_EZNODE_EXP, path, null, null, + sessionHex, null); } } else { if (ZKAuditLogger.isAuditEnabled()) { ZKAuditLogger.logFailure(ZKAuditLogger.getZKUser(), - AuditConstants.OP_DEL_EZNODE_EXP, path, null, - sessionHex, null); + AuditConstants.OP_DEL_EZNODE_EXP, path, null, null, + sessionHex, null); } } } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java index edafba6f671..2e504464f12 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java @@ -21,9 +21,13 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import org.apache.jute.Record; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.MultiTransactionRecord; +import org.apache.zookeeper.Op; import org.apache.zookeeper.common.Time; import org.apache.zookeeper.data.Id; import org.slf4j.Logger; @@ -83,6 +87,7 @@ import java.nio.ByteBuffer; import java.util.List; import java.util.Locale; +import java.util.Map; /** * This Request processor actually applies any transaction associated with a @@ -219,6 +224,7 @@ public void processRequest(Request request) { case OpCode.multi: { lastOp = "MULT"; rsp = new MultiResponse() ; + Map createModes = getCreateModes(request); boolean multiFailed = false; for (ProcessTxnResult subTxnResult : rc.multiResult) { @@ -230,25 +236,29 @@ public void processRequest(Request request) { break; case OpCode.create: subResult = new CreateResult(subTxnResult.path); - addSuccessAudit(request, cnxn, AuditConstants.OP_CREATE, subTxnResult.path); + addSuccessAudit(request, cnxn, AuditConstants.OP_CREATE, + subTxnResult.path, null, createModes.get(subTxnResult.path)); break; case OpCode.create2: case OpCode.createTTL: case OpCode.createContainer: subResult = new CreateResult(subTxnResult.path, subTxnResult.stat); - addSuccessAudit(request, cnxn, AuditConstants.OP_CREATE, subTxnResult.path); + addSuccessAudit(request, cnxn, AuditConstants.OP_CREATE, + subTxnResult.path, null, createModes.get(subTxnResult.path)); break; case OpCode.delete: case OpCode.deleteContainer: subResult = new DeleteResult(); - addSuccessAudit(request, cnxn, AuditConstants.OP_DELETE, subTxnResult.path); + addSuccessAudit(request, cnxn, AuditConstants.OP_DELETE, + subTxnResult.path); break; case OpCode.setData: subResult = new SetDataResult(subTxnResult.stat); - addSuccessAudit(request, cnxn, AuditConstants.OP_SETDATA, subTxnResult.path); + addSuccessAudit(request, cnxn, AuditConstants.OP_SETDATA, + subTxnResult.path); break; case OpCode.error: - subResult = new ErrorResult(subTxnResult.err) ; + subResult = new ErrorResult(subTxnResult.err); multiFailed = true; break; default: @@ -267,7 +277,9 @@ public void processRequest(Request request) { lastOp = "CREA"; rsp = new CreateResponse(rc.path); err = Code.get(rc.err); - addAuditLog(request, cnxn, AuditConstants.OP_CREATE, rc.path, null, err); + String createMode = getCreateMode(request); + addAuditLog(request, cnxn, AuditConstants.OP_CREATE, rc.path, null, createMode, + err); break; } case OpCode.create2: @@ -276,37 +288,40 @@ public void processRequest(Request request) { lastOp = "CREA"; rsp = new Create2Response(rc.path, rc.stat); err = Code.get(rc.err); - addAuditLog(request, cnxn, AuditConstants.OP_CREATE, rc.path, null, err); + String createMode = getCreateMode(request); + addAuditLog(request, cnxn, AuditConstants.OP_CREATE, rc.path, null, createMode, + err); break; } case OpCode.delete: case OpCode.deleteContainer: { lastOp = "DELE"; err = Code.get(rc.err); - addAuditLog(request, cnxn, AuditConstants.OP_DELETE, rc.path, null, err); + addAuditLog(request, cnxn, AuditConstants.OP_DELETE, rc.path, null, null, err); break; } case OpCode.setData: { lastOp = "SETD"; rsp = new SetDataResponse(rc.stat); err = Code.get(rc.err); - addAuditLog(request, cnxn, AuditConstants.OP_SETDATA, rc.path, null, err); + addAuditLog(request, cnxn, AuditConstants.OP_SETDATA, rc.path, null, null, err); break; } case OpCode.reconfig: { lastOp = "RECO"; rsp = new GetDataResponse(((QuorumZooKeeperServer)zks).self.getQuorumVerifier().toString().getBytes(), rc.stat); err = Code.get(rc.err); - addAuditLog(request, cnxn, AuditConstants.OP_RECONFIG, rc.path, null, err); + addAuditLog(request, cnxn, AuditConstants.OP_RECONFIG, rc.path, null, null, err); break; } case OpCode.setACL: { lastOp = "SETA"; rsp = new SetACLResponse(rc.stat); err = Code.get(rc.err); - /** Here audit enable check is done to avoid getACLs() call in case audit is disabled. */ + /** Here audit enable check is done to avoid getACLs() call in case audit is disabled. */ if (ZKAuditLogger.isAuditEnabled()) { - addAuditLog(request, cnxn, AuditConstants.OP_SETACL, rc.path, getACLs(request), err); + addAuditLog(request, cnxn, AuditConstants.OP_SETACL, rc.path, getACLs(request), + null, err); } break; } @@ -540,51 +555,46 @@ public void shutdown() { LOG.info("shutdown of request processor complete"); } - private void addSuccessAudit(Request request, ServerCnxn cnxn, String op, - String path) { - addSuccessAudit(request, cnxn, op, path, null); + private void addSuccessAudit(Request request, ServerCnxn cnxn, String op, String path) { + addSuccessAudit(request, cnxn, op, path, null, null); } - private void addSuccessAudit(Request request, ServerCnxn cnxn, String op, - String path, - String acl) { + private void addSuccessAudit(Request request, ServerCnxn cnxn, String op, String path, + String acl, String createMode) { if (!ZKAuditLogger.isAuditEnabled()) { return; } - ZKAuditLogger.logSuccess(request.getUsers(), op, path, acl, - cnxn.getSessionIdHex(), + ZKAuditLogger + .logSuccess(request.getUsers(), op, path, acl, createMode, cnxn.getSessionIdHex(), cnxn.getHostAddress()); } - private void addFailureAudit(Request request, ServerCnxn cnxn, String op, - String path) { - addFailureAudit(request, cnxn, op, path, null); + private void addFailureAudit(Request request, ServerCnxn cnxn, String op, String path) { + addFailureAudit(request, cnxn, op, path, null, null); } - private void addFailureAudit(Request request, ServerCnxn cnxn, String op, - String path, - String acl) { + private void addFailureAudit(Request request, ServerCnxn cnxn, String op, String path, + String acl, String createMode) { if (!ZKAuditLogger.isAuditEnabled()) { return; } - ZKAuditLogger.logFailure(request.getUsers(), op, path, acl, - cnxn.getSessionIdHex(), + ZKAuditLogger + .logFailure(request.getUsers(), op, path, acl, createMode, cnxn.getSessionIdHex(), cnxn.getHostAddress()); } - private void addAuditLog(Request request, ServerCnxn cnxn, String op, - String path, String acl, - Code err) { + private void addAuditLog(Request request, ServerCnxn cnxn, String op, String path, String acl, + String createMode, Code err) { if (!ZKAuditLogger.isAuditEnabled()) { return; } if (err == Code.OK) { - ZKAuditLogger.logSuccess(request.getUsers(), op, path, acl, - cnxn.getSessionIdHex(), + ZKAuditLogger + .logSuccess(request.getUsers(), op, path, acl, createMode, cnxn.getSessionIdHex(), cnxn.getHostAddress()); } else { - ZKAuditLogger.logFailure(request.getUsers(), op, path, acl, - cnxn.getSessionIdHex(), + ZKAuditLogger + .logFailure(request.getUsers(), op, path, acl, createMode, cnxn.getSessionIdHex(), cnxn.getHostAddress()); } } @@ -610,64 +620,92 @@ private void addFailedTxnAuditLog(Request request) { long sessionId = -1; String address = null; String acls = null; + String createMode = null; ByteBuffer reqData = request.request.duplicate(); reqData.rewind(); try { sessionId = request.cnxn.getSessionId(); switch (request.type) { - case OpCode.create: - case OpCode.create2: - case OpCode.createContainer: - op = AuditConstants.OP_CREATE; - CreateRequest createRequest = new CreateRequest(); - ByteBufferInputStream.byteBuffer2Record(reqData, createRequest); - path = createRequest.getPath(); - break; - case OpCode.delete: - case OpCode.deleteContainer: - op = AuditConstants.OP_DELETE; - DeleteRequest deleteRequest = new DeleteRequest(); - ByteBufferInputStream.byteBuffer2Record(reqData, deleteRequest); - path = deleteRequest.getPath(); - break; - case OpCode.setData: - op = AuditConstants.OP_SETDATA; - SetDataRequest setDataRequest = new SetDataRequest(); - ByteBufferInputStream.byteBuffer2Record(reqData, - setDataRequest); - path = setDataRequest.getPath(); - break; - case OpCode.setACL: - op = AuditConstants.OP_SETACL; - SetACLRequest setACLRequest = new SetACLRequest(); - ByteBufferInputStream.byteBuffer2Record(reqData, setACLRequest); - path = setACLRequest.getPath(); - acls = ZKUtil.aclToString(setACLRequest.getAcl()); - break; - case OpCode.multi: - op = AuditConstants.OP_MULTI_OP; - break; - case OpCode.reconfig: - op = AuditConstants.OP_RECONFIG; - break; - default: - // This should not happen as audit is logged only from known - // operations. This is added to fix find bug issue. - op = "Unknown"; - break; + case OpCode.create: + case OpCode.create2: + case OpCode.createContainer: + op = AuditConstants.OP_CREATE; + CreateRequest createRequest = new CreateRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, createRequest); + path = createRequest.getPath(); + createMode = + CreateMode.fromFlag(createRequest.getFlags()).toString().toLowerCase(); + break; + case OpCode.delete: + case OpCode.deleteContainer: + op = AuditConstants.OP_DELETE; + DeleteRequest deleteRequest = new DeleteRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, deleteRequest); + path = deleteRequest.getPath(); + break; + case OpCode.setData: + op = AuditConstants.OP_SETDATA; + SetDataRequest setDataRequest = new SetDataRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, setDataRequest); + path = setDataRequest.getPath(); + break; + case OpCode.setACL: + op = AuditConstants.OP_SETACL; + SetACLRequest setACLRequest = new SetACLRequest(); + ByteBufferInputStream.byteBuffer2Record(reqData, setACLRequest); + path = setACLRequest.getPath(); + acls = ZKUtil.aclToString(setACLRequest.getAcl()); + break; + case OpCode.multi: + op = AuditConstants.OP_MULTI_OP; + break; + case OpCode.reconfig: + op = AuditConstants.OP_RECONFIG; + break; + default: + // This should not happen as audit is logged only from known + // operations. This is added to fix find bug issue. + op = "Unknown"; + break; } if (request.cnxn.getRemoteSocketAddress() != null - && request.cnxn.getRemoteSocketAddress() - .getAddress() != null) { - address = request.cnxn.getRemoteSocketAddress().getAddress() - .getHostAddress(); + && request.cnxn.getRemoteSocketAddress().getAddress() != null) { + address = request.cnxn.getRemoteSocketAddress().getAddress().getHostAddress(); } - ZKAuditLogger.logFailure(request.getUsers(), op, path, acls, - "0x" + Long.toHexString(sessionId), address); + ZKAuditLogger.logFailure(request.getUsers(), op, path, acls, createMode, + "0x" + Long.toHexString(sessionId), address); } catch (Throwable e) { - LOG.error("Failed to audit log request {} failure", request.type, - e); + LOG.error("Failed to audit log request {} failure", request.type, e); + } + } + + private String getCreateMode(Request request) throws IOException, KeeperException { + request.request.rewind(); + CreateRequest createRequest = new CreateRequest(); + ByteBufferInputStream.byteBuffer2Record(request.request, createRequest); + request.request.rewind(); + String lowerCase = CreateMode.fromFlag(createRequest.getFlags()).toString().toLowerCase(); + return lowerCase; + } + + private Map getCreateModes(Request request) + throws IOException, KeeperException { + Map createModes = new HashMap(); + // set the buffer position to 0. + request.request.rewind(); + MultiTransactionRecord multiRequest = new MultiTransactionRecord(); + ByteBufferInputStream.byteBuffer2Record(request.request, multiRequest); + // reset the buffer position to zero + request.request.rewind(); + for (Op op : multiRequest) { + if (op.getType() == ZooDefs.OpCode.create || op.getType() == ZooDefs.OpCode.create2 + || op.getType() == ZooDefs.OpCode.createContainer) { + CreateRequest requestRecord = (CreateRequest) op.toRequestRecord(); + createModes.put(requestRecord.getPath(), + CreateMode.fromFlag(requestRecord.getFlags()).toString().toLowerCase()); + } } + return createModes; } } diff --git a/src/java/main/org/apache/zookeeper/server/util/AuthUtil.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/util/AuthUtil.java similarity index 100% rename from src/java/main/org/apache/zookeeper/server/util/AuthUtil.java rename to zookeeper-server/src/main/java/org/apache/zookeeper/server/util/AuthUtil.java diff --git a/src/java/test/org/apache/zookeeper/audit/AuditLogPerfReading.java b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/AuditLogPerfReading.java similarity index 100% rename from src/java/test/org/apache/zookeeper/audit/AuditLogPerfReading.java rename to zookeeper-server/src/test/java/org/apache/zookeeper/audit/AuditLogPerfReading.java diff --git a/src/java/test/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java similarity index 100% rename from src/java/test/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java rename to zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLogFormatterTest.java diff --git a/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java similarity index 100% rename from src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java rename to zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLoggerPerformance.java diff --git a/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLoggerTest.java similarity index 86% rename from src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java rename to zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLoggerTest.java index 4210240e818..b58eded0d32 100644 --- a/src/java/test/org/apache/zookeeper/audit/ZKAuditLoggerTest.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/ZKAuditLoggerTest.java @@ -107,19 +107,18 @@ private void verifyEphemralZNodeAuditLogs(ByteArrayOutputStream os) CreateMode.EPHEMERAL); String session2 = "0x" + Long.toHexString(zk2.getSessionId()); verifyLog(getAuditLog(AuditConstants.OP_CREATE, ephemralPath, - AuditConstants.SUCCESS, null, - session2), readAuditLog(os)); + AuditConstants.SUCCESS, null, + CreateMode.EPHEMERAL.toString().toLowerCase(), + session2), readAuditLog(os)); zk2.close(); waitForDeletion(zk, ephemralPath, 100); // verify that ephemeral node deletion on session close are captured // in audit log // Because these operations are done by ZooKeeper server itself, // there are no IP user is zkServer user, not any client user - verifyLogs( - getAuditLog(AuditConstants.OP_DEL_EZNODE_EXP, ephemralPath, - AuditConstants.SUCCESS, - null, session2, ZKAuditLogger.getZKUser(), null), - readAuditLog(os, SERVER_COUNT)); + verifyLogs(getAuditLog(AuditConstants.OP_DEL_EZNODE_EXP, ephemralPath, + AuditConstants.SUCCESS, null, null, session2, + ZKAuditLogger.getZKUser(), null), readAuditLog(os, SERVER_COUNT)); } private void verifyMultiOperationAuditLogs(ByteArrayOutputStream os) @@ -135,6 +134,8 @@ private void verifyMultiOperationAuditLogs(ByteArrayOutputStream os) Op check = Op.check(multiop, -1); Op delete = Op.delete(multiop, -1); + String createMode = CreateMode.PERSISTENT.toString().toLowerCase(); + ops.add(create); ops.add(setData); ops.add(check); @@ -143,7 +144,8 @@ private void verifyMultiOperationAuditLogs(ByteArrayOutputStream os) zk.multi(ops); List multiOpLogs = readAuditLog(os, 3); // verify that each multi operation success is logged - verifyLog(getAuditLog(AuditConstants.OP_CREATE, multiop), + verifyLog(getAuditLog(AuditConstants.OP_CREATE, multiop, + AuditConstants.SUCCESS, null, createMode), multiOpLogs.get(0)); verifyLog(getAuditLog(AuditConstants.OP_SETDATA, multiop), multiOpLogs.get(1)); @@ -192,13 +194,13 @@ private void verifySetACLAuditLogs(ByteArrayOutputStream os, String path) Code code = exception.code(); assertEquals(Code.BADVERSION, code); } - verifyLog(getAuditLog(AuditConstants.OP_SETACL, path, - AuditConstants.FAILURE, - ZKUtil.aclToString(openAclUnsafe)), readAuditLog(os)); + verifyLog( + getAuditLog(AuditConstants.OP_SETACL, path, AuditConstants.FAILURE, + ZKUtil.aclToString(openAclUnsafe), null), readAuditLog(os)); zk.setACL(path, openAclUnsafe, -1); - verifyLog(getAuditLog(AuditConstants.OP_SETACL, path, - AuditConstants.SUCCESS, - ZKUtil.aclToString(openAclUnsafe)), readAuditLog(os)); + verifyLog( + getAuditLog(AuditConstants.OP_SETACL, path, AuditConstants.SUCCESS, + ZKUtil.aclToString(openAclUnsafe), null), readAuditLog(os)); } private void verifySetDataAuditLogs(ByteArrayOutputStream os, String path) @@ -218,23 +220,25 @@ private void verifySetDataAuditLogs(ByteArrayOutputStream os, String path) } private void verifyCreateAuditLogs(ByteArrayOutputStream os, String path) - throws KeeperException, InterruptedException, IOException { + throws KeeperException, InterruptedException, IOException { zk.create(path, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); + CreateMode.PERSISTENT); // success log - verifyLog(getAuditLog(AuditConstants.OP_CREATE, path), - readAuditLog(os)); + String createMode = CreateMode.PERSISTENT.toString().toLowerCase(); + verifyLog( + getAuditLog(AuditConstants.OP_CREATE, path, AuditConstants.SUCCESS, + null, createMode), readAuditLog(os)); try { zk.create(path, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, - CreateMode.PERSISTENT); + CreateMode.PERSISTENT); } catch (KeeperException exception) { Code code = exception.code(); assertEquals(Code.NODEEXISTS, code); } // Verify create operation log - verifyLog(getAuditLog(AuditConstants.OP_CREATE, path, - AuditConstants.FAILURE), - readAuditLog(os)); + verifyLog( + getAuditLog(AuditConstants.OP_CREATE, path, AuditConstants.FAILURE, + null, createMode), readAuditLog(os)); } private String getStartLog() { @@ -243,9 +247,9 @@ private String getStartLog() { * user=userName operation=ZooKeeperServer start result=success * */ - return ZKAuditLogger.createLog(ZKAuditLogger.getZKUser(), - AuditConstants.OP_START, null, - null, null, null, AuditConstants.SUCCESS); + return ZKAuditLogger + .createLog(ZKAuditLogger.getZKUser(), AuditConstants.OP_START, null, + null, null, null, null, AuditConstants.SUCCESS); } private String getAuditLog(String operation, String znode) { @@ -253,31 +257,31 @@ private String getAuditLog(String operation, String znode) { } private String getAuditLog(String operation, String znode, String status) { - return getAuditLog(operation, znode, status, null); + return getAuditLog(operation, znode, status, null, null); } private String getAuditLog(String operation, String znode, String status, - String acl) { + String acl, String createMode) { String session = getSession(); - return getAuditLog(operation, znode, status, acl, session); + return getAuditLog(operation, znode, status, acl, createMode, session); } private String getAuditLog(String operation, String znode, String status, - String acl, - String session) { + String acl, String createMode, String session) { String user = getUser(); String ip = getIp(); - return getAuditLog(operation, znode, status, acl, session, user, ip); + return getAuditLog(operation, znode, status, acl, createMode, session, + user, ip); } private String getAuditLog(String operation, String znode, String status, - String acl, - String session, String user, String ip) { + String acl, String createMode, String session, String user, String ip) { - String auditLog = ZKAuditLogger.createLog(user, operation, znode, acl, - session, ip, status); + String auditLog = ZKAuditLogger + .createLog(user, operation, znode, acl, createMode, session, ip, + status); LOG.info("expected audit log for operation '" + operation + "' is '" - + auditLog + "'"); + + auditLog + "'"); return auditLog; } diff --git a/src/java/test/org/apache/zookeeper/server/util/AuthUtilTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/util/AuthUtilTest.java similarity index 100% rename from src/java/test/org/apache/zookeeper/server/util/AuthUtilTest.java rename to zookeeper-server/src/test/java/org/apache/zookeeper/server/util/AuthUtilTest.java From ba3a447dd82855e76c5eb1cee11c9ef5dac46061 Mon Sep 17 00:00:00 2001 From: Mohammad Arshad Date: Thu, 3 Oct 2019 23:54:50 +0530 Subject: [PATCH 4/4] ZOOKEEPER-1260:Fixed review comments --- .../main/resources/markdown/zookeeperAuditLogs.md | 2 +- .../src/main/java/org/apache/zookeeper/Login.java | 3 ++- .../src/main/java/org/apache/zookeeper/ZKUtil.java | 14 +++----------- .../org/apache/zookeeper/audit/AuditConstants.java | 5 ++++- .../zookeeper/server/FinalRequestProcessor.java | 4 ++-- .../apache/zookeeper/server/ServerCnxnFactory.java | 2 +- .../zookeeper/audit/AuditLogPerfReading.java | 2 +- 7 files changed, 14 insertions(+), 18 deletions(-) diff --git a/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md b/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md index 79ebca7efec..306e908ce37 100644 --- a/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md +++ b/zookeeper-docs/src/main/resources/markdown/zookeeperAuditLogs.md @@ -23,7 +23,7 @@ limitations under the License. ## ZooKeeper Audit Logs -Apache ZooKeeper supports audit logs form version 3.5.7. By default audit logs are disabled. To enable audit logs configure audit.enable=true in conf/zoo.cfg. Audit logs are not logged on all the ZooKeeper servers, but logged only on the servers where client is connected as depicted in below figure. +Apache ZooKeeper supports audit logs from version 3.5.7. By default audit logs are disabled. To enable audit logs configure audit.enable=true in conf/zoo.cfg. Audit logs are not logged on all the ZooKeeper servers, but logged only on the servers where client is connected as depicted in below figure. ![Audit Logs](images/zkAuditLogs.jpg) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java b/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java index 4b81e2c0327..82f8e75c64a 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/Login.java @@ -49,6 +49,7 @@ public class Login { private static final String KINIT_COMMAND_DEFAULT = "/usr/bin/kinit"; private static final Logger LOG = LoggerFactory.getLogger(Login.class); + public static String SYSTEM_USER = System.getProperty("user.name", ""); public CallbackHandler callbackHandler; // LoginThread will sleep until 80% of time from last refresh to @@ -288,7 +289,7 @@ public Subject getSubject() { public String getUserName() { if (principal == null || principal.isEmpty()) { - return System.getProperty("user.name", ""); + return SYSTEM_USER; } return principal; } diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java index dfbfddb1b32..0ba5e05fb8c 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKUtil.java @@ -20,11 +20,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Deque; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.zookeeper.AsyncCallback.StringCallback; import org.apache.zookeeper.AsyncCallback.VoidCallback; import org.apache.zookeeper.KeeperException.Code; @@ -35,7 +35,7 @@ public class ZKUtil { private static final Logger LOG = LoggerFactory.getLogger(ZKUtil.class); - private static final Map permCache = new HashMap(); + private static final Map permCache = new ConcurrentHashMap(); /** * Recursively delete the node with the given path. *

@@ -179,15 +179,7 @@ private static void visitSubTreeDFSHelper(ZooKeeper zk, final String path, * @return string representation of permissions */ public static String getPermString(int perms) { - String permString = permCache.get(perms); - if (permString != null) { - return permString; - } - permString = constructPermString(perms); - if (!permString.isEmpty()) { - permCache.put(perms, permString); - } - return permString; + return permCache.computeIfAbsent(perms, k -> constructPermString(k)); } private static String constructPermString(int perms) { diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/audit/AuditConstants.java b/zookeeper-server/src/main/java/org/apache/zookeeper/audit/AuditConstants.java index 63c15d511cd..ef763df349f 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/audit/AuditConstants.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/audit/AuditConstants.java @@ -17,7 +17,10 @@ */ package org.apache.zookeeper.audit; -public class AuditConstants { +public final class AuditConstants { + private AuditConstants() { + //Utility classes should not have public constructors + } public static final String SUCCESS = "success"; public static final String FAILURE = "failure"; // operation is performed, result is not known yet diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java index 2e504464f12..405b9a339d5 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/FinalRequestProcessor.java @@ -600,7 +600,7 @@ private void addAuditLog(Request request, ServerCnxn cnxn, String op, String pat } private String getACLs(Request request) { - ByteBuffer reqData = request.request.duplicate(); + ByteBuffer reqData = request.request.slice(); reqData.rewind(); SetACLRequest setACLRequest = new SetACLRequest(); try { @@ -621,7 +621,7 @@ private void addFailedTxnAuditLog(Request request) { String address = null; String acls = null; String createMode = null; - ByteBuffer reqData = request.request.duplicate(); + ByteBuffer reqData = request.request.slice(); reqData.rewind(); try { sessionId = request.cnxn.getSessionId(); diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java index 1fe42b8d2be..b32fb370c73 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxnFactory.java @@ -54,7 +54,7 @@ public abstract class ServerCnxnFactory { */ static final ByteBuffer closeConn = ByteBuffer.allocate(0); - private static String loginUser = System.getProperty("user.name", ""); + private static String loginUser = Login.SYSTEM_USER; public abstract int getLocalPort(); public abstract Iterable getConnections(); diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/audit/AuditLogPerfReading.java b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/AuditLogPerfReading.java index 46d4e6534eb..491dadb510f 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/audit/AuditLogPerfReading.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/audit/AuditLogPerfReading.java @@ -20,7 +20,7 @@ /** * Audit log performance reading */ -public class AuditLogPerfReading { +public final class AuditLogPerfReading { // time taken by create operations private long create; // time taken by setData operations