From 4cc33e5e376b86164d9ea45e036032e072f98ed8 Mon Sep 17 00:00:00 2001 From: Simbarashe Dzinamarira Date: Wed, 22 Feb 2023 13:58:44 -0800 Subject: [PATCH] HDFS-16901: RBF: Propagates real user's username via the caller context, when a proxy user is being used. (#5346) --- .../org/apache/hadoop/ipc/CallerContext.java | 1 + .../federation/router/RouterRpcClient.java | 11 +++-- .../federation/router/TestRouterRpc.java | 41 +++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/CallerContext.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/CallerContext.java index b6e6f0c57a..fec2e36f85 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/CallerContext.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/ipc/CallerContext.java @@ -49,6 +49,7 @@ public final class CallerContext { public static final String CLIENT_PORT_STR = "clientPort"; public static final String CLIENT_ID_STR = "clientId"; public static final String CLIENT_CALL_ID_STR = "clientCallId"; + public static final String REAL_USER_STR = "realUser"; /** The caller context. * diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/RouterRpcClient.java b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/RouterRpcClient.java index 62ae4b0b95..06e6443901 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/RouterRpcClient.java +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/main/java/org/apache/hadoop/hdfs/server/federation/router/RouterRpcClient.java @@ -491,7 +491,7 @@ public Object invokeMethod( + router.getRouterId()); } - addClientInfoToCallerContext(); + addClientInfoToCallerContext(ugi); Object ret = null; if (rpcMonitor != null) { @@ -627,14 +627,18 @@ public Object invokeMethod( /** * For tracking some information about the actual client. * It adds trace info "clientIp:ip", "clientPort:port", - * "clientId:id" and "clientCallId:callId" + * "clientId:id", "clientCallId:callId" and "realUser:userName" * in the caller context, removing the old values if they were * already present. */ - private void addClientInfoToCallerContext() { + private void addClientInfoToCallerContext(UserGroupInformation ugi) { CallerContext ctx = CallerContext.getCurrent(); String origContext = ctx == null ? null : ctx.getContext(); byte[] origSignature = ctx == null ? null : ctx.getSignature(); + String realUser = null; + if (ugi.getRealUser() != null) { + realUser = ugi.getRealUser().getUserName(); + } CallerContext.Builder builder = new CallerContext.Builder("", contextFieldSeparator) .append(CallerContext.CLIENT_IP_STR, Server.getRemoteAddress()) @@ -644,6 +648,7 @@ private void addClientInfoToCallerContext() { StringUtils.byteToHexString(Server.getClientId())) .append(CallerContext.CLIENT_CALL_ID_STR, Integer.toString(Server.getCallId())) + .append(CallerContext.REAL_USER_STR, realUser) .setSignature(origSignature); // Append the original caller context if (origContext != null) { diff --git a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/router/TestRouterRpc.java b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/router/TestRouterRpc.java index cd98b635b5..b2facee8c9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/router/TestRouterRpc.java +++ b/hadoop-hdfs-project/hadoop-hdfs-rbf/src/test/java/org/apache/hadoop/hdfs/server/federation/router/TestRouterRpc.java @@ -39,6 +39,7 @@ import java.io.IOException; import java.lang.reflect.Method; import java.net.URISyntaxException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -218,6 +219,14 @@ public static void globalSetUp() throws Exception { cluster.setIndependentDNs(); Configuration conf = new Configuration(); + // Setup proxy users. + conf.set("hadoop.proxyuser.testRealUser.groups", "*"); + conf.set("hadoop.proxyuser.testRealUser.hosts", "*"); + String loginUser = UserGroupInformation.getLoginUser().getUserName(); + conf.set(String.format("hadoop.proxyuser.%s.groups", loginUser), "*"); + conf.set(String.format("hadoop.proxyuser.%s.hosts", loginUser), "*"); + // Enable IP proxy users. + conf.set(DFSConfigKeys.DFS_NAMENODE_IP_PROXY_USERS, "placeholder"); conf.setInt(DFSConfigKeys.DFS_LIST_LIMIT, 5); cluster.addNamenodeOverrides(conf); // Start NNs and DNs and wait until ready @@ -2077,6 +2086,38 @@ public void testMkdirsWithCallerContext() throws IOException { assertTrue(verifyFileExists(routerFS, dirPath)); } + @Test + public void testRealUserPropagationInCallerContext() + throws IOException, InterruptedException { + GenericTestUtils.LogCapturer auditlog = + GenericTestUtils.LogCapturer.captureLogs(FSNamesystem.auditLog); + + // Current callerContext is null + assertNull(CallerContext.getCurrent()); + + UserGroupInformation loginUser = UserGroupInformation.getLoginUser(); + UserGroupInformation realUser = UserGroupInformation + .createUserForTesting("testRealUser", new String[]{"group"}); + UserGroupInformation proxyUser = UserGroupInformation + .createProxyUser("testProxyUser", realUser); + FileSystem proxyFs = proxyUser.doAs( + (PrivilegedExceptionAction) () -> router.getFileSystem()); + proxyFs.listStatus(new Path("/")); + + + final String logOutput = auditlog.getOutput(); + // Login user, which is used as the router's user, is different from the realUser. + assertNotEquals(loginUser.getUserName(), realUser.getUserName()); + // Login user is used in the audit log's ugi field. + assertTrue("The login user is the proxyUser in the UGI field", + logOutput.contains(String.format("ugi=%s (auth:PROXY) via %s (auth:SIMPLE)", + proxyUser.getUserName(), + loginUser.getUserName()))); + // Real user is added to the caller context. + assertTrue("The audit log should contain the real user.", + logOutput.contains(String.format("realUser:%s", realUser.getUserName()))); + } + @Test public void testSetBalancerBandwidth() throws Exception { long defaultBandwidth =