Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions conf/log4j.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure this is a very dumb question, why is this called RFAAUDIT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is very valid question. By RFAAUDIT i meant Rolling File Audit. Also one A is extra by mistake. I think it is better to change the name to AUDITFILE.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't we use the same conversion pattern as the other log types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging is done only from one class, which thread is logging is not important. so only required parameters are logged.

log4j.appender.RFAAUDIT.Threshold=${zookeeper.auditlog.threshold}

# Max log file size of 10MB
log4j.appender.RFAAUDIT.MaxFileSize=10MB
log4j.appender.RFAAUDIT.MaxBackupIndex=10
7 changes: 7 additions & 0 deletions src/java/main/org/apache/zookeeper/Login.java
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,13 @@ public Subject getSubject() {
return subject;
}

public String getUserName() {
if (principal == null || principal.isEmpty()) {
return System.getProperty("user.name", "<NA>");
}
return principal;
}

public String getLoginContextName() {
return loginContextName;
}
Expand Down
53 changes: 53 additions & 0 deletions src/java/main/org/apache/zookeeper/ZKUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,22 @@
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;
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;

public class ZKUtil {
private static final Logger LOG = LoggerFactory.getLogger(ZKUtil.class);
private static final Map<Integer, String> permCache = new HashMap<Integer, String>();
/**
* Recursively delete the node with the given path.
* <p>
Expand Down Expand Up @@ -168,4 +172,53 @@ 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered caching these values in some way?
I see 2 options to make this faster:

  1. dynamic caching of already generated values,
  2. static caching of all possible values in a static hashmap (32 possible values if I'm not mistaken)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion
selecting dynamic caching

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');
}
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<ACL> 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();
}
}
37 changes: 37 additions & 0 deletions src/java/main/org/apache/zookeeper/audit/AuditConstants.java
Original file line number Diff line number Diff line change
@@ -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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about having another listing of ZooKeeper operations. Can we possibly do something in ZooDefs that keeps these string versions of the operations closer to the Opcodes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Operation names are something internal to ZooKeeper server. Shall we expose this end user through API? I am not sure. I have tried to segregate operation names though a different class. Please have a look.

  • org.apache.zookeeper.ZooDefs.opNames is exposing operation names, this operation name list is not complete. Also it has operation names which do not exist like getMaxChildren and setMaxChildren. I have no idea why operation name should be exposed and how it is used. Any idea on this ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should not expose the internal op codes, etc to end users, through API or through the public constant definition included in this patch. We can get rid of the constants definitions here and instead, whenever we want to audit log something we create the operation string on site and directly pass the string to the audit event function call. Something like:
case OpCode.create: subResult = new CreateResult(subTxnResult.path); addSuccessAudit(request, cnxn, "create", subTxnResult.path); break;
This approach seems make the maintain overhead low as we don't have to worry about the correlations between various op codes definitions and the only catch is we might have to duplicate the op code string in different places if we audit logging same thing in different places - but in this case it seems the cost of duplication is low comparing to maintaining explicit op constants.

I also checked HDFS code base and it took a similar approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operation names are used multiple places through constants. So keeping the AuditConstants as it is. Also not exposing operation name through ZooDefs constants.

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";
}
56 changes: 56 additions & 0 deletions src/java/main/org/apache/zookeeper/audit/ZKAuditLogFormatter.java
Original file line number Diff line number Diff line change
@@ -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<String, String> fieldVsValue = new LinkedHashMap<String, String>();

/**
* 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<String, String> 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();
}
}
133 changes: 133 additions & 0 deletions src/java/main/org/apache/zookeeper/audit/ZKAuditLogger.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
24 changes: 2 additions & 22 deletions src/java/main/org/apache/zookeeper/cli/GetAclCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -75,32 +75,12 @@ public boolean exec() throws CliException {

for (ACL a : acl) {
out.println(a.getId() + ": "
+ getPermString(a.getPerms()));
+ ZKUtil.getPermString(a.getPerms()));
}

if (cl.hasOption("s")) {
new StatPrinter(out).print(stat);
}
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();
}
}
Loading