Author: cdouglas
Date: Sun Jun 6 04:36:11 2010
New Revision: 951815
URL: http://svn.apache.org/viewvc?rev=951815&view=rev
Log:
MAPREDUCE-1543. Add an audit log for authentication events. Contributed by Amar Kamat and
Luke Lu
Added:
hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/AuditLogger.java
hadoop/mapreduce/trunk/src/test/mapred/org/apache/hadoop/mapred/TestAuditLogger.java
Modified:
hadoop/mapreduce/trunk/CHANGES.txt
hadoop/mapreduce/trunk/conf/log4j.properties
hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobACLsManager.java
hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobInProgress.java
hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobTracker.java
hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/QueueManager.java
Modified: hadoop/mapreduce/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/CHANGES.txt?rev=951815&r1=951814&r2=951815&view=diff
==============================================================================
--- hadoop/mapreduce/trunk/CHANGES.txt (original)
+++ hadoop/mapreduce/trunk/CHANGES.txt Sun Jun 6 04:36:11 2010
@@ -42,6 +42,9 @@ Trunk (unreleased changes)
MAPREDUCE-1545. Add timestamps for first task type launched in job summary.
(Luke Lu via cdouglas)
+ MAPREDUCE-1543. Add an audit log for authentication events. (Amar Kamat and
+ Luke Lu via cdouglas)
+
OPTIMIZATIONS
MAPREDUCE-1354. Enhancements to JobTracker for better performance and
Modified: hadoop/mapreduce/trunk/conf/log4j.properties
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/conf/log4j.properties?rev=951815&r1=951814&r2=951815&view=diff
==============================================================================
--- hadoop/mapreduce/trunk/conf/log4j.properties (original)
+++ hadoop/mapreduce/trunk/conf/log4j.properties Sun Jun 6 04:36:11 2010
@@ -115,3 +115,23 @@ log4j.appender.JSA.layout.ConversionPatt
log4j.appender.JSA.DatePattern=.yyyy-MM-dd
log4j.logger.org.apache.hadoop.mapred.JobInProgress$JobSummary=${hadoop.mapreduce.jobsummary.logger}
log4j.additivity.org.apache.hadoop.mapred.JobInProgress$JobSummary=false
+
+#
+# MapReduce Audit Log Appender
+#
+
+# Set the MapReduce audit log filename
+#hadoop.mapreduce.audit.log.file=hadoop-mapreduce.audit.log
+
+# Appender for AuditLogger.
+# Requires the following system properties to be set
+# - hadoop.log.dir (Hadoop Log directory)
+# - hadoop.mapreduce.audit.log.file (MapReduce audit log filename)
+
+#log4j.logger.org.apache.hadoop.mapred.AuditLogger=INFO,MRAUDIT
+#log4j.additivity.org.apache.hadoop.mapred.AuditLogger=false
+#log4j.appender.MRAUDIT=org.apache.log4j.DailyRollingFileAppender
+#log4j.appender.MRAUDIT.File=${hadoop.log.dir}/${hadoop.mapreduce.audit.log.file}
+#log4j.appender.MRAUDIT.DatePattern=.yyyy-MM-dd
+#log4j.appender.MRAUDIT.layout=org.apache.log4j.PatternLayout
+#log4j.appender.MRAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
Added: hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/AuditLogger.java
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/AuditLogger.java?rev=951815&view=auto
==============================================================================
--- hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/AuditLogger.java (added)
+++ hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/AuditLogger.java Sun Jun 6 04:36:11
2010
@@ -0,0 +1,154 @@
+/**
+ * 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.hadoop.mapred;
+
+import java.net.InetAddress;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.ipc.Server;
+
+/** Manages MapReduce audit logs. Audit logs provides information about
+ * authorization/authentication events (success/failure).
+ *
+ * Audit log format is written as key=value pairs.
+ */
+class AuditLogger {
+ private static final Log LOG = LogFactory.getLog(AuditLogger.class);
+
+ static enum Keys {USER, OPERATION, TARGET, RESULT, IP, PERMISSIONS,
+ DESCRIPTION}
+
+ static class Constants {
+ static final String SUCCESS = "SUCCESS";
+ static final String FAILURE = "FAILURE";
+ static final String KEY_VAL_SEPARATOR = "=";
+ static final char PAIR_SEPARATOR = '\t';
+
+ // Some constants used by others using AuditLogger.
+
+ // Some commonly used targets
+ static final String JOBTRACKER = "JobTracker";
+
+ // Some commonly used operations
+ static final String REFRESH_QUEUE = "REFRESH_QUEUE";
+ static final String REFRESH_NODES = "REFRESH_NODES";
+
+ // Some commonly used descriptions
+ static final String UNAUTHORIZED_USER = "Unauthorized user";
+ }
+
+ /**
+ * A helper api for creating an audit log for a successful event.
+ * This is factored out for testing purpose.
+ */
+ static String createSuccessLog(String user, String operation, String target) {
+ StringBuilder b = new StringBuilder();
+ start(Keys.USER, user, b);
+ addRemoteIP(b);
+ add(Keys.OPERATION, operation, b);
+ add(Keys.TARGET, target ,b);
+ add(Keys.RESULT, Constants.SUCCESS, b);
+ return b.toString();
+ }
+
+ /**
+ * Create a readable and parseable audit log string for a successful event.
+ *
+ * @param user User who made the service request to the JobTracker.
+ * @param operation Operation requested by the user
+ * @param target The target on which the operation is being performed. Most
+ * commonly operated targets are jobs, JobTracker, queues etc
+ *
+ * <br><br>
+ * Note that the {@link AuditLogger} uses tabs ('\t') as a key-val delimiter
+ * and hence the value fields should not contains tabs ('\t').
+ */
+ static void logSuccess(String user, String operation, String target) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info(createSuccessLog(user, operation, target));
+ }
+ }
+
+ /**
+ * A helper api for creating an audit log for a failure event.
+ * This is factored out for testing purpose.
+ */
+ static String createFailureLog(String user, String operation, String perm,
+ String target, String description) {
+ StringBuilder b = new StringBuilder();
+ start(Keys.USER, user, b);
+ addRemoteIP(b);
+ add(Keys.OPERATION, operation, b);
+ add(Keys.TARGET, target ,b);
+ add(Keys.RESULT, Constants.FAILURE, b);
+ add(Keys.DESCRIPTION, description, b);
+ add(Keys.PERMISSIONS, perm, b);
+ return b.toString();
+ }
+
+ /**
+ * Create a readable and parseable audit log string for a failed event.
+ *
+ * @param user User who made the service request to the JobTracker.
+ * @param operation Operation requested by the user
+ * @param perm Target permissions like JobACLs for jobs, QueueACLs for queues.
+ * @param target The target on which the operation is being performed. Most
+ * commonly operated targets are jobs, JobTracker, queues etc
+ * @param description Some additional information as to why the operation
+ * failed.
+ *
+ * <br><br>
+ * Note that the {@link AuditLogger} uses tabs ('\t') as a key-val delimiter
+ * and hence the value fields should not contains tabs ('\t').
+ */
+ static void logFailure(String user, String operation, String perm,
+ String target, String description) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn(createFailureLog(user, operation, perm, target, description));
+ }
+ }
+
+ /**
+ * A helper api to add remote IP address
+ */
+ static void addRemoteIP(StringBuilder b) {
+ InetAddress ip = Server.getRemoteIp();
+ // ip address can be null for testcases
+ if (ip != null) {
+ add(Keys.IP, ip.getHostAddress(), b);
+ }
+ }
+
+ /**
+ * Adds the first key-val pair to the passed builder in the following format
+ * key=value
+ */
+ static void start(Keys key, String value, StringBuilder b) {
+ b.append(key.name()).append(Constants.KEY_VAL_SEPARATOR).append(value);
+ }
+
+ /**
+ * Appends the key-val pair to the passed builder in the following format
+ * <pair-delim>key=value
+ */
+ static void add(Keys key, String value, StringBuilder b) {
+ b.append(Constants.PAIR_SEPARATOR).append(key.name())
+ .append(Constants.KEY_VAL_SEPARATOR).append(value);
+ }
+}
Modified: hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobACLsManager.java
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobACLsManager.java?rev=951815&r1=951814&r2=951815&view=diff
==============================================================================
--- hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobACLsManager.java (original)
+++ hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobACLsManager.java Sun Jun
6 04:36:11 2010
@@ -23,6 +23,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
+import org.apache.hadoop.mapred.AuditLogger.Constants;
import org.apache.hadoop.mapreduce.JobACL;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
@@ -113,34 +114,22 @@ public abstract class JobACLsManager {
JobACL jobOperation, String jobOwner, AccessControlList jobACL)
throws AccessControlException {
+ String user = callerUGI.getShortUserName();
if (!isJobLevelAuthorizationEnabled()) {
return;
}
- // Check for superusers/supergroups
- if (isSuperUserOrSuperGroup(callerUGI)) {
- LOG.info("superuser/supergroupMember "
- + callerUGI.getShortUserName() + " trying to perform "
- + jobOperation.toString() + " on " + jobId);
- return;
- }
-
- // Job-owner is always part of all the ACLs
- if (callerUGI.getShortUserName().equals(jobOwner)) {
- LOG.info("Jobowner " + callerUGI.getShortUserName()
- + " trying to perform " + jobOperation.toString() + " on "
- + jobId);
- return;
- }
-
-
- if (jobACL.isUserAllowed(callerUGI)) {
- LOG.info("Normal user " + callerUGI.getShortUserName()
- + " trying to perform " + jobOperation.toString() + " on "
- + jobId);
+ // Allow uperusers/supergroups
+ // Allow Job-owner as job's owner is always part of all the ACLs
+ if (callerUGI.getShortUserName().equals(jobOwner)
+ || isSuperUserOrSuperGroup(callerUGI)
+ || jobACL.isUserAllowed(callerUGI)) {
+ AuditLogger.logSuccess(user, jobOperation.name(), jobId.toString());
return;
}
+ AuditLogger.logFailure(user, jobOperation.name(), null, jobId.toString(),
+ Constants.UNAUTHORIZED_USER);
throw new AccessControlException(callerUGI
+ UNAUTHORIZED_JOB_ACCESS_ERROR
+ jobOperation.toString() + " on " + jobId + ". "
Modified: hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobInProgress.java
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobInProgress.java?rev=951815&r1=951814&r2=951815&view=diff
==============================================================================
--- hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobInProgress.java (original)
+++ hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobInProgress.java Sun Jun 6
04:36:11 2010
@@ -428,6 +428,8 @@ public class JobInProgress {
String desc = "The username " + conf.getUser() + " obtained from the " +
"conf doesn't match the username " + user + " the user " +
"authenticated as";
+ AuditLogger.logFailure(user, Queue.QueueOperation.SUBMIT_JOB.name(),
+ conf.getUser(), jobId.toString(), desc);
throw new IOException(desc);
}
this.priority = conf.getJobPriority();
Modified: hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobTracker.java
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobTracker.java?rev=951815&r1=951814&r2=951815&view=diff
==============================================================================
--- hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobTracker.java (original)
+++ hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/JobTracker.java Sun Jun 6 04:36:11
2010
@@ -70,6 +70,7 @@ import org.apache.hadoop.io.Text;
import org.apache.hadoop.ipc.RPC;
import org.apache.hadoop.ipc.Server;
import org.apache.hadoop.ipc.RPC.VersionMismatch;
+import org.apache.hadoop.mapred.AuditLogger.Constants;
import org.apache.hadoop.mapred.ClusterStatus.BlackListInfo;
import org.apache.hadoop.mapred.JobInProgress.KillInterruptedException;
import org.apache.hadoop.mapred.JobStatusChangeEvent.EventType;
@@ -3085,6 +3086,8 @@ public class JobTracker implements MRCon
LOG.info("Job " + jobId + " added successfully for user '"
+ job.getJobConf().getUser() + "' to queue '"
+ job.getJobConf().getQueueName() + "'");
+ AuditLogger.logSuccess(job.getUser(),
+ Queue.QueueOperation.SUBMIT_JOB.name(), jobId.toString());
return job.getStatus();
}
@@ -4114,14 +4117,18 @@ public class JobTracker implements MRCon
* Rereads the files to update the hosts and exclude lists.
*/
public synchronized void refreshNodes() throws IOException {
+ String user = UserGroupInformation.getCurrentUser().getShortUserName();
// check access
if (!isSuperUserOrSuperGroup(UserGroupInformation.getCurrentUser(), mrOwner,
supergroup)) {
- String user = UserGroupInformation.getCurrentUser().getShortUserName();
+ AuditLogger.logFailure(user, Constants.REFRESH_NODES,
+ mrOwner + " " + supergroup, Constants.JOBTRACKER,
+ Constants.UNAUTHORIZED_USER);
throw new AccessControlException(user +
" is not authorized to refresh nodes.");
}
+ AuditLogger.logSuccess(user, Constants.REFRESH_NODES, Constants.JOBTRACKER);
// call the actual api
refreshHosts();
}
Modified: hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/QueueManager.java
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/QueueManager.java?rev=951815&r1=951814&r2=951815&view=diff
==============================================================================
--- hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/QueueManager.java (original)
+++ hadoop/mapreduce/trunk/src/java/org/apache/hadoop/mapred/QueueManager.java Sun Jun 6
04:36:11 2010
@@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFac
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
+import org.apache.hadoop.mapred.AuditLogger.Constants;
import org.apache.hadoop.mapred.TaskScheduler.QueueRefresher;
import org.apache.hadoop.mapreduce.QueueState;
import org.apache.hadoop.security.UserGroupInformation;
@@ -258,6 +259,8 @@ class QueueManager {
UserGroupInformation ugi) {
Queue q = leafQueues.get(queueName);
+ String user = ugi.getShortUserName();
+ String jobId = job == null ? "-" : job.getJobID().toString();
if (q == null) {
LOG.info("Queue " + queueName + " is not present");
@@ -282,6 +285,7 @@ class QueueManager {
if (oper.isJobOwnerAllowed()) {
if (job != null
&& job.getJobConf().getUser().equals(ugi.getShortUserName())) {
+ AuditLogger.logSuccess(user, oper.name(), queueName);
return true;
}
}
@@ -291,6 +295,8 @@ class QueueManager {
queueName,
oper.getAclName()));
if (acl == null) {
+ AuditLogger.logFailure(user, oper.name(), null, queueName,
+ "Disabled queue ACLs, job : " + jobId);
return false;
}
@@ -302,6 +308,12 @@ class QueueManager {
allowed = true;
}
}
+ if (allowed) {
+ AuditLogger.logSuccess(user, oper.name(), queueName);
+ } else {
+ AuditLogger.logFailure(user, oper.name(), null, queueName,
+ Constants.UNAUTHORIZED_USER + ", job : " + jobId);
+ }
return allowed;
}
Added: hadoop/mapreduce/trunk/src/test/mapred/org/apache/hadoop/mapred/TestAuditLogger.java
URL: http://svn.apache.org/viewvc/hadoop/mapreduce/trunk/src/test/mapred/org/apache/hadoop/mapred/TestAuditLogger.java?rev=951815&view=auto
==============================================================================
--- hadoop/mapreduce/trunk/src/test/mapred/org/apache/hadoop/mapred/TestAuditLogger.java (added)
+++ hadoop/mapreduce/trunk/src/test/mapred/org/apache/hadoop/mapred/TestAuditLogger.java Sun
Jun 6 04:36:11 2010
@@ -0,0 +1,154 @@
+/**
+ * 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.hadoop.mapred;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.ipc.RPC;
+import org.apache.hadoop.ipc.Server;
+import org.apache.hadoop.ipc.TestRPC.TestImpl;
+import org.apache.hadoop.ipc.TestRPC.TestProtocol;
+import org.apache.hadoop.mapred.AuditLogger.Keys;
+import org.apache.hadoop.net.NetUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests {@link AuditLogger}.
+ */
+public class TestAuditLogger extends TestCase {
+ private static final String USER = "test";
+ private static final String OPERATION = "oper";
+ private static final String TARGET = "tgt";
+ private static final String PERM = "admin group";
+ private static final String DESC = "description of an audit log";
+
+ /**
+ * Test the AuditLog format with key-val pair.
+ */
+ public void testKeyValLogFormat() {
+ StringBuilder actLog = new StringBuilder();
+ StringBuilder expLog = new StringBuilder();
+ // add the first k=v pair and check
+ AuditLogger.start(Keys.USER, USER, actLog);
+ expLog.append("USER=test");
+ assertEquals(expLog.toString(), actLog.toString());
+
+ // append another k1=v1 pair to already added k=v and test
+ AuditLogger.add(Keys.OPERATION, OPERATION, actLog);
+ expLog.append("\tOPERATION=oper");
+ assertEquals(expLog.toString(), actLog.toString());
+
+ // append another k1=null pair and test
+ AuditLogger.add(Keys.PERMISSIONS, (String)null, actLog);
+ expLog.append("\tPERMISSIONS=null");
+ assertEquals(expLog.toString(), actLog.toString());
+
+ // now add the target and check of the final string
+ AuditLogger.add(Keys.TARGET, TARGET, actLog);
+ expLog.append("\tTARGET=tgt");
+ assertEquals(expLog.toString(), actLog.toString());
+ }
+
+ /**
+ * Test the AuditLog format for successful events.
+ */
+ private void testSuccessLogFormat(boolean checkIP) {
+ // check without the IP
+ String sLog = AuditLogger.createSuccessLog(USER, OPERATION, TARGET);
+ StringBuilder expLog = new StringBuilder();
+ expLog.append("USER=test\t");
+ if (checkIP) {
+ InetAddress ip = Server.getRemoteIp();
+ expLog.append(Keys.IP.name() + "=" + ip.getHostAddress() + "\t");
+ }
+ expLog.append("OPERATION=oper\tTARGET=tgt\tRESULT=SUCCESS");
+ assertEquals(expLog.toString(), sLog);
+
+ }
+
+ /**
+ * Test the AuditLog format for failure events.
+ */
+ private void testFailureLogFormat(boolean checkIP, String perm) {
+ String fLog =
+ AuditLogger.createFailureLog(USER, OPERATION, perm, TARGET, DESC);
+ StringBuilder expLog = new StringBuilder();
+ expLog.append("USER=test\t");
+ if (checkIP) {
+ InetAddress ip = Server.getRemoteIp();
+ expLog.append(Keys.IP.name() + "=" + ip.getHostAddress() + "\t");
+ }
+ expLog.append("OPERATION=oper\tTARGET=tgt\tRESULT=FAILURE\t");
+ expLog.append("DESCRIPTION=description of an audit log\t");
+ expLog.append("PERMISSIONS=" + perm);
+ assertEquals(expLog.toString(), fLog);
+ }
+
+ /**
+ * Test the AuditLog format for failure events.
+ */
+ private void testFailureLogFormat(boolean checkIP) {
+ testFailureLogFormat(checkIP, PERM);
+ testFailureLogFormat(checkIP, null);
+ }
+
+ /**
+ * Test {@link AuditLogger} without IP set.
+ */
+ public void testAuditLoggerWithoutIP() throws Exception {
+ // test without ip
+ testSuccessLogFormat(false);
+ testFailureLogFormat(false);
+ }
+
+ /**
+ * A special extension of {@link TestImpl} RPC server with
+ * {@link TestImpl#ping()} testing the audit logs.
+ */
+ private class MyTestRPCServer extends TestImpl {
+ @Override
+ public void ping() {
+ // test with ip set
+ testSuccessLogFormat(true);
+ testFailureLogFormat(true);
+ }
+ }
+
+ /**
+ * Test {@link AuditLogger} with IP set.
+ */
+ public void testAuditLoggerWithIP() throws Exception {
+ Configuration conf = new Configuration();
+ // start the IPC server
+ Server server = RPC.getServer(new MyTestRPCServer(), "0.0.0.0", 0, conf);
+ server.start();
+
+ InetSocketAddress addr = NetUtils.getConnectAddress(server);
+
+ // Make a client connection and test the audit log
+ TestProtocol proxy = (TestProtocol)RPC.getProxy(TestProtocol.class,
+ TestProtocol.versionID, addr, conf);
+ // Start the testcase
+ proxy.ping();
+
+ server.stop();
+ }
+}
|