From 2e215484bd05cd5e3b7a81d3558c6879a05dd2d2 Mon Sep 17 00:00:00 2001 From: tgraves Date: Wed, 29 Apr 2015 21:25:42 +0000 Subject: [PATCH] YARN-3517. RM web ui for dumping scheduler logs should be for admins only (Varun Vasudev via tgraves) --- hadoop-yarn-project/CHANGES.txt | 3 + .../security/ApplicationACLsManager.java | 11 +++ .../webapp/CapacitySchedulerPage.java | 51 ++++++++---- .../resourcemanager/webapp/RMWebServices.java | 13 +++- .../webapp/TestRMWebServices.java | 77 +++++++++++++++++++ 5 files changed, 139 insertions(+), 16 deletions(-) diff --git a/hadoop-yarn-project/CHANGES.txt b/hadoop-yarn-project/CHANGES.txt index b5581d6907..6b8bde9d92 100644 --- a/hadoop-yarn-project/CHANGES.txt +++ b/hadoop-yarn-project/CHANGES.txt @@ -268,6 +268,9 @@ Release 2.8.0 - UNRELEASED YARN-2740. Fix NodeLabelsManager to properly handle node label modifications when distributed node label configuration enabled. (Naganarasimha G R via wangda) + YARN-3517. RM web ui for dumping scheduler logs should be for admins only + (Varun Vasudev via tgraves) + Release 2.7.1 - UNRELEASED INCOMPATIBLE CHANGES diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java index 4daaa68312..97b4163387 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/server/security/ApplicationACLsManager.java @@ -138,4 +138,15 @@ public boolean checkAccess(UserGroupInformation callerUGI, } return false; } + + /** + * Check if the given user in an admin. + * + * @param calledUGI + * UserGroupInformation for the user + * @return true if the user is an admin, false otherwise + */ + public final boolean isAdmin(final UserGroupInformation calledUGI) { + return this.adminAclsManager.isAdmin(calledUGI); + } } 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/CapacitySchedulerPage.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java index 2eeda6652b..fa22a0d830 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/webapp/CapacitySchedulerPage.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.Map; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.yarn.server.resourcemanager.ResourceManager; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerHealth; @@ -33,6 +34,7 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerLeafQueueInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.CapacitySchedulerQueueInfo; +import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.util.Times; import org.apache.hadoop.yarn.webapp.ResponseInfo; import org.apache.hadoop.yarn.webapp.SubView; @@ -190,28 +192,46 @@ public void render(Block html) { static class QueuesBlock extends HtmlBlock { final CapacityScheduler cs; final CSQInfo csqinfo; + private final ResourceManager rm; @Inject QueuesBlock(ResourceManager rm, CSQInfo info) { cs = (CapacityScheduler) rm.getResourceScheduler(); csqinfo = info; + this.rm = rm; } @Override public void render(Block html) { html._(MetricsOverviewTable.class); - // Dump CapacityScheduler debug logs - html.div() + + UserGroupInformation callerUGI = this.getCallerUGI(); + boolean isAdmin = false; + ApplicationACLsManager aclsManager = rm.getApplicationACLsManager(); + if (aclsManager.areACLsEnabled()) { + if (callerUGI != null && aclsManager.isAdmin(callerUGI)) { + isAdmin = true; + } + } else { + isAdmin = true; + } + + // only show button to dump CapacityScheduler debug logs to admins + if (isAdmin) { + html.div() .button() - .$onclick("confirmAction()").b("Dump scheduler logs")._() - .select().$id("time") - .option().$value("60")._("1 min")._() - .option().$value("300")._("5 min")._() - .option().$value("600")._("10 min")._() + .$style( + "border-style: solid; border-color: #000000; border-width: 1px;" + + " cursor: hand; cursor: pointer; border-radius: 4px") + .$onclick("confirmAction()").b("Dump scheduler logs")._().select() + .$id("time").option().$value("60")._("1 min")._().option() + .$value("300")._("5 min")._().option().$value("600")._("10 min")._() ._()._(); - StringBuilder script = new StringBuilder(); - script.append("function confirmAction() {") - .append(" b = confirm(\"Are you sure you wish to generate scheduler logs?\");") + StringBuilder script = new StringBuilder(); + script + .append("function confirmAction() {") + .append(" b = confirm(\"Are you sure you wish to generate" + + " scheduler logs?\");") .append(" if (b == true) {") .append(" var timePeriod = $(\"#time\").val();") .append(" $.ajax({") @@ -225,13 +245,14 @@ public void render(Block html) { .append(" alert(\"Scheduler log is being generated.\");") .append(" }, 1000);") .append(" }).fail(function(data){") - .append(" alert(\"Scheduler log generation failed. Please check the ResourceManager log for more informtion.\");") - .append(" console.log(data);") - .append(" });") - .append(" }") + .append( + " alert(\"Scheduler log generation failed. Please check the" + + " ResourceManager log for more informtion.\");") + .append(" console.log(data);").append(" });").append(" }") .append("}"); - html.script().$type("text/javascript")._(script.toString())._(); + html.script().$type("text/javascript")._(script.toString())._(); + } UL>> ul = html. div("#cs-wrapper.ui-widget"). 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 9aea62d1c8..4ce2b5478e 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 @@ -142,10 +142,12 @@ import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.SchedulerTypeInfo; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.StatisticsItemInfo; +import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.server.utils.BuilderUtils; import org.apache.hadoop.yarn.util.AdHocLogDumper; import org.apache.hadoop.yarn.util.ConverterUtils; import org.apache.hadoop.yarn.webapp.BadRequestException; +import org.apache.hadoop.yarn.webapp.ForbiddenException; import org.apache.hadoop.yarn.webapp.NotFoundException; import org.apache.hadoop.yarn.webapp.util.WebAppUtils; @@ -263,8 +265,17 @@ public SchedulerTypeInfo getSchedulerInfo() { @POST @Path("/scheduler/logs") @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) - public String dumpSchedulerLogs(@FormParam("time") String time) throws IOException { + public String dumpSchedulerLogs(@FormParam("time") String time, + @Context HttpServletRequest hsr) throws IOException { init(); + UserGroupInformation callerUGI = getCallerUserGroupInformation(hsr, true); + ApplicationACLsManager aclsManager = rm.getApplicationACLsManager(); + if (aclsManager.areACLsEnabled()) { + if (callerUGI == null || !aclsManager.isAdmin(callerUGI)) { + String msg = "Only admins can carry out this operation."; + throw new ForbiddenException(msg); + } + } ResourceScheduler rs = rm.getResourceScheduler(); int period = Integer.parseInt(time); if (period <= 0) { 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 e4614f8c9e..cd1d7716be 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 @@ -26,7 +26,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import java.io.File; import java.io.StringReader; +import java.security.Principal; import java.util.Arrays; import java.util.Collections; import java.util.Set; @@ -37,6 +39,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import org.apache.commons.io.FileUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.service.Service.STATE; import org.apache.hadoop.util.VersionInfo; @@ -54,9 +57,13 @@ import org.apache.hadoop.yarn.server.resourcemanager.nodelabels.RMNodeLabelsManager; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.QueueMetrics; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.ResourceScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler; +import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler; import org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler; import org.apache.hadoop.yarn.server.resourcemanager.webapp.dao.AppsInfo; +import org.apache.hadoop.yarn.server.security.ApplicationACLsManager; import org.apache.hadoop.yarn.util.YarnVersionInfo; +import org.apache.hadoop.yarn.webapp.ForbiddenException; import org.apache.hadoop.yarn.webapp.GenericExceptionHandler; import org.apache.hadoop.yarn.webapp.JerseyTestBase; import org.apache.hadoop.yarn.webapp.WebServicesTestUtils; @@ -643,4 +650,74 @@ public void testAppsRace() throws Exception { null, null, null, null, null, null, null, emptySet, emptySet); assertTrue(appsInfo.getApps().isEmpty()); } + + @Test + public void testDumpingSchedulerLogs() throws Exception { + + ResourceManager mockRM = mock(ResourceManager.class); + Configuration conf = new YarnConfiguration(); + HttpServletRequest mockHsr = mock(HttpServletRequest.class); + ApplicationACLsManager aclsManager = new ApplicationACLsManager(conf); + when(mockRM.getApplicationACLsManager()).thenReturn(aclsManager); + RMWebServices webSvc = + new RMWebServices(mockRM, conf, mock(HttpServletResponse.class)); + + // nothing should happen + webSvc.dumpSchedulerLogs("1", mockHsr); + Thread.sleep(1000); + checkSchedulerLogFileAndCleanup(); + + conf.setBoolean(YarnConfiguration.YARN_ACL_ENABLE, true); + conf.setStrings(YarnConfiguration.YARN_ADMIN_ACL, "admin"); + aclsManager = new ApplicationACLsManager(conf); + when(mockRM.getApplicationACLsManager()).thenReturn(aclsManager); + webSvc = new RMWebServices(mockRM, conf, mock(HttpServletResponse.class)); + boolean exceptionThrown = false; + try { + webSvc.dumpSchedulerLogs("1", mockHsr); + fail("Dumping logs should fail"); + } catch (ForbiddenException ae) { + exceptionThrown = true; + } + assertTrue("ForbiddenException expected", exceptionThrown); + exceptionThrown = false; + when(mockHsr.getUserPrincipal()).thenReturn(new Principal() { + @Override + public String getName() { + return "testuser"; + } + }); + try { + webSvc.dumpSchedulerLogs("1", mockHsr); + fail("Dumping logs should fail"); + } catch (ForbiddenException ae) { + exceptionThrown = true; + } + assertTrue("ForbiddenException expected", exceptionThrown); + + when(mockHsr.getUserPrincipal()).thenReturn(new Principal() { + @Override + public String getName() { + return "admin"; + } + }); + webSvc.dumpSchedulerLogs("1", mockHsr); + Thread.sleep(1000); + checkSchedulerLogFileAndCleanup(); + } + + private void checkSchedulerLogFileAndCleanup() { + String targetFile; + ResourceScheduler scheduler = rm.getResourceScheduler(); + if (scheduler instanceof FairScheduler) { + targetFile = "yarn-fair-scheduler-debug.log"; + } else if (scheduler instanceof CapacityScheduler) { + targetFile = "yarn-capacity-scheduler-debug.log"; + } else { + targetFile = "yarn-scheduler-debug.log"; + } + File logFile = new File(System.getProperty("yarn.log.dir"), targetFile); + assertTrue("scheduler log file doesn't exist", logFile.exists()); + FileUtils.deleteQuietly(logFile); + } }