HDFS-3535. Audit logging should log denied accesses. Contributed by Andy Isaacson

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1354144 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Eli Collins 2012-06-26 18:14:09 +00:00
parent f455566982
commit 0270889b4e
4 changed files with 381 additions and 14 deletions

View File

@ -244,6 +244,8 @@ Branch-2 ( Unreleased changes )
HDFS-3516. Check content-type in WebHdfsFileSystem. (szetszwo) HDFS-3516. Check content-type in WebHdfsFileSystem. (szetszwo)
HDFS-3535. Audit logging should log denied accesses. (Andy Isaacson via eli)
OPTIMIZATIONS OPTIMIZATIONS
HDFS-2982. Startup performance suffers when there are many edit log HDFS-2982. Startup performance suffers when there are many edit log

View File

@ -240,8 +240,15 @@ protected StringBuilder initialValue() {
private static final void logAuditEvent(UserGroupInformation ugi, private static final void logAuditEvent(UserGroupInformation ugi,
InetAddress addr, String cmd, String src, String dst, InetAddress addr, String cmd, String src, String dst,
HdfsFileStatus stat) { HdfsFileStatus stat) {
logAuditEvent(true, ugi, addr, cmd, src, dst, stat);
}
private static final void logAuditEvent(boolean succeeded,
UserGroupInformation ugi, InetAddress addr, String cmd, String src,
String dst, HdfsFileStatus stat) {
final StringBuilder sb = auditBuffer.get(); final StringBuilder sb = auditBuffer.get();
sb.setLength(0); sb.setLength(0);
sb.append("allowed=").append(succeeded).append("\t");
sb.append("ugi=").append(ugi).append("\t"); sb.append("ugi=").append(ugi).append("\t");
sb.append("ip=").append(addr).append("\t"); sb.append("ip=").append(addr).append("\t");
sb.append("cmd=").append(cmd).append("\t"); sb.append("cmd=").append(cmd).append("\t");
@ -1018,6 +1025,21 @@ private boolean isAccessTimeSupported() {
void setPermission(String src, FsPermission permission) void setPermission(String src, FsPermission permission)
throws AccessControlException, FileNotFoundException, SafeModeException, throws AccessControlException, FileNotFoundException, SafeModeException,
UnresolvedLinkException, IOException { UnresolvedLinkException, IOException {
try {
setPermissionInt(src, permission);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"setPermission", src, null, null);
}
throw e;
}
}
private void setPermissionInt(String src, FsPermission permission)
throws AccessControlException, FileNotFoundException, SafeModeException,
UnresolvedLinkException, IOException {
HdfsFileStatus resultingStat = null; HdfsFileStatus resultingStat = null;
writeLock(); writeLock();
try { try {
@ -1049,6 +1071,21 @@ void setPermission(String src, FsPermission permission)
void setOwner(String src, String username, String group) void setOwner(String src, String username, String group)
throws AccessControlException, FileNotFoundException, SafeModeException, throws AccessControlException, FileNotFoundException, SafeModeException,
UnresolvedLinkException, IOException { UnresolvedLinkException, IOException {
try {
setOwnerInt(src, username, group);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"setOwner", src, null, null);
}
throw e;
}
}
private void setOwnerInt(String src, String username, String group)
throws AccessControlException, FileNotFoundException, SafeModeException,
UnresolvedLinkException, IOException {
HdfsFileStatus resultingStat = null; HdfsFileStatus resultingStat = null;
writeLock(); writeLock();
try { try {
@ -1106,6 +1143,22 @@ LocatedBlocks getBlockLocations(String clientMachine, String src,
LocatedBlocks getBlockLocations(String src, long offset, long length, LocatedBlocks getBlockLocations(String src, long offset, long length,
boolean doAccessTime, boolean needBlockToken, boolean checkSafeMode) boolean doAccessTime, boolean needBlockToken, boolean checkSafeMode)
throws FileNotFoundException, UnresolvedLinkException, IOException { throws FileNotFoundException, UnresolvedLinkException, IOException {
try {
return getBlockLocationsInt(src, offset, length, doAccessTime,
needBlockToken, checkSafeMode);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"open", src, null, null);
}
throw e;
}
}
private LocatedBlocks getBlockLocationsInt(String src, long offset, long length,
boolean doAccessTime, boolean needBlockToken, boolean checkSafeMode)
throws FileNotFoundException, UnresolvedLinkException, IOException {
if (isPermissionEnabled) { if (isPermissionEnabled) {
checkPathAccess(src, FsAction.READ); checkPathAccess(src, FsAction.READ);
} }
@ -1202,6 +1255,20 @@ private LocatedBlocks getBlockLocationsUpdateTimes(String src,
*/ */
void concat(String target, String [] srcs) void concat(String target, String [] srcs)
throws IOException, UnresolvedLinkException { throws IOException, UnresolvedLinkException {
try {
concatInt(target, srcs);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getLoginUser(),
Server.getRemoteIp(),
"concat", Arrays.toString(srcs), target, null);
}
throw e;
}
}
private void concatInt(String target, String [] srcs)
throws IOException, UnresolvedLinkException {
if(FSNamesystem.LOG.isDebugEnabled()) { if(FSNamesystem.LOG.isDebugEnabled()) {
FSNamesystem.LOG.debug("concat " + Arrays.toString(srcs) + FSNamesystem.LOG.debug("concat " + Arrays.toString(srcs) +
" to " + target); " to " + target);
@ -1354,6 +1421,20 @@ private void concatInternal(String target, String [] srcs)
* written to the edits log but is not flushed. * written to the edits log but is not flushed.
*/ */
void setTimes(String src, long mtime, long atime) void setTimes(String src, long mtime, long atime)
throws IOException, UnresolvedLinkException {
try {
setTimesInt(src, mtime, atime);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"setTimes", src, null, null);
}
throw e;
}
}
private void setTimesInt(String src, long mtime, long atime)
throws IOException, UnresolvedLinkException { throws IOException, UnresolvedLinkException {
if (!isAccessTimeSupported() && atime != -1) { if (!isAccessTimeSupported() && atime != -1) {
throw new IOException("Access time for hdfs is not configured. " + throw new IOException("Access time for hdfs is not configured. " +
@ -1390,6 +1471,21 @@ void setTimes(String src, long mtime, long atime)
void createSymlink(String target, String link, void createSymlink(String target, String link,
PermissionStatus dirPerms, boolean createParent) PermissionStatus dirPerms, boolean createParent)
throws IOException, UnresolvedLinkException { throws IOException, UnresolvedLinkException {
try {
createSymlinkInt(target, link, dirPerms, createParent);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"createSymlink", link, target, null);
}
throw e;
}
}
private void createSymlinkInt(String target, String link,
PermissionStatus dirPerms, boolean createParent)
throws IOException, UnresolvedLinkException {
HdfsFileStatus resultingStat = null; HdfsFileStatus resultingStat = null;
writeLock(); writeLock();
try { try {
@ -1457,8 +1553,22 @@ private void createSymlinkInternal(String target, String link,
* @return true if successful; * @return true if successful;
* false if file does not exist or is a directory * false if file does not exist or is a directory
*/ */
boolean setReplication(final String src, final short replication boolean setReplication(final String src, final short replication)
) throws IOException { throws IOException {
try {
return setReplicationInt(src, replication);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"setReplication", src, null, null);
}
throw e;
}
}
private boolean setReplicationInt(final String src, final short replication)
throws IOException {
blockManager.verifyReplication(src, replication, null); blockManager.verifyReplication(src, replication, null);
final boolean isFile; final boolean isFile;
@ -1491,7 +1601,7 @@ boolean setReplication(final String src, final short replication
} }
return isFile; return isFile;
} }
long getPreferredBlockSize(String filename) long getPreferredBlockSize(String filename)
throws IOException, UnresolvedLinkException { throws IOException, UnresolvedLinkException {
readLock(); readLock();
@ -1537,6 +1647,24 @@ void startFile(String src, PermissionStatus permissions, String holder,
short replication, long blockSize) throws AccessControlException, short replication, long blockSize) throws AccessControlException,
SafeModeException, FileAlreadyExistsException, UnresolvedLinkException, SafeModeException, FileAlreadyExistsException, UnresolvedLinkException,
FileNotFoundException, ParentNotDirectoryException, IOException { FileNotFoundException, ParentNotDirectoryException, IOException {
try {
startFileInt(src, permissions, holder, clientMachine, flag, createParent,
replication, blockSize);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"create", src, null, null);
}
throw e;
}
}
private void startFileInt(String src, PermissionStatus permissions, String holder,
String clientMachine, EnumSet<CreateFlag> flag, boolean createParent,
short replication, long blockSize) throws AccessControlException,
SafeModeException, FileAlreadyExistsException, UnresolvedLinkException,
FileNotFoundException, ParentNotDirectoryException, IOException {
writeLock(); writeLock();
try { try {
checkOperation(OperationCategory.WRITE); checkOperation(OperationCategory.WRITE);
@ -1840,6 +1968,22 @@ LocatedBlock appendFile(String src, String holder, String clientMachine)
throws AccessControlException, SafeModeException, throws AccessControlException, SafeModeException,
FileAlreadyExistsException, FileNotFoundException, FileAlreadyExistsException, FileNotFoundException,
ParentNotDirectoryException, IOException { ParentNotDirectoryException, IOException {
try {
return appendFileInt(src, holder, clientMachine);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"append", src, null, null);
}
throw e;
}
}
private LocatedBlock appendFileInt(String src, String holder, String clientMachine)
throws AccessControlException, SafeModeException,
FileAlreadyExistsException, FileNotFoundException,
ParentNotDirectoryException, IOException {
if (!supportAppends) { if (!supportAppends) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"Append is not enabled on this NameNode. Use the " + "Append is not enabled on this NameNode. Use the " +
@ -2326,6 +2470,20 @@ boolean checkFileProgress(INodeFile v, boolean checkall) {
*/ */
@Deprecated @Deprecated
boolean renameTo(String src, String dst) boolean renameTo(String src, String dst)
throws IOException, UnresolvedLinkException {
try {
return renameToInt(src, dst);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"rename", src, dst, null);
}
throw e;
}
}
private boolean renameToInt(String src, String dst)
throws IOException, UnresolvedLinkException { throws IOException, UnresolvedLinkException {
boolean status = false; boolean status = false;
HdfsFileStatus resultingStat = null; HdfsFileStatus resultingStat = null;
@ -2437,20 +2595,35 @@ private void renameToInternal(String src, String dst,
* @see ClientProtocol#delete(String, boolean) for detailed descriptoin and * @see ClientProtocol#delete(String, boolean) for detailed descriptoin and
* description of exceptions * description of exceptions
*/ */
boolean delete(String src, boolean recursive) boolean delete(String src, boolean recursive)
throws AccessControlException, SafeModeException, throws AccessControlException, SafeModeException,
UnresolvedLinkException, IOException { UnresolvedLinkException, IOException {
if (NameNode.stateChangeLog.isDebugEnabled()) { try {
NameNode.stateChangeLog.debug("DIR* NameSystem.delete: " + src); return deleteInt(src, recursive);
} } catch (AccessControlException e) {
boolean status = deleteInternal(src, recursive, true); if (auditLog.isInfoEnabled() && isExternalInvocation()) {
if (status && auditLog.isInfoEnabled() && isExternalInvocation()) { logAuditEvent(false, UserGroupInformation.getCurrentUser(),
logAuditEvent(UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(), Server.getRemoteIp(),
"delete", src, null, null); "delete", src, null, null);
} }
return status; throw e;
} }
}
private boolean deleteInt(String src, boolean recursive)
throws AccessControlException, SafeModeException,
UnresolvedLinkException, IOException {
if (NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("DIR* NameSystem.delete: " + src);
}
boolean status = deleteInternal(src, recursive, true);
if (status && auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"delete", src, null, null);
}
return status;
}
/** /**
* Remove a file/directory from the namespace. * Remove a file/directory from the namespace.
@ -2606,6 +2779,20 @@ HdfsFileStatus getFileInfo(String src, boolean resolveLink)
*/ */
boolean mkdirs(String src, PermissionStatus permissions, boolean mkdirs(String src, PermissionStatus permissions,
boolean createParent) throws IOException, UnresolvedLinkException { boolean createParent) throws IOException, UnresolvedLinkException {
try {
return mkdirsInt(src, permissions, createParent);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"mkdirs", src, null, null);
}
throw e;
}
}
private boolean mkdirsInt(String src, PermissionStatus permissions,
boolean createParent) throws IOException, UnresolvedLinkException {
boolean status = false; boolean status = false;
if(NameNode.stateChangeLog.isDebugEnabled()) { if(NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("DIR* NameSystem.mkdirs: " + src); NameNode.stateChangeLog.debug("DIR* NameSystem.mkdirs: " + src);
@ -3057,6 +3244,21 @@ void renewLease(String holder) throws IOException {
*/ */
DirectoryListing getListing(String src, byte[] startAfter, DirectoryListing getListing(String src, byte[] startAfter,
boolean needLocation) boolean needLocation)
throws AccessControlException, UnresolvedLinkException, IOException {
try {
return getListingInt(src, startAfter, needLocation);
} catch (AccessControlException e) {
if (auditLog.isInfoEnabled() && isExternalInvocation()) {
logAuditEvent(false, UserGroupInformation.getCurrentUser(),
Server.getRemoteIp(),
"listStatus", src, null, null);
}
throw e;
}
}
private DirectoryListing getListingInt(String src, byte[] startAfter,
boolean needLocation)
throws AccessControlException, UnresolvedLinkException, IOException { throws AccessControlException, UnresolvedLinkException, IOException {
DirectoryListing dl; DirectoryListing dl;
readLock(); readLock();

View File

@ -0,0 +1,162 @@
/**
* 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.hdfs.server.namenode;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.After;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Pattern;
import org.apache.commons.logging.impl.Log4JLogger;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.hdfs.DFSConfigKeys;
import org.apache.hadoop.hdfs.DFSTestUtil;
import org.apache.hadoop.hdfs.HdfsConfiguration;
import org.apache.hadoop.hdfs.MiniDFSCluster;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.RollingFileAppender;
import org.junit.Test;
/**
* A JUnit test that audit logs are generated
*/
public class TestAuditLogs {
static final String auditLogFile = System.getProperty("test.build.dir",
"build/test") + "/audit.log";
// Pattern for:
// allowed=(true|false) ugi=name ip=/address cmd={cmd} src={path} dst=null perm=null
static final Pattern auditPattern = Pattern.compile(
"allowed=.*?\\s" +
"ugi=.*?\\s" +
"ip=/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\s" +
"cmd=.*?\\ssrc=.*?\\sdst=null\\s" +
"perm=.*?");
static final Pattern successPattern = Pattern.compile(
".*allowed=true.*");
static final String username = "bob";
static final String[] groups = { "group1" };
static final String fileName = "/srcdat";
DFSTestUtil util;
MiniDFSCluster cluster;
FileSystem fs;
String fnames[];
Configuration conf;
UserGroupInformation userGroupInfo;
@Before
public void setupCluster() throws Exception {
conf = new HdfsConfiguration();
final long precision = 1L;
conf.setLong(DFSConfigKeys.DFS_NAMENODE_ACCESSTIME_PRECISION_KEY, precision);
conf.setLong(DFSConfigKeys.DFS_BLOCKREPORT_INTERVAL_MSEC_KEY, 10000L);
util = new DFSTestUtil("TestAuditAllowed", 20, 3, 8*1024);
cluster = new MiniDFSCluster.Builder(conf).numDataNodes(4).build();
fs = cluster.getFileSystem();
util.createFiles(fs, fileName);
fnames = util.getFileNames(fileName);
util.waitReplication(fs, fileName, (short)3);
userGroupInfo = UserGroupInformation.createUserForTesting(username, groups);
}
@After
public void teardownCluster() throws Exception {
util.cleanup(fs, "/srcdat");
fs.close();
cluster.shutdown();
}
/** test that allowed operation puts proper entry in audit log */
@Test
public void testAuditAllowed() throws Exception {
final Path file = new Path(fnames[0]);
FileSystem userfs = DFSTestUtil.getFileSystemAs(userGroupInfo, conf);
setupAuditLogs();
InputStream istream = userfs.open(file);
int val = istream.read();
istream.close();
verifyAuditLogs(true);
assertTrue("failed to read from file", val > 0);
}
/** test that denied operation puts proper entry in audit log */
@Test
public void testAuditDenied() throws Exception {
final Path file = new Path(fnames[0]);
FileSystem userfs = DFSTestUtil.getFileSystemAs(userGroupInfo, conf);
fs.setPermission(file, new FsPermission((short)0600));
fs.setOwner(file, "root", null);
setupAuditLogs();
try {
userfs.open(file);
fail("open must not succeed");
} catch(AccessControlException e) {
System.out.println("got access denied, as expected.");
}
verifyAuditLogs(false);
}
/** Sets up log4j logger for auditlogs */
private void setupAuditLogs() throws IOException {
File file = new File(auditLogFile);
if (file.exists()) {
file.delete();
}
Logger logger = ((Log4JLogger) FSNamesystem.auditLog).getLogger();
logger.setLevel(Level.INFO);
PatternLayout layout = new PatternLayout("%m%n");
RollingFileAppender appender = new RollingFileAppender(layout, auditLogFile);
logger.addAppender(appender);
}
private void verifyAuditLogs(boolean expectSuccess) throws IOException {
// Turn off the logs
Logger logger = ((Log4JLogger) FSNamesystem.auditLog).getLogger();
logger.setLevel(Level.OFF);
// Ensure audit log has only one entry
BufferedReader reader = new BufferedReader(new FileReader(auditLogFile));
String line = reader.readLine();
assertNotNull(line);
assertTrue("Expected audit event not found in audit log",
auditPattern.matcher(line).matches());
assertTrue("Expected success=" + expectSuccess,
successPattern.matcher(line).matches() == expectSuccess);
assertNull("Unexpected event in audit log", reader.readLine());
}
}

View File

@ -76,8 +76,9 @@ public class TestFsck {
"build/test") + "/audit.log"; "build/test") + "/audit.log";
// Pattern for: // Pattern for:
// ugi=name ip=/address cmd=FSCK src=/ dst=null perm=null // allowed=true ugi=name ip=/address cmd=FSCK src=/ dst=null perm=null
static final Pattern fsckPattern = Pattern.compile( static final Pattern fsckPattern = Pattern.compile(
"allowed=.*?\\s" +
"ugi=.*?\\s" + "ugi=.*?\\s" +
"ip=/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\s" + "ip=/\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\s" +
"cmd=fsck\\ssrc=\\/\\sdst=null\\s" + "cmd=fsck\\ssrc=\\/\\sdst=null\\s" +