From 21717db6a02b4e619033bfab8bd82a0f52c892c4 Mon Sep 17 00:00:00 2001 From: Sunil G Date: Sun, 18 Mar 2018 11:00:30 +0530 Subject: [PATCH] YARN-8028. Support authorizeUserAccessToQueue in RMWebServices. Contributed by Wangda Tan. --- .../security/QueueACLsManager.java | 1 - .../resourcemanager/webapp/RMWSConsts.java | 8 ++ .../webapp/RMWebServiceProtocol.java | 18 ++++ .../resourcemanager/webapp/RMWebServices.java | 55 +++++++++++- .../webapp/TestRMWebServices.java | 84 +++++++++++++++++++ .../webapp/DefaultRequestInterceptorREST.java | 9 ++ .../webapp/FederationInterceptorREST.java | 6 ++ .../router/webapp/RouterWebServices.java | 17 ++++ .../webapp/MockRESTRequestInterceptor.java | 6 ++ .../PassThroughRESTRequestInterceptor.java | 8 ++ 10 files changed, 208 insertions(+), 4 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/QueueACLsManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/QueueACLsManager.java index 530cb25bf6c..4c22a55b269 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/QueueACLsManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/security/QueueACLsManager.java @@ -114,7 +114,6 @@ public class QueueACLsManager { // version is added for the moving the application case. The check has // extra logging to distinguish between the queue not existing in the // application move request case and the real access denied case. - if (scheduler instanceof CapacityScheduler) { CSQueue queue = ((CapacityScheduler) scheduler).getQueue(targetQueue); if (queue == null) { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWSConsts.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWSConsts.java index 5a945daf864..29ae81b6bea 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWSConsts.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWSConsts.java @@ -174,6 +174,12 @@ public final class RMWSConsts { public static final String GET_CONTAINER = "/apps/{appid}/appattempts/{appattemptid}/containers/{containerid}"; + /** + * Path for {code checkUserAccessToQueue#} + */ + public static final String CHECK_USER_ACCESS_TO_QUEUE = + "/queues/{queue}/access"; + // ----------------QueryParams for RMWebServiceProtocol---------------- public static final String TIME = "time"; @@ -183,6 +189,7 @@ public final class RMWSConsts { public static final String FINAL_STATUS = "finalStatus"; public static final String USER = "user"; public static final String QUEUE = "queue"; + public static final String QUEUES = "queues"; public static final String LIMIT = "limit"; public static final String STARTED_TIME_BEGIN = "startedTimeBegin"; public static final String STARTED_TIME_END = "startedTimeEnd"; @@ -209,6 +216,7 @@ public final class RMWSConsts { public static final String GET_LABELS = "get-labels"; public static final String DESELECTS = "deSelects"; public static final String CONTAINERS = "containers"; + public static final String QUEUE_ACL_TYPE = "queue-acl-type"; private RMWSConsts() { // not called diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServiceProtocol.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServiceProtocol.java index 062ca4c8408..423c4e1d1a5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServiceProtocol.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServiceProtocol.java @@ -658,4 +658,22 @@ public interface RMWebServiceProtocol { * @return all the attempts info for a specific application */ AppAttemptsInfo getAppAttempts(HttpServletRequest hsr, String appId); + + /** + * This method verifies if an user has access to a specified queue. + * + * @return Response containing the status code. + * + * @param queue queue + * @param username user + * @param queueAclType acl type of queue, it could be + * SUBMIT_APPLICATIONS/ADMINISTER_QUEUE + * @param hsr request + * + * @throws AuthorizationException if the user is not authorized to invoke this + * method. + */ + Response checkUserAccessToQueue(String queue, String username, + String queueAclType, HttpServletRequest hsr) + throws AuthorizationException; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java index c5d52854717..c40e8be947b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/RMWebServices.java @@ -55,8 +55,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; -import com.google.common.base.Joiner; -import org.apache.commons.codec.binary.Base64; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; @@ -140,7 +139,6 @@ import org.apache.hadoop.yarn.server.resourcemanager.scheduler.MutableConfigurat import org.apache.hadoop.yarn.server.resourcemanager.scheduler.activities.ActivitiesManager; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.YarnScheduler; -import org.apache.hadoop.yarn.server.resourcemanager.scheduler.activities.ActivitiesManager; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CSQueue; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.common.fica.FiCaSchedulerNode; @@ -2520,4 +2518,55 @@ public class RMWebServices extends WebServices implements RMWebServiceProtocol { .build(); } } + + @GET + @Path(RMWSConsts.CHECK_USER_ACCESS_TO_QUEUE) + @Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8, + MediaType.APPLICATION_XML + "; " + JettyUtils.UTF_8 }) + public Response checkUserAccessToQueue( + @PathParam(RMWSConsts.QUEUE) String queue, + @QueryParam(RMWSConsts.USER) String username, + @QueryParam(RMWSConsts.QUEUE_ACL_TYPE) + @DefaultValue("SUBMIT_APPLICATIONS") String queueAclType, + @Context HttpServletRequest hsr) throws AuthorizationException { + init(); + + // Check if the specified queue acl is valid. + QueueACL queueACL; + try { + queueACL = QueueACL.valueOf(queueAclType); + } catch (IllegalArgumentException e) { + return Response.status(Status.BAD_REQUEST).entity( + "Specified queueAclType=" + queueAclType + + " is not a valid type, valid queue acl types={" + + "SUBMIT_APPLICATIONS/ADMINISTER_QUEUE}").build(); + } + + // For the user who invokes this REST call, he/she should have admin access + // to the queue. Otherwise we will reject the call. + UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr, true); + if (callerUGI != null && !this.rm.getResourceScheduler().checkAccess( + callerUGI, QueueACL.ADMINISTER_QUEUE, queue)) { + return Response.status(Status.FORBIDDEN).entity( + "User=" + callerUGI.getUserName() + " doesn't haven access to queue=" + + queue + " so it cannot check ACLs for other users.") + .build(); + } + + // Create UGI for the to-be-checked user. + UserGroupInformation user = UserGroupInformation.createRemoteUser(username); + if (user == null) { + return Response.status(Status.FORBIDDEN).entity( + "Failed to retrieve UserGroupInformation for user=" + username) + .build(); + } + + if (!this.rm.getResourceScheduler().checkAccess(user, queueACL, queue)) { + return Response.status(Status.FORBIDDEN).entity( + "User=" + username + " doesn't have access to queue=" + queue + + " with acl-type=" + queueAclType).build(); + } + + return Response.status(Status.OK).build(); + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java index f93a3fc3540..c8bbb5c4510 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/TestRMWebServices.java @@ -39,15 +39,18 @@ import javax.ws.rs.core.MediaType; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import com.google.common.collect.ImmutableSet; import org.apache.commons.io.FileUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.http.JettyUtils; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.service.Service.STATE; import org.apache.hadoop.util.VersionInfo; import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationsRequest; import org.apache.hadoop.yarn.api.protocolrecords.GetApplicationsResponse; import org.apache.hadoop.yarn.api.records.ApplicationId; import org.apache.hadoop.yarn.api.records.ApplicationReport; +import org.apache.hadoop.yarn.api.records.QueueACL; import org.apache.hadoop.yarn.api.records.QueueState; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.server.resourcemanager.ClientRMService; @@ -72,6 +75,8 @@ import org.apache.hadoop.yarn.webapp.JerseyTestBase; import org.apache.hadoop.yarn.webapp.WebServicesTestUtils; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; +import org.eclipse.jetty.server.Response; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -752,4 +757,83 @@ public class TestRMWebServices extends JerseyTestBase { tickcount--; } } + + private HttpServletRequest mockHttpServletRequestByUserName(String username) { + HttpServletRequest mockHsr = mock(HttpServletRequest.class); + when(mockHsr.getRemoteUser()).thenReturn(username); + Principal principal = mock(Principal.class); + when(principal.getName()).thenReturn(username); + when(mockHsr.getUserPrincipal()).thenReturn(principal); + return mockHsr; + } + + @Test + public void testCheckUserAccessToQueue() throws Exception { + + ResourceManager mockRM = mock(ResourceManager.class); + Configuration conf = new YarnConfiguration(); + + // Inject a mock scheduler implementation. + // Only admin user has ADMINISTER_QUEUE access. + // For SUBMIT_APPLICATION ACL, both of admin/yarn user have acess + ResourceScheduler mockScheduler = new FifoScheduler() { + @Override + public synchronized boolean checkAccess(UserGroupInformation callerUGI, + QueueACL acl, String queueName) { + if (acl == QueueACL.ADMINISTER_QUEUE) { + if (callerUGI.getUserName().equals("admin")) { + return true; + } + } else { + if (ImmutableSet.of("admin", "yarn").contains(callerUGI.getUserName())) { + return true; + } + } + return false; + } + }; + + when(mockRM.getResourceScheduler()).thenReturn(mockScheduler); + + RMWebServices webSvc = + new RMWebServices(mockRM, conf, mock(HttpServletResponse.class)); + + // Case 1: Only queue admin user can access other user's information + HttpServletRequest mockHsr = mockHttpServletRequestByUserName("non-admin"); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "jack", + QueueACL.SUBMIT_APPLICATIONS.name(), mockHsr).getStatus(), + Response.SC_FORBIDDEN); + + // Case 2: request an unknown ACL causes BAD_REQUEST + mockHsr = mockHttpServletRequestByUserName("admin"); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "jack", + "XYZ_ACL", mockHsr).getStatus(), Response.SC_BAD_REQUEST); + + // Case 3: get FORBIDDEN for rejected ACL + mockHsr = mockHttpServletRequestByUserName("admin"); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "jack", + QueueACL.SUBMIT_APPLICATIONS.name(), mockHsr).getStatus(), + Response.SC_FORBIDDEN); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "jack", + QueueACL.ADMINISTER_QUEUE.name(), mockHsr).getStatus(), + Response.SC_FORBIDDEN); + + // Case 4: get OK for listed ACLs + mockHsr = mockHttpServletRequestByUserName("admin"); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "admin", + QueueACL.SUBMIT_APPLICATIONS.name(), mockHsr).getStatus(), + Response.SC_OK); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "admin", + QueueACL.ADMINISTER_QUEUE.name(), mockHsr).getStatus(), + Response.SC_OK); + + // Case 5: get OK only for SUBMIT_APP acl for "yarn" user + mockHsr = mockHttpServletRequestByUserName("admin"); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "yarn", + QueueACL.SUBMIT_APPLICATIONS.name(), mockHsr).getStatus(), + Response.SC_OK); + Assert.assertEquals(webSvc.checkUserAccessToQueue("queue", "yarn", + QueueACL.ADMINISTER_QUEUE.name(), mockHsr).getStatus(), + Response.SC_FORBIDDEN); + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/DefaultRequestInterceptorREST.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/DefaultRequestInterceptorREST.java index 72ed02fd9a8..f2edd665f2e 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/DefaultRequestInterceptorREST.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/DefaultRequestInterceptorREST.java @@ -470,6 +470,15 @@ public class DefaultRequestInterceptorREST null, null); } + @Override + public Response checkUserAccessToQueue(String queue, String username, + String queueAclType, HttpServletRequest hsr) { + return RouterWebServiceUtil.genericForward(webAppAddress, hsr, + Response.class, HTTPMethods.GET, + RMWSConsts.RM_WEB_SERVICE_PATH + RMWSConsts.QUEUES + "/" + queue + + "/access", null, null); + } + @Override public AppAttemptInfo getAppAttempt(HttpServletRequest req, HttpServletResponse res, String appId, String appAttemptId) { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/FederationInterceptorREST.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/FederationInterceptorREST.java index 626d794c1e5..7c703c4a3df 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/FederationInterceptorREST.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/FederationInterceptorREST.java @@ -1183,6 +1183,12 @@ public class FederationInterceptorREST extends AbstractRESTRequestInterceptor { throw new NotImplementedException(); } + @Override + public Response checkUserAccessToQueue(String queue, String username, + String queueAclType, HttpServletRequest hsr) { + throw new NotImplementedException(); + } + @Override public AppAttemptInfo getAppAttempt(HttpServletRequest req, HttpServletResponse res, String appId, String appAttemptId) { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/RouterWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/RouterWebServices.java index b3272527ab6..1eba5864997 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/RouterWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/main/java/org/apache/hadoop/yarn/server/router/webapp/RouterWebServices.java @@ -832,6 +832,23 @@ public class RouterWebServices implements RMWebServiceProtocol { return pipeline.getRootInterceptor().getAppAttempts(hsr, appId); } + @GET + @Path(RMWSConsts.CHECK_USER_ACCESS_TO_QUEUE) + @Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8, + MediaType.APPLICATION_XML + "; " + JettyUtils.UTF_8 }) + @Override + public Response checkUserAccessToQueue( + @PathParam(RMWSConsts.QUEUE) String queue, + @QueryParam(RMWSConsts.USER) String username, + @QueryParam(RMWSConsts.QUEUE_ACL_TYPE) + @DefaultValue("SUBMIT_APPLICATIONS") String queueAclType, + @Context HttpServletRequest hsr) throws AuthorizationException { + init(); + RequestInterceptorChainWrapper pipeline = getInterceptorChain(hsr); + return pipeline.getRootInterceptor().checkUserAccessToQueue(queue, + username, queueAclType, hsr); + } + @GET @Path(RMWSConsts.APPS_APPID_APPATTEMPTS_APPATTEMPTID) @Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8, diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/MockRESTRequestInterceptor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/MockRESTRequestInterceptor.java index 69afdeaf49c..f9abb5d3c7b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/MockRESTRequestInterceptor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/MockRESTRequestInterceptor.java @@ -318,6 +318,12 @@ public class MockRESTRequestInterceptor extends AbstractRESTRequestInterceptor { return new AppAttemptsInfo(); } + @Override + public Response checkUserAccessToQueue(String queue, String username, + String queueAclType, HttpServletRequest hsr) { + return Response.status(Status.OK).build(); + } + @Override public AppAttemptInfo getAppAttempt(HttpServletRequest req, HttpServletResponse res, String appId, String appAttemptId) { diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/PassThroughRESTRequestInterceptor.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/PassThroughRESTRequestInterceptor.java index ea985a2f232..e02203e199b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/PassThroughRESTRequestInterceptor.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-router/src/test/java/org/apache/hadoop/yarn/server/router/webapp/PassThroughRESTRequestInterceptor.java @@ -68,6 +68,14 @@ public class PassThroughRESTRequestInterceptor return getNextInterceptor().getAppAttempts(hsr, appId); } + @Override + public Response checkUserAccessToQueue(String queue, String username, + String queueAclType, HttpServletRequest hsr) + throws AuthorizationException { + return getNextInterceptor().checkUserAccessToQueue(queue, username, + queueAclType, hsr); + } + @Override public AppAttemptInfo getAppAttempt(HttpServletRequest req, HttpServletResponse res, String appId, String appAttemptId) {