This is an automated email from the ASF dual-hosted git repository. rzo1 pushed a commit to branch fix/drpc-impersonation-check in repository https://gitbox.apache.org/repos/asf/storm.git
commit ebdefd9d57d9c82b23fc22c5ceb330054f10e98e Author: Richard Zowalla <[email protected]> AuthorDate: Mon Oct 19 16:37:46 2026 +0200 Apply the impersonation authorizer to DRPC requests DRPC now checks nimbus.impersonation.authorizer for requests made on behalf of another user, like Nimbus and the UI already do. The HTTP request context now carries the remote address so host lists in nimbus.impersonation.acl apply there too. Behaviour without an impersonation authorizer is unchanged. --- SECURITY.md | 2 + docs/SECURITY.md | 2 + .../auth/authorizer/ImpersonationAuthorizer.java | 9 +- .../java/org/apache/storm/daemon/drpc/DRPC.java | 41 ++++- .../org/apache/storm/daemon/drpc/DRPCTest.java | 130 ++++++++++++++ .../storm/daemon/drpc/webapp/ReqContextFilter.java | 19 +- .../apache/storm/daemon/drpc/DRPCServerTest.java | 199 +++++++++++++++++++++ 7 files changed, 392 insertions(+), 10 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 366c10271..3cd40f895 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -451,6 +451,8 @@ nimbus.impersonation.acl: [comma separated list of groups whose users impersonating_user2 is allowed to impersonate] ``` +The DRPC server applies the same `nimbus.impersonation.authorizer` and `nimbus.impersonation.acl` to requests that impersonate another user (for example through the `doAsUser` HTTP header or query parameter), so the DRPC server should be configured with the same settings (including `storm.group.mapping.service`). + To support the oozie use-case, the following config can be supplied: ```yaml nimbus.impersonation.acl: diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 36154271f..ebf233d46 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -620,6 +620,8 @@ nimbus.impersonation.acl: [comma separated list of groups whose users impersonating_user2 is allowed to impersonate] ``` +The DRPC server applies the same `nimbus.impersonation.authorizer` and `nimbus.impersonation.acl` to requests that impersonate another user (for example through the `doAsUser` HTTP header or query parameter), so the DRPC server should be configured with the same settings (including `storm.group.mapping.service`). + To support the oozie use case following config can be supplied: ```yaml nimbus.impersonation.acl: diff --git a/storm-client/src/jvm/org/apache/storm/security/auth/authorizer/ImpersonationAuthorizer.java b/storm-client/src/jvm/org/apache/storm/security/auth/authorizer/ImpersonationAuthorizer.java index 22aa4e166..63d0294e1 100644 --- a/storm-client/src/jvm/org/apache/storm/security/auth/authorizer/ImpersonationAuthorizer.java +++ b/storm-client/src/jvm/org/apache/storm/security/auth/authorizer/ImpersonationAuthorizer.java @@ -125,8 +125,13 @@ public class ImpersonationAuthorizer implements IAuthorizer { } private boolean isAllowedToImpersonateFromHost(Set<String> authorizedHosts, InetAddress remoteAddress) { - return authorizedHosts.contains(WILD_CARD) - || authorizedHosts.contains(remoteAddress.getCanonicalHostName()) + if (authorizedHosts.contains(WILD_CARD)) { + return true; + } + if (remoteAddress == null) { + return false; + } + return authorizedHosts.contains(remoteAddress.getCanonicalHostName()) || authorizedHosts.contains(remoteAddress.getHostName()) || authorizedHosts.contains(remoteAddress.getHostAddress()); } diff --git a/storm-server/src/main/java/org/apache/storm/daemon/drpc/DRPC.java b/storm-server/src/main/java/org/apache/storm/daemon/drpc/DRPC.java index df9ed364f..6b58e9c1b 100644 --- a/storm-server/src/main/java/org/apache/storm/daemon/drpc/DRPC.java +++ b/storm-server/src/main/java/org/apache/storm/daemon/drpc/DRPC.java @@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import org.apache.storm.Config; import org.apache.storm.DaemonConfig; import org.apache.storm.daemon.StormCommon; import org.apache.storm.generated.AuthorizationException; @@ -76,14 +77,21 @@ public class DRPC implements AutoCloseable { private final Timer timer = new Timer("DRPC-CLEANUP-TIMER", true); private final AtomicLong ctr = new AtomicLong(0); private final IAuthorizer auth; + private final IAuthorizer impersonationAuth; public DRPC(StormMetricsRegistry metricsRegistry, Map<String, Object> conf) { this(metricsRegistry, mkAuthorizationHandler((String) conf.get(DaemonConfig.DRPC_AUTHORIZER), conf), + mkAuthorizationHandler((String) conf.get(DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER), conf), ObjectReader.getInt(conf.get(DaemonConfig.DRPC_REQUEST_TIMEOUT_SECS), 600) * 1000); } public DRPC(StormMetricsRegistry metricsRegistry, IAuthorizer auth, long timeoutMs) { + this(metricsRegistry, auth, null, timeoutMs); + } + + public DRPC(StormMetricsRegistry metricsRegistry, IAuthorizer auth, IAuthorizer impersonationAuth, long timeoutMs) { this.auth = auth; + this.impersonationAuth = impersonationAuth; this.meterServerTimedOut = metricsRegistry.registerMeter("drpc:num-server-timedout-requests"); this.meterExecuteCalls = metricsRegistry.registerMeter("drpc:num-execute-calls"); this.meterResultCalls = metricsRegistry.registerMeter("drpc:num-result-calls"); @@ -117,17 +125,36 @@ public class DRPC implements AutoCloseable { @VisibleForTesting static void checkAuthorization(ReqContext reqContext, IAuthorizer auth, String operation, String function) throws AuthorizationException { - checkAuthorization(reqContext, auth, operation, function, true); + checkAuthorization(reqContext, auth, null, operation, function, true); } - private static void checkAuthorization(ReqContext reqContext, IAuthorizer auth, String operation, String function, boolean log) - throws AuthorizationException { + private static void checkAuthorization(ReqContext reqContext, IAuthorizer auth, IAuthorizer impersonationAuth, String operation, + String function, boolean log) throws AuthorizationException { if (reqContext != null && log) { logAccess(reqContext, operation, function); } + Map<String, Object> map = new HashMap<>(); + map.put(DRPCAuthorizerBase.FUNCTION_NAME, function); + if (reqContext != null && reqContext.isImpersonating()) { + if (log) { + LOG.info("principal: {} is trying to impersonate principal: {}", reqContext.realPrincipal(), reqContext.principal()); + } else { + LOG.debug("principal: {} is trying to impersonate principal: {}", reqContext.realPrincipal(), reqContext.principal()); + } + if (impersonationAuth == null) { + LOG.warn("impersonation attempt but {} has no authorizer configured, so it is not checked. " + + "See the impersonation section of the documentation to learn how to configure it.", + DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER); + } else if (!impersonationAuth.permit(reqContext, operation, map)) { + String fromHost = reqContext.remoteAddress() == null ? "" : " from host " + reqContext.remoteAddress(); + throw new WrappedAuthorizationException("principal " + reqContext.realPrincipal() + + " is not authorized to impersonate principal " + reqContext.principal() + + fromHost + + " for DRPC request '" + operation + "'." + + " See " + Config.NIMBUS_IMPERSONATION_ACL + " to configure impersonation."); + } + } if (auth != null) { - Map<String, Object> map = new HashMap<>(); - map.put(DRPCAuthorizerBase.FUNCTION_NAME, function); if (!auth.permit(reqContext, operation, map)) { Principal principal = reqContext.principal(); String user = (principal != null) ? principal.getName() : "unknown"; @@ -137,11 +164,11 @@ public class DRPC implements AutoCloseable { } private void checkAuthorization(String operation, String function) throws AuthorizationException { - checkAuthorization(ReqContext.context(), auth, operation, function); + checkAuthorization(ReqContext.context(), auth, impersonationAuth, operation, function, true); } private void checkAuthorizationNoLog(String operation, String function) throws AuthorizationException { - checkAuthorization(ReqContext.context(), auth, operation, function, false); + checkAuthorization(ReqContext.context(), auth, impersonationAuth, operation, function, false); } private void cleanup(String id) { diff --git a/storm-server/src/test/java/org/apache/storm/daemon/drpc/DRPCTest.java b/storm-server/src/test/java/org/apache/storm/daemon/drpc/DRPCTest.java index a9fd7f1d1..4c6057f6b 100644 --- a/storm-server/src/test/java/org/apache/storm/daemon/drpc/DRPCTest.java +++ b/storm-server/src/test/java/org/apache/storm/daemon/drpc/DRPCTest.java @@ -18,6 +18,7 @@ package org.apache.storm.daemon.drpc; +import java.net.InetAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -33,6 +34,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import javax.security.auth.Subject; import org.apache.storm.Config; +import org.apache.storm.DaemonConfig; import org.apache.storm.generated.AuthorizationException; import org.apache.storm.generated.DRPCExceptionType; import org.apache.storm.generated.DRPCExecutionException; @@ -43,9 +45,11 @@ import org.apache.storm.security.auth.SingleUserPrincipal; import org.apache.storm.security.auth.authorizer.DRPCSimpleACLAuthorizer; import org.apache.storm.security.auth.authorizer.DRPCSimpleACLAuthorizer.AclFunctionEntry; import org.apache.storm.security.auth.authorizer.DenyAuthorizer; +import org.apache.storm.security.auth.authorizer.ImpersonationAuthorizer; import org.apache.storm.utils.Time; import org.awaitility.Awaitility; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -72,6 +76,11 @@ public class DRPCTest { exec.shutdownNow(); } + @AfterEach + public void resetContext() { + ReqContext.reset(); + } + public static DRPCRequest getNextAvailableRequest(DRPC server, String func) { AtomicReference<DRPCRequest> result = new AtomicReference<>(); Awaitility.await("DRPC request on " + func) @@ -359,6 +368,127 @@ public class DRPCTest { DRPC.checkAuthorization(other, auth, "execute", "not_jump"); } + /** + * Only "alice" may execute the function "sensitive". + */ + public static class SensitiveFunctionAclAuthorizer extends DRPCSimpleACLAuthorizer { + @Override + protected Map<String, AclFunctionEntry> readAclFromConfig() { + Map<String, AclFunctionEntry> acl = new HashMap<>(); + acl.put("sensitive", new AclFunctionEntry(Collections.singletonList("alice"), "sensitive_topo")); + return acl; + } + } + + private static Map<String, Object> impersonationConf(boolean withImpersonationAuthorizer, boolean grantBob) { + return impersonationConf(withImpersonationAuthorizer, grantBob, "*"); + } + + private static Map<String, Object> impersonationConf(boolean withImpersonationAuthorizer, boolean grantBob, String bobHost) { + Map<String, Object> conf = new HashMap<>(); + conf.put(DaemonConfig.DRPC_AUTHORIZER, SensitiveFunctionAclAuthorizer.class.getName()); + conf.put(Config.DRPC_AUTHORIZER_ACL_STRICT, true); + conf.put(Config.STORM_PRINCIPAL_TO_LOCAL_PLUGIN, DefaultPrincipalToLocal.class.getName()); + if (withImpersonationAuthorizer) { + conf.put(DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER, ImpersonationAuthorizer.class.getName()); + Map<String, Map<String, List<String>>> impersonationAcl = new HashMap<>(); + if (grantBob) { + Map<String, List<String>> bobAcl = new HashMap<>(); + bobAcl.put("groups", Collections.singletonList("*")); + bobAcl.put("hosts", Collections.singletonList(bobHost)); + impersonationAcl.put("bob", bobAcl); + } + conf.put(Config.NIMBUS_IMPERSONATION_ACL, impersonationAcl); + } + return conf; + } + + /** + * Sets up the current request context for {@code user}, optionally as {@code realUser} impersonating {@code user}. + */ + private static void setCurrentUser(String user, String realUser) { + setCurrentUser(user, realUser, InetAddress.getLoopbackAddress()); + } + + private static void setCurrentUser(String user, String realUser, InetAddress remoteAddress) { + Subject subject = new Subject(); + subject.getPrincipals().add(new SingleUserPrincipal(user)); + ReqContext context = ReqContext.context(); + context.setSubject(subject); + context.setRealPrincipal(realUser == null ? null : new SingleUserPrincipal(realUser)); + context.setRemoteAddress(remoteAddress); + } + + private static void executeSensitive(DRPC server) throws Exception { + server.execute("sensitive", "args", BlockingOutstandingRequest.FACTORY); + } + + @Test + public void testImpersonationDeniedByImpersonationAuthorizer() throws Exception { + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(true, false))) { + setCurrentUser("alice", "bob"); + assertTrue(ReqContext.context().isImpersonating()); + assertThrows(() -> executeSensitive(server), AuthorizationException.class); + assertEquals(0, server.getNumTrackedFunctions()); + } + } + + @Test + public void testImpersonationPermittedByImpersonationAuthorizer() throws Exception { + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(true, true))) { + setCurrentUser("alice", "bob"); + executeSensitive(server); + assertEquals(1, server.getNumTrackedFunctions()); + } + } + + @Test + public void testNoImpersonationAllowedUser() throws Exception { + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(true, false))) { + setCurrentUser("alice", null); + executeSensitive(server); + assertEquals(1, server.getNumTrackedFunctions()); + } + } + + @Test + public void testNoImpersonationOtherUserDenied() throws Exception { + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(true, true))) { + setCurrentUser("bob", null); + assertThrows(() -> executeSensitive(server), AuthorizationException.class); + assertEquals(0, server.getNumTrackedFunctions()); + } + } + + @Test + public void testImpersonationFromListedHostPermitted() throws Exception { + InetAddress loopback = InetAddress.getLoopbackAddress(); + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(true, true, loopback.getHostAddress()))) { + setCurrentUser("alice", "bob", loopback); + executeSensitive(server); + assertEquals(1, server.getNumTrackedFunctions()); + } + } + + @Test + public void testImpersonationWithUnknownRemoteAddressDeniedForHostList() throws Exception { + InetAddress loopback = InetAddress.getLoopbackAddress(); + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(true, true, loopback.getHostAddress()))) { + setCurrentUser("alice", "bob", null); + assertThrows(() -> executeSensitive(server), AuthorizationException.class); + assertEquals(0, server.getNumTrackedFunctions()); + } + } + + @Test + public void testImpersonationWithoutImpersonationAuthorizer() throws Exception { + try (DRPC server = new DRPC(new StormMetricsRegistry(), impersonationConf(false, false))) { + setCurrentUser("alice", "bob"); + executeSensitive(server); + assertEquals(1, server.getNumTrackedFunctions()); + } + } + public interface ThrowStuff { void run() throws Exception; } diff --git a/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/webapp/ReqContextFilter.java b/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/webapp/ReqContextFilter.java index 8f9ff9b5a..0137bef4c 100644 --- a/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/webapp/ReqContextFilter.java +++ b/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/webapp/ReqContextFilter.java @@ -30,6 +30,8 @@ import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.ext.Provider; import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; import org.apache.storm.security.auth.IHttpCredentialsPlugin; import org.apache.storm.security.auth.ReqContext; @@ -48,8 +50,23 @@ public class ReqContextFilter implements Filter { * @param request the request to populate */ public void populateContext(HttpServletRequest request) { + ReqContext context = ReqContext.context(); if (httpCredsHandler != null) { - httpCredsHandler.populateContext(ReqContext.context(), request); + httpCredsHandler.populateContext(context, request); + } + context.setRemoteAddress(getRemoteAddress(request)); + } + + private static InetAddress getRemoteAddress(HttpServletRequest request) { + String remoteAddr = request.getRemoteAddr(); + if (remoteAddr == null || remoteAddr.isEmpty()) { + return null; + } + try { + // The servlet container reports an IP address literal, so no name lookup happens here. + return InetAddress.getByName(remoteAddr); + } catch (UnknownHostException e) { + return null; } } diff --git a/storm-webapp/src/test/java/org/apache/storm/daemon/drpc/DRPCServerTest.java b/storm-webapp/src/test/java/org/apache/storm/daemon/drpc/DRPCServerTest.java index 7e446d91f..8ed64e596 100644 --- a/storm-webapp/src/test/java/org/apache/storm/daemon/drpc/DRPCServerTest.java +++ b/storm-webapp/src/test/java/org/apache/storm/daemon/drpc/DRPCServerTest.java @@ -19,20 +19,38 @@ package org.apache.storm.daemon.drpc; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.fail; import org.junit.jupiter.api.AfterAll; +import java.io.IOException; import java.io.InputStream; +import java.net.HttpURLConnection; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; import org.apache.storm.Config; import org.apache.storm.DaemonConfig; @@ -40,7 +58,13 @@ import org.apache.storm.drpc.DRPCInvocationsClient; import org.apache.storm.generated.DRPCExecutionException; import org.apache.storm.generated.DRPCRequest; import org.apache.storm.metric.StormMetricsRegistry; +import org.apache.storm.security.auth.DefaultHttpCredentialsPlugin; +import org.apache.storm.security.auth.DefaultPrincipalToLocal; +import org.apache.storm.security.auth.ReqContext; import org.apache.storm.security.auth.SimpleTransportPlugin; +import org.apache.storm.security.auth.SingleUserPrincipal; +import org.apache.storm.security.auth.authorizer.DRPCSimpleACLAuthorizer; +import org.apache.storm.security.auth.authorizer.ImpersonationAuthorizer; import org.apache.storm.utils.DRPCClient; import org.junit.jupiter.api.Test; import org.slf4j.Logger; @@ -191,4 +215,179 @@ public class DRPCServerTest { } } } + + /** + * Authenticates every HTTP request as the user given in the "user" init parameter. + */ + public static class FixedUserFilter implements Filter { + private String user; + + @Override + public void init(FilterConfig config) { + user = config.getInitParameter("user"); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + chain.doFilter(new HttpServletRequestWrapper((HttpServletRequest) request) { + @Override + public Principal getUserPrincipal() { + return new SingleUserPrincipal(user); + } + + @Override + public String getRemoteUser() { + return user; + } + }, response); + } + + @Override + public void destroy() { + } + } + + /** + * Only "alice" may execute the function "sensitive"; any client may fetch and answer requests. + */ + public static class SensitiveFunctionAclAuthorizer extends DRPCSimpleACLAuthorizer { + @Override + protected Map<String, AclFunctionEntry> readAclFromConfig() { + Map<String, AclFunctionEntry> acl = new HashMap<>(); + acl.put("sensitive", new AclFunctionEntry(Collections.singletonList("alice"), "sensitive_topo")); + return acl; + } + + @Override + protected boolean permitInvocationRequest(ReqContext context, String operation, Map<String, Object> params) { + return true; + } + } + + private Map<String, Object> getImpersonationConf(String httpUser) { + return getImpersonationConf(httpUser, new HashMap<>()); + } + + private Map<String, Object> getImpersonationConf(String httpUser, Map<String, Map<String, List<String>>> impersonationAcl) { + Map<String, Object> conf = getConf(0, 0, 0); + conf.put(DaemonConfig.DRPC_HTTP_FILTER, FixedUserFilter.class.getName()); + conf.put(DaemonConfig.DRPC_HTTP_FILTER_PARAMS, Collections.singletonMap("user", httpUser)); + conf.put(DaemonConfig.DRPC_HTTP_CREDS_PLUGIN, DefaultHttpCredentialsPlugin.class.getName()); + conf.put(DaemonConfig.DRPC_AUTHORIZER, SensitiveFunctionAclAuthorizer.class.getName()); + conf.put(Config.DRPC_AUTHORIZER_ACL_STRICT, true); + conf.put(Config.STORM_PRINCIPAL_TO_LOCAL_PLUGIN, DefaultPrincipalToLocal.class.getName()); + conf.put(DaemonConfig.NIMBUS_IMPERSONATION_AUTHORIZER, ImpersonationAuthorizer.class.getName()); + conf.put(Config.NIMBUS_IMPERSONATION_ACL, impersonationAcl); + conf.put(DaemonConfig.DRPC_REQUEST_TIMEOUT_SECS, 30); + return conf; + } + + private static Map<String, Map<String, List<String>>> impersonationAclFor(String user, List<String> hosts) { + Map<String, List<String>> entry = new HashMap<>(); + entry.put("groups", Collections.singletonList("*")); + entry.put("hosts", hosts); + return Collections.singletonMap(user, entry); + } + + private static HttpResult doGetWithStatus(int port, String pathAndQuery) { + try { + URL url = new URL("http://localhost:" + port + pathAndQuery); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + try { + conn.setReadTimeout(10_000); + int status = conn.getResponseCode(); + String body = ""; + try (InputStream in = status < 400 ? conn.getInputStream() : conn.getErrorStream()) { + if (in != null) { + body = new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + } + return new HttpResult(status, body); + } finally { + conn.disconnect(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static final class HttpResult { + private final int status; + private final String body; + + private HttpResult(int status, String body) { + this.status = status; + this.body = body; + } + } + + /** + * Sends the GET request and answers any request for "sensitive" that reaches the DRPC queue. + */ + private HttpResult runSensitiveRequest(Map<String, Object> conf, String query) throws Exception { + try (DRPCServer server = new DRPCServer(conf, new StormMetricsRegistry())) { + server.start(); + //TODO need a better way to do this + Thread.sleep(2000); + try (DRPCInvocationsClient invoke = new DRPCInvocationsClient(conf, "localhost", server.getDrpcInvokePort())) { + Future<HttpResult> found = exec.submit( + () -> doGetWithStatus(server.getHttpServerPort(), "/drpc/sensitive/args" + query)); + long timeout = System.currentTimeMillis() + 5_000; + while (!found.isDone() && System.currentTimeMillis() < timeout) { + DRPCRequest request = invoke.getClient().fetchRequest("sensitive"); + if (request != null && request.get_request_id() != null && !request.get_request_id().isEmpty()) { + invoke.result(request.get_request_id(), "sensitive-result"); + break; + } + Thread.sleep(1); + } + try { + return found.get(5, TimeUnit.SECONDS); + } catch (TimeoutException e) { + found.cancel(true); + fail("Test timed out waiting for the response to the request on sensitive"); + return null; + } + } + } + } + + @Test + public void testHttpImpersonationDeniedByImpersonationAuthorizer() throws Exception { + HttpResult result = runSensitiveRequest(getImpersonationConf("bob"), "?doAsUser=alice"); + assertEquals(401, result.status); + assertNotEquals("sensitive-result", result.body); + } + + @Test + public void testHttpImpersonationFromListedHostPermitted() throws Exception { + Map<String, Map<String, List<String>>> acl = + impersonationAclFor("bob", Arrays.asList("localhost", "127.0.0.1", "0:0:0:0:0:0:0:1")); + HttpResult result = runSensitiveRequest(getImpersonationConf("bob", acl), "?doAsUser=alice"); + assertEquals(200, result.status); + assertEquals("sensitive-result", result.body); + } + + @Test + public void testHttpImpersonationFromUnlistedHostDenied() throws Exception { + Map<String, Map<String, List<String>>> acl = impersonationAclFor("bob", Collections.singletonList("192.0.2.1")); + HttpResult result = runSensitiveRequest(getImpersonationConf("bob", acl), "?doAsUser=alice"); + assertEquals(401, result.status); + assertNotEquals("sensitive-result", result.body); + } + + @Test + public void testHttpAllowedUserWithoutImpersonation() throws Exception { + HttpResult result = runSensitiveRequest(getImpersonationConf("alice"), ""); + assertEquals(200, result.status); + assertEquals("sensitive-result", result.body); + } + + @Test + public void testHttpOtherUserWithoutImpersonationDenied() throws Exception { + HttpResult result = runSensitiveRequest(getImpersonationConf("bob"), ""); + assertEquals(401, result.status); + assertNotEquals("sensitive-result", result.body); + } }
