From 172b23af33554b7d58fd41b022d983bcc2433da7 Mon Sep 17 00:00:00 2001 From: Eric Yang Date: Sat, 18 Feb 2017 18:34:13 -0800 Subject: [PATCH] HADOOP-14077. Add ability to access jmx via proxy. Contributed by Yuanbo Liu. --- .../AuthenticationWithProxyUserFilter.java | 43 +++--- .../hadoop/http/TestHttpServerWithSpengo.java | 15 +- .../v2/app/webapp/AppController.java | 7 +- .../hadoop/yarn/server/webapp/AppBlock.java | 135 ++++++++++-------- 4 files changed, 114 insertions(+), 86 deletions(-) diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/AuthenticationWithProxyUserFilter.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/AuthenticationWithProxyUserFilter.java index ea9b282ab89..751cf02ca54 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/AuthenticationWithProxyUserFilter.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/AuthenticationWithProxyUserFilter.java @@ -17,10 +17,11 @@ */ package org.apache.hadoop.security; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.security.authentication.server.AuthenticationFilter; import org.apache.hadoop.security.authorize.AuthorizationException; import org.apache.hadoop.security.authorize.ProxyUsers; -import org.apache.hadoop.util.HttpExceptionUtils; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; @@ -41,6 +42,9 @@ */ public class AuthenticationWithProxyUserFilter extends AuthenticationFilter { + public static final Log LOG = + LogFactory.getLog(AuthenticationWithProxyUserFilter.class); + /** * Constant used in URL's query string to perform a proxy user request, the * value of the DO_AS parameter is the user the request will be @@ -66,29 +70,30 @@ public class AuthenticationWithProxyUserFilter extends AuthenticationFilter { protected void doFilter(FilterChain filterChain, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - // authorize proxy user before calling next filter. - String proxyUser = getDoAs(request); + final String proxyUser = getDoAs(request); if (proxyUser != null) { - UserGroupInformation realUser = - UserGroupInformation.createRemoteUser(request.getRemoteUser()); - UserGroupInformation proxyUserInfo = - UserGroupInformation.createProxyUser(proxyUser, realUser); - try { - ProxyUsers.authorize(proxyUserInfo, request.getRemoteAddr()); - } catch (AuthorizationException ex) { - HttpExceptionUtils.createServletExceptionResponse(response, - HttpServletResponse.SC_FORBIDDEN, ex); - // stop filter chain if there is an Authorization Exception. - return; - } - - final UserGroupInformation finalProxyUser = proxyUserInfo; // Change the remote user after proxy user is authorized. - request = new HttpServletRequestWrapper(request) { + final HttpServletRequest finalReq = request; + request = new HttpServletRequestWrapper(finalReq) { + + private String getRemoteOrProxyUser() throws AuthorizationException { + UserGroupInformation realUser = + UserGroupInformation.createRemoteUser(finalReq.getRemoteUser()); + UserGroupInformation proxyUserInfo = + UserGroupInformation.createProxyUser(proxyUser, realUser); + ProxyUsers.authorize(proxyUserInfo, finalReq.getRemoteAddr()); + return proxyUserInfo.getUserName(); + } + @Override public String getRemoteUser() { - return finalProxyUser.getUserName(); + try { + return getRemoteOrProxyUser(); + } catch (AuthorizationException ex) { + LOG.error("Unable to verify proxy user: " + ex.getMessage(), ex); + } + return null; } }; diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java index cbdda907d02..3d3e020f412 100644 --- a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/http/TestHttpServerWithSpengo.java @@ -157,12 +157,25 @@ public void testAuthenticationWithProxyUser() throws Exception { Assert.assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode()); } - // userA cannot impersonate userC, it fails. + // userA cannot impersonate userC, but for /stacks, /jmx and /conf, + // they doesn't require users to authorize by default, so they + // can be accessed. for (String servlet : new String[]{"stacks", "jmx", "conf"}){ HttpURLConnection conn = authUrl .openConnection(new URL(serverURL + servlet + "?doAs=userC"), token); + Assert.assertEquals(HttpURLConnection.HTTP_OK, + conn.getResponseCode()); + } + + // "/logs" and "/logLevel" require admin authorization, + // only userA has the access. + for (String servlet : + new String[]{"logLevel", "logs"}) { + HttpURLConnection conn = authUrl + .openConnection(new URL(serverURL + servlet + "?doAs=userC"), + token); Assert.assertEquals(HttpURLConnection.HTTP_FORBIDDEN, conn.getResponseCode()); } diff --git a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java index e30e1b907dd..1c90cb9d172 100644 --- a/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java +++ b/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/main/java/org/apache/hadoop/mapreduce/v2/app/webapp/AppController.java @@ -392,10 +392,11 @@ void accessDenied(String s) { */ boolean checkAccess(Job job) { String remoteUser = request().getRemoteUser(); - UserGroupInformation callerUGI = null; - if (remoteUser != null) { - callerUGI = UserGroupInformation.createRemoteUser(remoteUser); + if (remoteUser == null) { + return false; } + UserGroupInformation callerUGI = + UserGroupInformation.createRemoteUser(remoteUser); if (callerUGI != null && !job.checkAccess(callerUGI, JobACL.VIEW_JOB)) { return false; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/webapp/AppBlock.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/webapp/AppBlock.java index 349a98c97f7..11bd9b47185 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/webapp/AppBlock.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/main/java/org/apache/hadoop/yarn/server/webapp/AppBlock.java @@ -31,6 +31,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.security.authentication.client.AuthenticationException; import org.apache.hadoop.security.http.RestCsrfPreventionFilter; import org.apache.hadoop.util.StringUtils; import org.apache.hadoop.yarn.api.ApplicationBaseProtocol; @@ -100,8 +101,8 @@ protected void render(Block html) { final GetApplicationReportRequest request = GetApplicationReportRequest.newInstance(appID); if (callerUGI == null) { - appReport = - appBaseProt.getApplicationReport(request).getApplicationReport(); + throw new AuthenticationException( + "Failed to get user name from request"); } else { appReport = callerUGI.doAs( new PrivilegedExceptionAction () { @@ -165,36 +166,76 @@ public ApplicationReport run() throws Exception { String schedulerPath = WebAppUtils.getResolvedRMWebAppURLWithScheme(conf) + "/cluster/scheduler?openQueues=" + app.getQueue(); + generateOverviewTable(app, schedulerPath, webUiType, appReport); + + Collection attempts; + try { + final GetApplicationAttemptsRequest request = + GetApplicationAttemptsRequest.newInstance(appID); + attempts = callerUGI.doAs( + new PrivilegedExceptionAction>() { + @Override + public Collection run() throws Exception { + return appBaseProt.getApplicationAttempts(request) + .getApplicationAttemptList(); + } + }); + } catch (Exception e) { + String message = + "Failed to read the attempts of the application " + appID + "."; + LOG.error(message, e); + html.p()._(message)._(); + return; + } + + createApplicationMetricsTable(html); + + html._(InfoBlock.class); + + generateApplicationTable(html, callerUGI, attempts); + + } + + /** + * Generate overview table for app web page. + * @param app app info. + * @param schedulerPath schedule path. + * @param webUiType web ui type. + * @param appReport app report. + */ + private void generateOverviewTable(AppInfo app, String schedulerPath, + String webUiType, ApplicationReport appReport) { ResponseInfo overviewTable = info("Application Overview") - ._("User:", schedulerPath, app.getUser()) - ._("Name:", app.getName()) - ._("Application Type:", app.getType()) - ._("Application Tags:", - app.getApplicationTags() == null ? "" : app.getApplicationTags()) - ._("Application Priority:", clarifyAppPriority(app.getPriority())) - ._( - "YarnApplicationState:", - app.getAppState() == null ? UNAVAILABLE : clarifyAppState(app - .getAppState())) - ._("Queue:", schedulerPath, app.getQueue()) - ._("FinalStatus Reported by AM:", - clairfyAppFinalStatus(app.getFinalAppStatus())) - ._("Started:", Times.format(app.getStartedTime())) - ._( - "Elapsed:", - StringUtils.formatTime(Times.elapsed(app.getStartedTime(), - app.getFinishedTime()))) - ._( - "Tracking URL:", - app.getTrackingUrl() == null - || app.getTrackingUrl().equals(UNAVAILABLE) ? null : root_url(app - .getTrackingUrl()), - app.getTrackingUrl() == null - || app.getTrackingUrl().equals(UNAVAILABLE) ? "Unassigned" : app - .getAppState() == YarnApplicationState.FINISHED - || app.getAppState() == YarnApplicationState.FAILED - || app.getAppState() == YarnApplicationState.KILLED ? "History" - : "ApplicationMaster"); + ._("User:", schedulerPath, app.getUser()) + ._("Name:", app.getName()) + ._("Application Type:", app.getType()) + ._("Application Tags:", + app.getApplicationTags() == null ? "" : app.getApplicationTags()) + ._("Application Priority:", clarifyAppPriority(app.getPriority())) + ._( + "YarnApplicationState:", + app.getAppState() == null ? UNAVAILABLE : clarifyAppState(app + .getAppState())) + ._("Queue:", schedulerPath, app.getQueue()) + ._("FinalStatus Reported by AM:", + clairfyAppFinalStatus(app.getFinalAppStatus())) + ._("Started:", Times.format(app.getStartedTime())) + ._( + "Elapsed:", + StringUtils.formatTime(Times.elapsed(app.getStartedTime(), + app.getFinishedTime()))) + ._( + "Tracking URL:", + app.getTrackingUrl() == null + || app.getTrackingUrl().equals(UNAVAILABLE) ? null : root_url(app + .getTrackingUrl()), + app.getTrackingUrl() == null + || app.getTrackingUrl().equals(UNAVAILABLE) ? "Unassigned" : app + .getAppState() == YarnApplicationState.FINISHED + || app.getAppState() == YarnApplicationState.FAILED + || app.getAppState() == YarnApplicationState.KILLED ? "History" + : "ApplicationMaster"); if (webUiType != null && webUiType.equals(YarnWebParams.RM_WEB_UI)) { LogAggregationStatus status = getLogAggregationStatus(); @@ -226,38 +267,6 @@ public ApplicationReport run() throws Exception { overviewTable._("AM container Node Label expression:", app.getAmNodeLabelExpression() == null ? "" : app.getAmNodeLabelExpression()); - - Collection attempts; - try { - final GetApplicationAttemptsRequest request = - GetApplicationAttemptsRequest.newInstance(appID); - if (callerUGI == null) { - attempts = appBaseProt.getApplicationAttempts(request) - .getApplicationAttemptList(); - } else { - attempts = callerUGI.doAs( - new PrivilegedExceptionAction> () { - @Override - public Collection run() throws Exception { - return appBaseProt.getApplicationAttempts(request) - .getApplicationAttemptList(); - } - }); - } - } catch (Exception e) { - String message = - "Failed to read the attempts of the application " + appID + "."; - LOG.error(message, e); - html.p()._(message)._(); - return; - } - - createApplicationMetricsTable(html); - - html._(InfoBlock.class); - - generateApplicationTable(html, callerUGI, attempts); - } protected void generateApplicationTable(Block html,