diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java index 70e1863..f468d59 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ClientRMService.java @@ -1176,6 +1176,19 @@ public MoveApplicationAcrossQueuesResponse moveApplicationAcrossQueues( + callerUGI.getShortUserName() + " cannot perform operation " + ApplicationAccessType.MODIFY_APP.name() + " on " + applicationId)); } + + if (!accessToTargetQueueAllowed(callerUGI, application, + request.getTargetQueue())) { + RMAuditLogger.logFailure(callerUGI.getShortUserName(), + AuditConstants.MOVE_APP_REQUEST, + "User doesn't have permissions to move application to queue " + + request.getTargetQueue(), "ClientRMService", + AuditConstants.UNAUTHORIZED_USER, applicationId); + throw RPCUtil.getRemoteException(new AccessControlException("User " + + callerUGI.getShortUserName() + + " doesn't have permissions to move application to queue " + + request.getTargetQueue() + " on " + applicationId)); + } // Moves only allowed when app is in a state that means it is tracked by // the scheduler @@ -1209,6 +1222,24 @@ public MoveApplicationAcrossQueuesResponse moveApplicationAcrossQueues( return response; } + /** + * Check if the submission of an application to the target queue is allowed + * @param callerUGI the caller UGI + * @param application the application to move + * @param targetQueue the queue to move the application to + * @return true if submission is allowed, false otherwise + */ + private boolean accessToTargetQueueAllowed(UserGroupInformation callerUGI, + RMApp application, String targetQueue) { + return + queueACLsManager.checkAccess(callerUGI, + QueueACL.SUBMIT_APPLICATIONS, application, + Server.getRemoteAddress(), null, targetQueue)|| + queueACLsManager.checkAccess(callerUGI, + QueueACL.ADMINISTER_QUEUE, application, + Server.getRemoteAddress(), null, targetQueue); + } + private String getRenewerForToken(Token token) throws IOException { UserGroupInformation user = UserGroupInformation.getCurrentUser(); 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 c9d55f1..780e7b1 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 @@ -80,4 +80,38 @@ public boolean checkAccess(UserGroupInformation callerUGI, QueueACL acl, return scheduler.checkAccess(callerUGI, acl, app.getQueue()); } } + + /* + * Check access to a targetQueue in the case of a move of an application + * The application cannot contain the destination queue since it has not + * been moved yet, thus need to pass it in separately + * + * @param callerUgi the caller UGI + * @param acl the acl for the Queue to check + * @param app the application to move + * @param remoteAddress + * @param forwardedAddresses + * @param targetQueue the name of the queue to move the application to + * @return true if submission is allowed, false otherwise + */ + public boolean checkAccess(UserGroupInformation callerUGI, QueueACL acl, + RMApp app, String remoteAddress, List forwardedAddresses, + String targetQueue) { + if (!isACLsEnable) { + return true; + } + if (scheduler instanceof CapacityScheduler) { + CSQueue queue = ((CapacityScheduler) scheduler).getQueue(targetQueue); + if (queue == null) { + return false; + } + return authorizer.checkPermission( + new AccessRequest(queue.getPrivilegedEntity(), callerUGI, + SchedulerUtils.toAccessType(acl), + app.getApplicationId().toString(), app.getName(), + remoteAddress, forwardedAddresses)); + } else { + return scheduler.checkAccess(callerUGI, acl, targetQueue); + } + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java index 2a7971e..8717a2c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/java/org/apache/hadoop/yarn/server/resourcemanager/TestClientRMService.java @@ -33,6 +33,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.net.InetSocketAddress; +import java.security.AccessControlException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -130,6 +132,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMApp; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppEvent; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppImpl; +import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppMoveEvent; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.RMAppState; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttempt; import org.apache.hadoop.yarn.server.resourcemanager.rmapp.attempt.RMAppAttemptImpl; @@ -565,11 +568,172 @@ public void testMoveAbsentApplication() throws YarnException { ApplicationId applicationId = BuilderUtils.newApplicationId(System.currentTimeMillis(), 0); MoveApplicationAcrossQueuesRequest request = - MoveApplicationAcrossQueuesRequest.newInstance(applicationId, "newqueue"); + MoveApplicationAcrossQueuesRequest.newInstance(applicationId, + "newqueue"); rmService.moveApplicationAcrossQueues(request); } @Test + public void testMoveApplicationSubmitTargetQueue() throws Exception { + // move the application as the owner + ApplicationId applicationId = getApplicationId(1); + UserGroupInformation aclUGI = UserGroupInformation.getCurrentUser(); + ClientRMService rmService = createClientRMServiceForMoveApplicationRequest( + applicationId, "allowed_queue", QueueACL.SUBMIT_APPLICATIONS, aclUGI); + + // move as the owner queue in the acl + MoveApplicationAcrossQueuesRequest moveAppRequest = MoveApplicationAcrossQueuesRequest. + newInstance(applicationId, "allowed_queue"); + rmService.moveApplicationAcrossQueues(moveAppRequest); + + // move as the owner queue not in the acl + moveAppRequest = MoveApplicationAcrossQueuesRequest.newInstance( + applicationId, "not_allowed"); + + try { + rmService.moveApplicationAcrossQueues(moveAppRequest); + Assert.fail("The request should fail with an AccessControlException"); + } catch (YarnException rex) { + Assert.assertTrue("AccessControlException is expected", + rex.getCause() instanceof AccessControlException); + } + + // ACL is owned by "moveuser", move is performed as a different user + aclUGI = UserGroupInformation.createUserForTesting("moveuser", + new String[]{}); + ClientRMService rmService2 = + createClientRMServiceForMoveApplicationRequest(applicationId, + "move_queue", QueueACL.SUBMIT_APPLICATIONS, aclUGI); + + // access to the queue not OK: user not allowed in this queue + MoveApplicationAcrossQueuesRequest moveAppRequest2 = + MoveApplicationAcrossQueuesRequest. + newInstance(applicationId, "move_queue"); + try { + rmService2.moveApplicationAcrossQueues(moveAppRequest2); + Assert.fail("The request should fail with an AccessControlException"); + } catch (YarnException rex) { + Assert.assertTrue("AccessControlException is expected", + rex.getCause() instanceof AccessControlException); + } + + // execute the move as the acl owner + // access to the queue OK: user allowed in this queue + aclUGI.doAs(new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + return rmService2.moveApplicationAcrossQueues(moveAppRequest2); + } + }); + } + + @Test + public void testMoveApplicationAdminTargetQueue() throws Exception { + ApplicationId applicationId = getApplicationId(1); + UserGroupInformation aclUGI = UserGroupInformation.getCurrentUser(); + ClientRMService rmService = + createClientRMServiceForMoveApplicationRequest(applicationId, + "allowed_queue", QueueACL.ADMINISTER_QUEUE, aclUGI); + + // user is admin move to queue in acl + MoveApplicationAcrossQueuesRequest moveAppRequest = + MoveApplicationAcrossQueuesRequest.newInstance(applicationId, + "allowed_queue"); + rmService.moveApplicationAcrossQueues(moveAppRequest); + + // user is admin move to queue not in acl + moveAppRequest = MoveApplicationAcrossQueuesRequest.newInstance( + applicationId, "not_allowed"); + + try { + rmService.moveApplicationAcrossQueues(moveAppRequest); + Assert.fail("The request should fail with an AccessControlException"); + } catch (YarnException rex) { + Assert.assertTrue("AccessControlException is expected", + rex.getCause() instanceof AccessControlException); + } + + // ACL is owned by "moveuser", move is performed as a different user + aclUGI = UserGroupInformation.createUserForTesting("moveuser", + new String[]{}); + ClientRMService rmService2 = + createClientRMServiceForMoveApplicationRequest(applicationId, + "move_queue", QueueACL.ADMINISTER_QUEUE, aclUGI); + + // no access to this queue + MoveApplicationAcrossQueuesRequest moveAppRequest2 = + MoveApplicationAcrossQueuesRequest. + newInstance(applicationId, "move_queue"); + + try { + rmService2.moveApplicationAcrossQueues(moveAppRequest2); + Assert.fail("The request should fail with an AccessControlException"); + } catch (YarnException rex) { + Assert.assertTrue("AccessControlException is expected", + rex.getCause() instanceof AccessControlException); + } + + // execute the move as the acl owner + // access to the queue OK: user allowed in this queue + aclUGI.doAs(new PrivilegedExceptionAction() { + @Override + public Object run() throws Exception { + return rmService2.moveApplicationAcrossQueues(moveAppRequest2); + } + }); + } + + /** + * Create an instance of ClientRMService for testing + * moveApplicationAcrossQueues requests. + * @param applicationId the application + * @param allowedQueue the queue to allow the move to + * @param acl the acl to check: submit app or queue admin + * @return ClientRMService + */ + private ClientRMService createClientRMServiceForMoveApplicationRequest( + ApplicationId applicationId, String allowedQueue, QueueACL acl, + UserGroupInformation aclUser) { + ConcurrentHashMap apps = new ConcurrentHashMap<>(); + apps.put(applicationId, mock(RMApp.class)); + + RMContext rmContext = mock(RMContext.class); + when(rmContext.getRMApps()).thenReturn(apps); + Dispatcher dispatcher = mock(Dispatcher.class); + when(rmContext.getDispatcher()).thenReturn(dispatcher); + when(rmContext.getDispatcher().getEventHandler()).thenReturn(event -> { + if (event instanceof RMAppMoveEvent) { + // signal move event is processed successfully + ((RMAppMoveEvent) event).getResult().set(null); + } + }); + + ApplicationACLsManager aclsManager = mock(ApplicationACLsManager.class); + when(aclsManager.checkAccess( + any(UserGroupInformation.class), + any(ApplicationAccessType.class), + any(), any(ApplicationId.class))).thenReturn(true); + + // ACL that checks the queue is allowed + QueueACLsManager queueACLsManager = mock(QueueACLsManager.class); + when(queueACLsManager.checkAccess( + any(UserGroupInformation.class), + any(QueueACL.class), + any(RMApp.class), + any(String.class), + any(List.class), + any(String.class))).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + UserGroupInformation user = (UserGroupInformation) args[0]; + return (allowedQueue.equals(args[5]) && acl.equals(args[1]) && + aclUser.getShortUserName().equals(user.getShortUserName())); + }); + + return new ClientRMService(rmContext, null, null, aclsManager, + queueACLsManager, null); + } + + @Test public void testGetQueueInfo() throws Exception { YarnScheduler yarnScheduler = mock(YarnScheduler.class); RMContext rmContext = mock(RMContext.class);